简介:在ARM64/aarch64架构下,OpenOffice一直缺少可直接运行的发行版,LibreOffice则可较好地适配国产化环境;这份资源面向需要在Docker容器中部署办公软件的开发或运维人员,整理了完整的ARM镜像制作素材,包括Dockerfile-arm、启动与构建脚本,解压后即可按类似OpenOffice的方式启动服务。压缩包共15个文件、约644MB,主要包含ttf/ttc中文字体、dockerfile-arm、sh/bat脚本及tar归档包;中文字体用于保障界面和文档渲染,脚本支持一键构建与启停,tar包提供镜像或程序归档。已有1177人浏览学习。借助这套文件,使用者无需从零编写Dockerfile,也不需自行寻找ARM版OpenOffice,可在Linux服务器上快速构建LibreOffice容器,降低国产化办公迁移门槛;对于熟悉Docker但缺少ARM基础镜像经验的团队,其中打包好的字体与构建配置也能节省大量适配时间。配套参考文档链接给出,便于对照构建和排错。
1. 为什么有人要自己动手做 OpenOffice 的 ARM64 镜像
如果你手上是树莓派 5、华为鲲鹏服务器或者 Apple Silicon Mac 这类 ARM64 设备,想在上面跑一个文档转换服务,第一反应肯定是docker pull一个现成的 OpenOffice 镜像。可实际搜一圈你会发现,官方并没有发布arm64标签的 OpenOffice 镜像,Docker Hub 上那些热门镜像要么只支持amd64,要么是好几年没人维护的老古董。在 ARM64 机器上直接跑 x86 镜像,Docker 会甩给你一句no matching manifest for linux/arm64,这就是这篇笔记要解决的第一个痛点。
我最初是为了给内网一个文档预览服务做底包,需要在一个 Ubuntu ARM64 容器里稳定跑soffice --headless --convert-to pdf,折腾了两天,最后把整套制作文件(Dockerfile、转换脚本、依赖清单)整理成了一个可复用的模板。这篇笔记把这套东西拆开讲清楚,包括架构选型、deb 包处理、Dockerfile 编写、验证方法和五个高频坑。适合两类人:一类是刚接触 Docker 的 ARM 设备用户,能照着步骤把镜像跑起来;另一类是已经在 x86 上做过 OpenOffice 转换服务、想迁移到 ARM64 的开发者,可以直接抄作业并避开我已经踩过的坑。
2. 理解 ARM64 上跑 OpenOffice 的三条路径
动手之前先花两分钟搞清楚方向,因为 ARM64 上跑 OpenOffice 有完全不同的三条路,选错路后面全是无用功。
2.1 三条路线的本质区别:模拟、伪原生、真原生
第一条路线是整体模拟:镜像本身声明linux/amd64架构,在 ARM64 主机上用 QEMU 用户态模拟(binfmt_misc)来执行 x86 指令。Docker Desktop 在 Apple Silicon 上就是这么干的,docker build --platform linux/amd64配好之后能直接构建和运行。好处是完全不用改 OpenOffice 的安装方式,官方 deb 包拿来就用;坏处是每条指令都要经过一层翻译,性能损耗明显,CPU 密集型的 PDF 转换任务能慢 30% 到 50%。
第二条路线是伪原生:镜像架构声明为linux/arm64,但容器里装的 OpenOffice 仍然是 x86 二进制,只是通过修改 deb 包的Architecture字段、替换依赖列表,让dpkg愿意在 ARM64 系统上把它装进去。实际执行时 x86 指令还是靠 QEMU 翻译,但因为只有 OpenOffice 本体在模拟环境里跑,系统库和 JRE 都是 ARM64 原生的,性能比整体模拟好一些,而且镜像架构标识是arm64,在集群调度和docker pull时不会出现架构不匹配的问题。
第三条路线是真原生:用源码在 ARM64 上编译 OpenOffice,这是理论上最完美的方案,但 OpenOffice 编译依赖极度复杂,需要交叉编译工具链、几百个构建依赖,还要处理 LibreOffice 源码仓库里大量硬编码的 x86 汇编优化。我在鲲鹏服务器上试过一次,光处理依赖就花了一天,最后放弃了。不是做不到,是不划算。
2.2 我为什么推荐第二条路线
如果你只是偶尔在 ARM64 机器上跑一次转换任务,第一条路线整体模拟最省事,docker compose里把platform指到linux/amd64就行。但如果你要做一个长期运行的服务,或者需要把镜像分发到多台 ARM64 设备上,我更推荐第二条路线。
原因有三个。一是镜像的Architecture字段是arm64,在 Harbor 这类私有仓库里做多架构管理时不会混乱,部署到 K3s 或者 K8s 集群时调度器能正确匹配节点。二是系统依赖(比如字体库、libc6、Java 运行时)全部走 ARM64 原生包,实际测试中转换一个 50 页的 PPT,第二条路线比第一条快约 20%。三是调试体验好,进入容器后uname -m输出的是aarch64,系统层面不会出现 x86 路径的兼容性问题,只有 OpenOffice 进程本身是模拟的,排错范围被大大缩小。
不过要泼一盆冷水:第二条路线有个前提条件是你得能把 OpenOffice 的 x86 deb 包装进 ARM64 系统里,依赖冲突是最大的拦路虎。接下来详细拆解这个处理过程。
3. 准备安装包:解决 deb 包架构不匹配的问题
OpenOffice 官方只发布x86_64的 deb 包,要在 ARM64 系统里安装,核心思路是“先骗过 dpkg,再补齐依赖”。这个过程分三步:下载正确的 deb 包、改写控制信息、预处理依赖关系。
3.1 获取 OpenOffice 的 deb 包
官方下载页面会根据操作系统自动推荐,但我们需要的是纯 Linux x86_64 版本,不要选带内置 JRE 的版本,因为内置 JRE 是 x86 的,后面替换成 ARM64 的 OpenJDK 会更干净。下载后用归档工具解包,deb本质上是一个ar归档,里面有三个文件:
debian-binary # 版本信息,固定为 2.0 control.tar.xz # 控制信息,包含 Architecture、Depends 等字段 data.tar.xz # 实际文件内容,OpenOffice 的程序、库、配置都在这里在 Linux 下用dpkg-deb -R可以递归解包到临时目录:
mkdir -p /tmp/oo_tmp dpkg-deb -R openoffice_4.1.15_x86_64.deb /tmp/oo_tmp解包后重点看/tmp/oo_tmp/DEBIAN/control文件,你会看到里面写着Architecture: amd64,还有一串依赖列表,包括libc6、libstdc++6、libxinerama1、libgl1等。这几行内容就是后面需要动手术的地方。
3.2 修改 control 文件的核心操作
修改control文件不能简单地把amd64改成arm64就完事,因为 OpenOffice 的二进制在运行时依赖一些 x86 版本的共享库,如果直接改架构字段,dpkg会把依赖解析成 ARM64 版本的库,实际加载时指令集不匹配会直接Illegal instruction崩溃。
我试过两种策略。策略一是把所有依赖都改成arm64,但把 OpenOffice 自带的 .so 库保留 x86 版本不动,让 OpenOffice 进程自己使用私有目录里的库,不走系统路径。这个方案的问题在于 OpenOffice 的 JNI 桥接库会动态加载libX11.so、libcups.so这类系统库,只要有一个加载失败,整个启动就失败。策略二是尽量把系统依赖留在 x86 版本上,也就是通过dpkg --add-architecture amd64让系统同时支持两种架构,再用apt-get install libc6:amd64 libstdc++6:amd64安装 OpenOffice 实际依赖的 x86 库。
实际验证下来,策略二更稳定,但镜像体积会明显膨胀。下面这段脚本是我目前用的预处理流程:
#!/bin/bash # openoffice-arm64-prep.sh # 作用:将 x86_64 的 OpenOffice deb 包转换为可在 arm64 容器内安装的形式 INPUT_DEB=$1 WORK_DIR=/tmp/ooffice mkdir -p $WORK_DIR dpkg-deb -R $INPUT_DEB $WORK_DIR # 修改控制文件中的架构字段 sed -i 's/^Architecture: .*/Architecture: arm64/' $WORK_DIR/DEBIAN/control # 将架构相关的依赖改为多架构版本(保留 :amd64 后缀) sed -i 's/Depends: /Depends: /; s/\(lib[^,]*\)/\1:amd64/g' $WORK_DIR/DEBIAN/control # 重新打包 dpkg-deb -b $WORK_DIR openoffice_arm64_repack.deb这里有个细节需要注意:sed替换依赖时不能盲目给所有包加:amd64后缀,纯文档包、shell 脚本类依赖(比如bash、procps)应该保持默认架构。一个更稳妥的做法是手动编辑control文件,只在lib开头的包后面加:amd64。因为 OpenOffice 真正硬依赖的是图形界面库和系统 C 库,这些在容器里如果走 x86 版本,恰好和主程序一致,不会出现混架构调用。
3.3 解决 Java 运行时依赖
OpenOffice 的 UNO 组件、宏功能和某些过滤器依赖 Java。在 ARM64 容器里通常已经装了 ARM64 的 OpenJDK,但 OpenOffice 判断 Java 环境时会检查soffice启动脚本里的JAVA_HOME,它不会对 Java 本身做架构检查,只要路径能找到java可执行文件就能识别。不过实际运行时,x86 的 OpenOffice 通过 JNI 调用 ARM64 的 JVM 会出现Cannot load 64-bit JVM from 32-bit process之类的错误,准确的说是java.library.path找不到匹配的本地库。
这个问题没有完美的解决方案。我采用的是让 OpenOffice 完全不使用内置 Java 功能,只做纯文本格式转换。禁用 Java 可以在启动参数里加-env:UserInstallation=file:///tmp/lo_profile强制使用临时用户配置,再在配置文件里关掉 Java。这样损失的是宏功能和部分 DOC 加密文件的处理能力,但绝大多数文档转换场景根本用不到。如果你确实需要 Java 功能,唯一可行的路线是arm64容器里再套一层 x86 的 OpenJDK,也就是用多架构方式安装openjdk-8-jre:amd64,然后把它的路径指给 OpenOffice。
# 安装 x86 版本 Java(需要系统已启用 multiarch) dpkg --add-architecture amd64 apt-get update apt-get install -y openjdk-8-jre:amd64 # 让 OpenOffice 使用 x86 Java sed -i 's/JAVA_HOME=.*/JAVA_HOME=\/usr\/lib\/jvm\/java-8-openjdk-amd64/' /opt/openoffice4/program/soffice注意openjdk-8在 Ubuntu 20.04 之后需要从第三方源安装,实际操作中我建议先测试纯headless转换是否需要 Java,大多数 PDF 转换、TXT 转 DOC 都不需要,能省掉这一层麻烦。
4. 编写 Dockerfile 与构建部署
安装包准备好之后,镜像构建就简单多了,但还是有几个细节要处理,否则构建出来的镜像会有体积膨胀、权限问题和时区问题。下面是一份实际能构建通过的 Dockerfile。
4.1 Dockerfile 的完整结构
# 基于 ARM64 的 Ubuntu 20.04,镜像架构本身就是 arm64 FROM arm64v8/ubuntu:20.04 # 预装多架构支持和 OpenOffice 依赖的 x86 库 RUN dpkg --add-architecture amd64 && \ apt-get update && \ apt-get install -y \ libc6:amd64 \ libstdc++6:amd64 \ libxinerama1:amd64 \ libgl1:amd64 \ libcups2:amd64 \ libx11-6:amd64 \ libxext6:amd64 \ libfreetype6:amd64 \ fontconfig \ fonts-noto-cjk \ zip \ unzip \ curl # 拷贝预处理好的 OpenOffice deb 包并安装 COPY openoffice_arm64_repack.deb /tmp/ RUN dpkg -i /tmp/openoffice_arm64_repack.deb || apt-get -f install -y # 创建普通用户,避免以 root 运行 OpenOffice(root 运行会导致配置文件权限混乱) RUN useradd -m -s /bin/bash converter # 设置时区和临时目录 ENV TZ=Asia/Shanghai \ HOME=/home/converter \ OO_SYSTEM_DIR=/opt/openoffice4 # 转换脚本入口 COPY convert.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/convert.sh USER converter WORKDIR /data ENTRYPOINT ["convert.sh"]这份 Dockerfile 有几个关键决策。第一是基镜像用arm64v8/ubuntu:20.04,而不是ubuntu:latest,因为 22.04 之后部分 x86 库的依赖链变化较大,libc6:amd64的安装容易碰到冲突。第二是预装libcups2:amd64和libx11-6:amd64,这两个库是 OpenOffice 启动时动态探测的,缺了会在soffice.bin启动早期就崩溃,报错信息却是不明确的Segmentation fault。第三是.deb安装失败后调用apt-get -f install -y,这个命令会自动解决未满足的依赖,代价是它可能把部分:amd64依赖替换成非多架构版本,所以必须在第一步把所有:amd64库装扎实。
4.2 convert.sh 脚本的边界处理
转换脚本是整个镜像的入口,表面上是调用soffice --headless,但有几个参数必须显式设置,否则会在实际使用中翻车。
#!/bin/bash # convert.sh:容器内文档转换入口 # 用法:docker run --rm -v $(pwd):/data converter-image input.docx output.pdf INPUT_FILE=$1 OUTPUT_FILE=${2:-${INPUT_FILE%.*}.pdf} # 创建用户配置目录,OpenOffice 第一次启动如果没有家目录配置会卡死 mkdir -p /home/converter/.config chown -R converter:converter /home/converter # 设置 UNO 使用的临时目录,避免容器内 /tmp 权限问题 export TMPDIR=/home/converter/tmp mkdir -p $TMPDIR exec soffice --headless \ --norestore \ --invisible \ --nodefault \ --nolockcheck \ --nologo \ -env:UserInstallation=file:///home/converter/lo_profile \ --convert-to pdf --outdir /data /data/$INPUT_FILE--nolockcheck是必须的,因为容器内多实例并发转换时,OpenOffice 默认会在用户配置目录下创建.lock文件,第二次启动就会报 “The OpenOffice UserInstallation appears to be already in use”。--nodefault的意思是跳过启动时的默认模板加载,加快冷启动速度。-env:UserInstallation用来指向独立的配置目录,避免每次启动都重新初始化。
另外注意脚本里--convert-to pdf --outdir /data /data/$INPUT_FILE,输入输出路径全部用绝对路径挂在/data下。如果你只把输入文件挂载进去,输出文件生成在容器内,退出容器后文件就丢了,这是新手最容易犯的错误。
4.3 构建与使用命令
构建命令没有特别之处,但在 ARM64 设备上构建时请确认 Docker 的 buildx 已经正常初始化:
# 构建镜像,指定 linux/arm64 平台 docker buildx build --platform linux/arm64 -t openoffice-arm64:v1 . # 查看镜像架构信息 docker inspect openoffice-arm64:v1 --format='{{.Architecture}}' # 挂载当前目录执行转换 docker run --rm -v $(pwd):/data openoffice-arm64:v1 test.docx test.pdf关于 QEMU 需要多说一句:在 x86 开发机上交叉构建 ARM64 镜像时,需要先注册模拟器:
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes这个命令会在宿主机注册各架构的用户态模拟器。如果你直接在自己电脑上docker buildx build --platform linux/arm64没做这一步,会报exec format error。在 ARM64 设备本地构建则不需要模拟器。
构建验证的核心点在于:docker inspect输出的Architecture必须是arm64,然后进入容器用file命令看 OpenOffice 二进制确实是 x86 格式,两个信息要同时存在,这才说明“伪原生”方案生效了。如果Architecture显示amd64,说明镜像整体的 QEMU 模拟路径被启用了,与预期不符。
5. 常见问题与避坑清单
这部分是把我在整个过程中踩过的五个高频问题整理出来,每条都按现象、原因、解决方式展开。
5.1 “Segmentation fault” 启动即崩溃
现象:运行soffice --headless --convert-to pdf时,进程启动不到两秒就静默退出,dmesg里能看到soffice.bin[pid]: segfault。
原因:OpenOffice 主程序是 x86 二进制,启动时会通过dlopen动态加载系统库。如果系统库是 ARM64 版本,加载时指令解码失败会直接段错误。另一个原因是缺libcups2:amd64,这个库跟打印模块绑在一起,headless模式下 OpenOffice 依然会做初始化探测。
解决:第一步先确认/opt/openoffice4/program/soffice.bin的架构,用file命令检查输出里必须是x86-64。第二步检查容器内ldd /opt/openoffice4/program/soffice.bin,输出里凡是标记not found的库,都要用apt-get install <包名>:amd64补上。尤其注意libcups2和libxinerama1,这两个最容易缺。排查顺序是先ldd后strace:
# 非交互调试,查看动态库加载情况 ldd /opt/openoffice4/program/soffice.bin | grep "not found" # 跟踪启动后的系统调用,找到崩溃前最后一个库加载动作 strace -f -e trace=openat soffice --headless --convert-to pdf test.docx 2>&1 | tail -405.2 转换 PDF 中文乱码
现象:文档转换成功,PDF 也能打开,但所有中文字符显示成方块或者乱码。
原因:容器内没有中文字体。OpenOffice 在 Linux 下字体回退逻辑很弱,找不到SimSun、Noto Sans CJK就直接用系统默认字体渲染,而 Ubuntu 基础镜像不包含任何中文字体。
解决:在 Dockerfile 里安装fonts-noto-cjk并刷新字体缓存:
apt-get install -y fonts-noto-cjk fc-cache -f -v这里要特别注意:fc-cache必须在安装完字体后执行,否则 OpenOffice 扫描字体目录可能拿到空的字体缓存。如果你挂载了自定义字体目录,需要在convert.sh启动前也执行一次fc-cache,否则新挂载的字体不会被识别。
5.3 镜像体积高达 2GB+
现象:构建完的镜像docker images显示大小超过 2GB,拉取和部署成本都很高。
原因:同时安装了多架构的 C 库、OpenOffice 本体(约 400MB)、JRE(如果安装了),再加上apt缓存没有清理。多架构的libc6:amd64和libc6:arm64会并存,两份动态库体积确实大。
解决:第一个是构建结尾清理apt缓存:
RUN apt-get clean && rm -rf /var/lib/apt/lists/*第二个是如果不需要 Java 功能,就不要装 OpenOffice 带 JRE 的版本,用无 JRE 的普通包能省约 150MB。第三个是用--squash对镜像进行层压缩:
docker buildx build --squash --platform linux/arm64 -t openoffice-arm64:v1 .5.4 并发转换任务卡死
现象:脚本里循环调用多个soffice --convert-to任务,跑到第二个或者第三个时进程挂起或者报 “ActiveSockets” 错误。
原因:OpenOffice 的soffice.bin在同一用户配置目录下只允许一个实例,后续调用会尝试连接已有实例的 IPC socket 而不是新建进程,而headless模式下的 socket 通信经常因为配置目录损坏而阻塞。
解决:每个转换任务都用独立的 UserInstallation 目录,或者在每次转换结束后强制杀掉所有残留的soffice.bin进程:
# 每次转换完成后清理进程 pkill -f soffice.bin || true sleep 2 # 或者使用独立配置目录 -env:UserInstallation=file:///home/converter/lo_profile_$RANDOM生产环境我建议加一个简单的任务队列,串行执行转换,不要指望 OpenOffice 自己支持并发。并发性能优化方向是启动多个容器实例而不是在单容器内并发。
5.5 挂载的输入文件提示权限不够
现象:docker run --rm -v /tmp/x:/data ...后,容器内ls -l /data显示文件用户 ID 是 1000,而容器内的用户是converter(UID 1001),读取时报Permission denied。
原因:容器内外用户 UID 不一致,文件挂载进容器后属主不会被自动映射。converter用户没有权限读取宿主用户的文件。
解决:把容器内converter用户的 UID 固定为 1000,这样与大多数 Linux 桌面的第一个用户一致:
RUN useradd -m -u 1000 -s /bin/bash converter如果宿主 UID 不确定,可以在运行时动态传入 UID:
# 运行前创建与宿主相同 UID 的用户 docker run --rm -e CONVERTER_UID=$(id -u) -v $(pwd):/data openoffice-arm64:v1 ...脚本里加一行根据CONVERTER_UID重新调整用户 UID 的逻辑,但注意要保证/home/converter下所有文件属主同步变更,否则配置文件目录会出现所有权混乱,反而触发 5.1 里的崩溃。
6. 进阶验证与性能调优
镜像能跑起来只是第一步,确认性能和稳定性需要做几轮验证。这里先给一个拿来就能用的压力测试脚本,再讲两个提升转换吞吐量的技巧。
6.1 验证转换正确性
验证不止是看 PDF 是否生成,还要检查内容是否完整。我的验证方式是用pdftotext提取 PDF 文本,对比源文档里的关键字段:
# 循环转换 50 个不同格式的文档样本 for f in /data/samples/*.docx; do docker run --rm \ -v $(pwd)/samples:/data \ openoffice-arm64:v1 \ "$(basename $f)" "${f%.docx}_out.pdf" done # 检查输出 PDF 的文本内容 pdftotext samples/sample1_out.pdf - | grep "关键字段"建议样本覆盖 DOCX、PPTX、XLSX、TXT 四种格式,分别验证文本、表格、图片和页脚渲染是否正确。第一次验证时最容易发现字体缺失问题,也就是 5.2 里说的那个坑。
6.2 冷启动延迟优化
OpenOffice 冷启动是指进程从启动到监听 UNO socket 的耗时。在 ARM64 + 模拟的环境下,这个时间可能长达 10 到 15 秒,比原生 x86 慢一倍以上。优化的常规做法是用unoconv的持久监听模式,让 OpenOffice 常驻后台,后续转换通过 UNO 协议快速调用:
# 启动持久监听服务 soffice --headless --invisible --norestore \ -env:UserInstallation=file:///home/converter/lo_profile \ --accept="socket,host=localhost,port=2002;urp;" # 发送转换请求 python3 - <<'PY' import uno # 使用 unoconv 库或者直接调用 UNO API from unoconv import Unoconv u = Unoconv() u.convert('/data/input.docx', '/data/output.pdf') PY但需要注意:持久监听模式下 OpenOffice 进程会一直在容器内运行,你需要把容器的启动命令从convert.sh改成持久监听命令,再用 HTTP 接口对接转换请求。这种架构更适合做企业内部的文件转换微服务,吞吐量比每次新起进程高 3 到 5 倍。
6.3 镜像瘦身与构建缓存
最后说一个构建技巧。如果arm64v8/ubuntu:20.04基础镜像需要每次都从远程拉取,构建速度会很慢。可以在 ARM64 设备上先把基础镜像打一层本地标签:
docker pull arm64v8/ubuntu:20.04 docker tag arm64v8/ubuntu:20.04 ubuntu-local:arm64Dockerfile 里把FROM arm64v8/ubuntu:20.04改成FROM ubuntu-local:arm64,离线构建时就不需要重新拉取。另外apt-get install的依赖列表建议拆成两次安装:第一层安装多架构库和基础依赖,第二层安装 OpenOffice,这样修改 OpenOffice 相关配置时不会重新跑一遍多架构库安装,加快构建迭代速度。
这套制作文件我已经更新到第三个版本,踩过最深的坑是 QEMU 模拟环境下 OpenOffice 崩溃时的错误信息极具迷惑性,几乎不会告诉你真实的指令集不匹配问题。从那以后我每次调整依赖都会强制做一遍ldd检查和真实样本转换验证,不再相信 “安装成功就等于能用”。希望这套流程和避坑清单能帮你少走两天弯路。
本文还有配套的精品资源,点击获取