共计 7803 个字符,预计需要花费 20 分钟才能阅读完成。
LibreOffice 是由文档基金会开发的自由及开放源代码的办公室套件.LibreOffice 套件包含文字处理器,电子表格,演示文稿程序,数量库管理程序及创建和编辑数学公式的应用程序。借助 LibreOffice 的命令行接口可以方便地将 office 文件转换成 pdf。如下所示:
$ soffice –convert-to pdf –outdir /tmp /tmp/test.doc
一个完整版本的 LibreOffice 大小为 2 GB,而函数计算运行时缓存目录 / tmp 空间限制为 512M,zip 程序包大小限制为 50M。好在社区已经有项目 aws-lambda-libreoffice 成功的将 libreoffice 移植到 AWS Lambda 平台,基于前人的方法和经验,本人创建了 fc-libreoffice 项目,使 libreoffice 成功的运行在阿里云函数计算平台.fc -libreoffice 在 aws-lambda-libreoffice 的基础上解决了如下问题:
重新编译和裁剪 libreoffice,使其适配 FC nodejs8 runtime 内置的 gcc 和内核版本;
安装运行时缺失的 libssl3 依赖;
借助 OSS 运行时下载解压,以绕过 zip 程序包 50M 的限制;
制作了一个例子项目,支持一键部署,快速体验。
本文侧重于记述整个移植过程,记录关键步骤以备忘,也为类似的转换工具移植到函数计算平台提供参考。如果您对于如何快速搭建一个廉价且可扩展的词转换 pdf 云服务更感兴趣,可以阅读另一篇文章“五分钟上线 – 函数计算 Word 转 PDF 云服务”。
准备工作
在开始之前建议找一个台配置较好的 Debain / Ubuntu 机器,libreoffice 编译比较消耗计算资源。并在机器上安装和配置如下工具:
docker-ce 安装方法参考官方安装文档
好玩的一款函数计算的编排工具,用于快速部署函数计算应用。
MacOS 平台可以使用如下方法安装
brew tap vangie/formula
brew install fun
其他平台可以通过 npm 安装
npm install @alicloud/fun -g
ossutil oss 的命令行工具。将其下载并放置到$ PATH 所在目录。
编译 libreoffice
我们会采用 fc-aliyunfc/runtime-nodejs8:build docker 提供的 docker 镜像进行编译.fc-docker 提供了一系列的 docker 镜像,这些 docker 镜像环境非常接近函数计算的真实环境。因为我们打算把 libreoffice 跑在 nodejs8 环境中,所以我们选用了 aliyunfc/runtime-nodejs8:build,建造标签镜像相比于其他镜像会多一些构建需要的基础包。
启动一个编译环境
通过如下命令可启动一个用于构建 libreoffice 的容器。
docker run –name libre-builder –rm -v $(pwd):/code -d -t –cap-add=SYS_PTRACE –security-opt seccomp=unconfined aliyunfc/runtime-nodejs8:build bash
上面的命令,我们启动了一个名为 libre-builder 的容器并把当前目录挂载到容器内文件系统的 / 代码目录。附加参数 –cap-add=SYS_PTRACE –security-opt seccomp=unconfined 是 cpp 程序编译需要的,否则会报出一些警告。- d 表示以后台 daemon 的方式启动。- t 表示启动 tty,配合后面的 bash 命令是为了卡主容器不退出。而 –rm 表示一旦容器停止了就自动删除容器。
安装编译工具
接下来进入容器安装编译工具
apt-get install -y ccache
apt-get build-dep -y libreoffice
ccache 是一个编译工具,可以加速 gcc 对同一个程序的多次编译。尽管第一次编译会花费长一点的时间,有了 ccache,后续的编译将变得非常非常快。
apt-get 的 build-dep 子命令会建立某个要编译软件的环境。具体行为就是把所有依赖的工具和软件包都安装上。
克隆源码
git clone –depth=1 git://anongit.freedesktop.org/libreoffice/core libreoffice
cd libreoffice
记得加上 –depth= 1 参数,因为 libreoffice 项目比较大,进行全量克隆会比较费时间,对于编译来说 git 提交历史没有意义。
配置并编译
# 如果多次编译,该设置可以加速后续编译
ccache –max-size 16 G && ccache -s
通过 –disable 参数去掉不需要的模块,以减少最终编译产物的体积。
# the most important part. Run ./autogen.sh –help to see wha each option means
./autogen.sh –disable-report-builder –disable-lpsolve –disable-coinmp \
–enable-mergelibs –disable-odk –disable-gtk –disable-cairo-canvas \
–disable-dbus –disable-sdremote –disable-sdremote-bluetooth –disable-gio –disable-randr \
–disable-gstreamer-1-0 –disable-cve-tests –disable-cups –disable-extension-update \
–disable-postgresql-sdbc –disable-lotuswordpro –disable-firebird-sdbc –disable-scripting-beanshell \
–disable-scripting-javascript –disable-largefile –without-helppack-integration \
–without-system-dicts –without-java –disable-gtk3 –disable-dconf –disable-gstreamer-0-10 \
–disable-firebird-sdbc –without-fonts –without-junit –with-theme=”no” –disable-evolution2 \
–disable-avahi –without-myspell-dicts –with-galleries=”no” \
–disable-kde4 –with-system-expat –with-system-libxml –with-system-nss \
–disable-introspection –without-krb5 –disable-python –disable-pch \
–with-system-openssl –with-system-curl –disable-ooenv –disable-dependency-tracking
开始编译
make
的名单最终 compile- 查询查询结果位于./instdir/ 目录下。
精简尺寸
使用 strip 命令去除二进制文件中的符号信息和编译信息
# this will remove ~100 MB of symbols from shared objects
strip ./instdir/**/*
删除不必要的文件
# remove unneeded stuff for headless mode
rm -rf ./instdir/share/gallery \
./instdir/share/config/images_*.zip \
./instdir/readmes \
./instdir/CREDITS.fodt \
./instdir/LICENSE* \
./instdir/NOTICE
验证
使用如下命令,测试一下编译出来的 soffice 是否能正常将 txt 文件转换成 pdf 文件。
echo “hello world” > a.txt
./instdir/program/soffice –headless –invisible –nodefault –nofirststartwizard \
–nolockcheck –nologo –norestore –convert-to pdf –outdir $(pwd) a.txt
打包
# archive
tar -zcvf lo.tar.gz instdir
然后使用如下命令将 lo.tar.gz 文件从容器文件系统拷贝到宿主机文件系统。
docker cp libre-builder:/code/libreoffice/lo.tar.gz ./lo.tar.gz
Gzip vs Zopfli vs Brotli Gzip,Zopfli 和 Brotli 是三种开源的压缩算法,对于一个 130M 的铬文件,分别采用这三种压缩算法最大级别的压缩效果是
文件
算法
MIB
压缩比
解压耗时
铬
–
130.62
–
–
chromium.gz
Gzip 已
44.13
66.22%
0.968s
chromium.gz
Zopfli
43.00
67.08%
0.935s
chromium.br
Brotli
33.21
74.58%
0.712s
从上面的结果看 Brotli 算法的效果最优。
由于 aliyunfc/runtime-nodejs8:build 是基于 debain jessie 发行版的。在 debain jessie 上安装 brotli 较为麻烦,所以我们借助 ubuntu 容器安装 brotli 工具,将 tar.gz 格式转为 tar.br 格式。
docker run –name brotli-util –rm -v $(pwd):/root -w /root -d -t ubuntu:18.04 bash
docker exec -t brotli-util apt-get update
docker exec -t brotli-util apt-get install -y brotli
docker exec -t brotli-util gzip -d lo.tar.gz
docker exec -t brotli-util brotli -q 11 -j -f lo.tar
然后当前目录会多一个 lo.tar.br 文件。
安装依赖
在函数计算 nodejs8 环境中运行 soffice,需要安装通过 npm 安装 tar.br 的解压依赖包 @shelf/aws-lambda-brotli-unpacker 和通过 apt-get 安装 libnss3 依赖。先启动一个 nodejs8 的容器,以保证依赖的安装环境和运行时环境是一致的。
docker run –rm –name libreoffice-builder -t -d -v $(pwd):/code –entrypoint /bin/sh aliyunfc/runtime-nodejs8
注意:存在 @shelf/aws-lambda-brotli-unpacker 本机绑定,所以在开发机 MacOS 上 npm install 打包上传是无法工作。
docker exec -t libreoffice-builder npm install
由于函数计算运行时无法安装全局的 deb 包,所以需要将 deb 和依赖的 deb 包下载下来,再安装到当前工作目录而不是系统目录。当前工作目录下可以随代码一起打包上传。
docker exec -t libreoffice-builder apt-get install -y -d -o=dir::cache=/code libnss3
docker exec -t libreoffice-builder bash -c ‘for f in $(ls /code/archives/*.deb); do dpkg -x $f $(pwd) ; done;’
libnss3 包含了许多.so 动态链接库文件,linux 系统下 LD_LIBRARY_PATH 环境变量里的动态链接库才能被找到,而函数计算将代码目录 / 代码下的 lib 目录默认添加到了 LD_LIBRARY_PATH 中。所以我们写个脚本,把所有安装的.so 文件软连接到 / code / lib 目录下
docker exec -t libreoffice-builder bash -c “rm -rf /code/archives/; mkdir -p /code/lib;cd /code/lib; find ../usr/lib -type f \(-name ‘*.so’ -o -name ‘*.chk’ \) -exec ln -sf {} . \;”
下载并解压 tar.br
为了使用这个 lo.tar.br 文件,需要先上传到 OSS
ossutil cp $SCRIPT_DIR/../node_modules/fc-libreoffice/bin/lo.tar.br oss://${OSS_BUCKET}/lo.tar.br \
-i ${ALIBABA_CLOUD_ACCESS_KEY_ID} -k ${ALIBABA_CLOUD_ACCESS_KEY_SECRET} -e oss-${ALIBABA_CLOUD_DEFAULT_REGION}.aliyuncs.com -f
在函数的初始化方法中下载。
module.exports.initializer = (context, callback) => {
store = new OSS({
region: `oss-${process.env.ALIBABA_CLOUD_DEFAULT_REGION}`,
bucket: process.env.OSS_BUCKET,
accessKeyId: context.credentials.accessKeyId,
accessKeySecret: context.credentials.accessKeySecret,
stsToken: context.credentials.securityToken,
internal: process.env.OSS_INTERNAL === ‘true’
});
if (fs.existsSync(binPath) === true) {
callback(null, “already downloaded.”);
return;
}
co(store.get(‘lo.tar.br’, binPath)).then(function (val) {
callback(null, val)
}).catch(function (err) {
callback(err)
});
};
然后借助于 @shelf/aws-lambda-brotli-unpackernpm 包解压 lo.tar.br
const {unpack} = require(‘@shelf/aws-lambda-brotli-unpacker’);
const {execSync} = require(‘child_process’);
const inputPath = path.join(__dirname, ‘..’, ‘bin’, ‘lo.tar.br’);
const outputPath = ‘/tmp/instdir/program/soffice’;
module.exports.handler = async event => {
await unpack({inputPath, outputPath});
execSync(`${outputPath} –convert-to pdf –outdir /tmp /tmp/example.docx`);
};
有趣部署函数
编写一个 template.yml 文件,将函数计算的配置都写在该文件中,然后使用 fun deploy 命令部署函数。
ROSTemplateFormatVersion: ‘2015-09-01’
Transform: ‘Aliyun::Serverless-2018-04-03’
Resources:
libre-svc: # service name
Type: ‘Aliyun::Serverless::Service’
Properties:
Description: ‘fc test’
Policies:
– AliyunOSSFullAccess
libre-fun: # function name
Type: ‘Aliyun::Serverless::Function’
Properties:
Handler: index.handler
Initializer: index.initializer
Runtime: nodejs8
CodeUri: ‘./’
Timeout: 60
MemorySize: 640
EnvironmentVariables:
ALIBABA_CLOUD_DEFAULT_REGION: ${ALIBABA_CLOUD_DEFAULT_REGION}
OSS_BUCKET: ${OSS_BUCKET}
OSS_INTERNAL: ‘true’
真实场景下,把秘钥和一起变量写在 template.yml 里并不合适。为了做到代码和配置相分离,上面使用了变量占位符 ${ALIBABA_CLOUD_DEFAULT_REGION} 和 ${OSS_BUCKET}。
然后使用 envsubst 进行替换
SCRIPT_DIR=`dirname — “$0″`
source $SCRIPT_DIR/../.env
export ALIBABA_CLOUD_DEFAULT_REGION OSS_BUCKET
envsubst < $SCRIPT_DIR/../template.yml.tpl > $SCRIPT_DIR/../template.yml
cd $SCRIPT_DIR/../
上面所有的配置都写在了.env 文件中,dotenv 是社区常见的方案,也有广泛的工具支持。
小结
本文重点介绍了编译 libreoffice 的过程,这也是移植中较为困难的部分。由于 libreoffice 又涉及到 npm 的本地绑定和 apt-get 安装到本地目录的问题,所以在函数计算依赖方面本例也是非常经典的场景。无论是编译还是依赖安装,本文中的步骤都强烈地依赖 fc-docker 镜像,正因为有了该镜像,解决了环境差异问题,大大降低了移植的难度。大文件运行时加载也是函数计算的常见问题,对于转换工具场景中常见的大文件是二进制程序,对于机器学习场景中大文件常是训练模型的数据问题,但是无论是哪一种,采用 OSS 下载解压的方法都是通用的,随着函数计算支持了 NAS,使用 NAS 挂载共享网盘的方式也是一种新的路径。
上文完整的源码可以在 FC-LibreOffice 的项目中找到。
参考阅读
https://zh.wikipedia.org/wiki/LibreOffice
如何在 AWS Lambda 中运行 LibreOffice 以获得大规模的廉价 PDF
https://github.com/alixaxel/chrome-aws-lambda
https://github.com/shelfio/aws-lambda-brotli-unpacker
本文作者:倚贤阅读原文
本文为云栖社区原创内容,未经允许不得转载。