使用 Serverless Framework 打包 AWS Lambda 可执行文件
结论
即使 packages/wkhtmltopdf 已经打进部署包,修改 PATH 也不会让它自动成为可执行命令。当前代码加入 PATH 的是 Lambda 代码根目录 LAMBDA_TASK_ROOT,而二进制文件实际位于 LAMBDA_TASK_ROOT/packages/wkhtmltopdf。
建议这样处理:
- 按照相对于
serverless.yml的路径打包二进制文件。 - 在代码中使用绝对路径调用,不依赖
PATH。 - 确认文件具有 Linux 可执行权限。
- 确认二进制文件兼容 AWS Lambda 的 Linux 系统和 CPU 架构。
正确配置打包路径
假设项目结构如下:
serverless.yml
payment/
module/
chargePackage.js
packages/
wkhtmltopdf
node_modules/
使用旧版 Serverless Framework 时,可以这样配置:
service: consult-payment-api
frameworkVersion: ">=1.1.0 <2.0.0"
package:
individually: true
provider:
name: aws
region: us-west-2
runtime: nodejs8.10
stage: dev
timeout: 300
functions:
UserPackageCharge:
handler: payment/module/chargePackage.create
package:
include:
- packages/wkhtmltopdf
events:
- http:
path: payment/module/package
method: post
cors:
origin: "*"
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
- X-Amz-User-Agent
- My-Custom-Header
YAML 缩进必须准确。include 中的路径以 serverless.yml 所在目录为基准,不是以 handler 文件所在目录为基准。
新版 Serverless Framework 使用 patterns 代替旧式 include:
package:
individually: true
functions:
UserPackageCharge:
handler: payment/module/chargePackage.create
package:
patterns:
- packages/wkhtmltopdf
题目中的版本范围对应旧版 Framework,因此应使用 include。升级 Framework 后再改用 patterns。
使用绝对路径调用二进制文件
与其只修改 PATH,不如直接将 Node.js 包使用的命令指向部署包内的文件:
const path = require('path');
const wkhtmltopdf = require('wkhtmltopdf');
const MemoryStream = require('memorystream');
wkhtmltopdf.command = path.join(
process.env.LAMBDA_TASK_ROOT,
'packages',
'wkhtmltopdf'
);
exports.create = function (event, context) {
const memStream = new MemoryStream();
const htmlUtf8 = Buffer.from(event.html_base64, 'base64').toString('utf8');
wkhtmltopdf(htmlUtf8, event.options, function (code, signal) {
if (code !== 0) {
return context.done(
new Error(`wkhtmltopdf exited with code ${code}, signal ${signal}`)
);
}
context.done(null, {
pdf_base64: memStream.read().toString('base64')
});
}).pipe(memStream);
};
配置中的 handler 是:
handler: payment/module/chargePackage.create
对应文件必须是:
payment/module/chargePackage.js
导出名称必须是:
exports.create = ...
如果实际文件是 index.js,导出项是 exports.handler,配置也要与真实路径和名称一致,例如:
handler: index.handler
handler 配置与代码导出名称不一致时,还会引发单独的加载错误。
也可以正确扩展 PATH
如果仍希望 wkhtmltopdf 模块通过命令名查找二进制文件,需要将二进制文件所在目录加入 PATH:
const path = require('path');
process.env.PATH = [
process.env.PATH,
path.join(process.env.LAMBDA_TASK_ROOT, 'packages')
].join(':');
这样才能通过命令名 wkhtmltopdf 找到 packages/wkhtmltopdf。不过,显式设置 wkhtmltopdf.command 更直观,也不容易受运行环境变量影响。
还可以将二进制文件直接放在部署包根目录:
serverless.yml
wkhtmltopdf
payment/
node_modules/
对应配置为:
package:
include:
- wkhtmltopdf
此时把 LAMBDA_TASK_ROOT 加入 PATH,才会与文件的实际位置相符。
确保具有执行权限
打包前,需要为文件设置可执行权限:
chmod 755 packages/wkhtmltopdf
然后只生成部署包,暂不部署:
serverless package
检查 ZIP 中是否包含该文件,以及路径是否正确:
unzip -l .serverless/*.zip | grep wkhtmltopdf
还可以解压后检查权限:
zipinfo -l .serverless/*.zip | grep wkhtmltopdf
部署包中应该出现:
packages/wkhtmltopdf
如果 ZIP 中没有该文件,可依次检查:
include路径是否相对于serverless.yml。- YAML 缩进是否正确。
- 全局或函数级
exclude是否与该路径冲突。 .serverlessignore是否排除了packages目录。- 是否有打包插件重新构建了部署产物。
二进制兼容性同样重要
wkhtmltopdf 必须是为 Lambda 运行环境编译的 Linux 二进制文件,不能直接使用 macOS 或 Windows 版本。它还要与函数的 CPU 架构匹配,例如 x86_64 或 arm64。
即使主程序能够执行,其依赖的动态链接库也必须存在于 Lambda 环境中。可以在兼容的 Linux 构建环境中检查:
file packages/wkhtmltopdf
ldd packages/wkhtmltopdf
如果之后出现以下错误:
Permission denied
通常说明执行权限有问题。
如果出现:
No such file or directory
而文件确定存在,则可能是二进制架构、动态加载器或共享库不兼容。
如果错误提示缺少 libXrender、字体、OpenSSL 等库,需要将相关依赖一同打包,使用专门为 Lambda 构建的静态版本,或者把二进制文件及其依赖放入 Lambda Layer。
nodejs8.10 已经是停止支持的 Lambda 运行时。实际部署时,应迁移到当前受支持的 Node.js 运行时,并在与目标运行时和 CPU 架构一致的环境中重新验证 wkhtmltopdf。核心处理方式不变:确认 ZIP 中包含该文件,再通过 LAMBDA_TASK_ROOT 拼出准确的绝对路径并执行。
备注:内容仅供参考。