以前用自定义运行时打包 Dart Server 到云函数,本质上也是跑 Docker,只是有着诸多限制和不足。 现在终于要手写 Dockfile,体验了一把真正云原生的感觉,还是很过瘾的。
简介&缘起
前端的朋友可能比较熟悉,Prisma 是一个大而全的 ORM 工具,它使用自己的 DSL 来定义数据结构,向前生成语义化、类型安全的查询方法供代码调用,向后在自己的引擎中生成 SQL 语句,发送到数据库进行交互。当你修改数据结构后,它还能帮你同步到数据库。前后一把抓,只需要维护一个数据类,岂不美哉?
虽然是个 Node 项目,但它的查询引擎是 Rust 写的,可以供不同语言调用,其中就包含了社区开源大佬支持的 Dart Client。
两年前没啥生态只能手撸 SQL 生成器,如今一查居然有了这么好的东西,那必须积极拥抱一下。
遇到的问题
按照官网教程在服务端项目中添加,初始化后会下载一个查询引擎二进制文件,本地运行很顺利。 在 Dart 打包镜像文件的基础上,把这个文件复制了过去。
但是跑起来访问查询接口时,等待约半分钟后 Log 打印了如下错误:
Prisma binary query engine not ready
查询引擎没有就绪,显然是调用出了问题。
解决思路和过程
解决问题的第一步是先定位问题。
为了排除干扰,新建了一个测试项目,不引入服务端框架,跑起来后只调用一个 Prisma 的连接和查询方法。 问题依旧,这就确认了与其他部分无关。开始逐步修改 Dockerfile 进行排查。
整体的大概思路是:
先确认当前是否能在 Docker 中跑起来(可能版本更新出问题了,本身在 Linux 下就跑不起来,没跑起来前不排除这个假设hhh),如果可以再逐步替换变量,找到问题所在,最后再层层筛选减小镜像大小。
看源码
开始排查前还是先看看源码,也许能直接找到问题。
代码中调用 prisma 是通过自动生成的 prisma/generated_dart_client/client.dart
进行的。
这个客户端文件调用了 package:orm/engines/binary.dart
与查询引擎进行交互,报错的地方在 103 行:
|
|
很显然是连接的时候出错了,重试了5次每次都有延迟所以会卡顿一会,立刻想到文件的位置放的不对,
改了一下 COPY 命令换到另一个目录,结果这次提示的很明显:No query engine binary found in '/' '/prisma' '/.dart_tool'
,说明并不是没有找到文件。
排除了引擎文件位置不对的可能。
查文档
意识到不同系统下打包的二进制可能会不通用,特地搜索了引擎相关章节,果然发现一个参数 binaryTargets
,可以指定平台。
填写打包时对应的基础镜像,失败。把 Prisma 的 init 和 generate 方法放在打包过程中,去生成默认的引擎,失败。
这里犯了个错误,如果再多看一会部署文档,就能发现其对环境的依赖列表,没有看到导致浪费了一些时间做实验。
此外也搜到一个 Dockerfile 示例,事后证明因为基础镜像的版本升级,某条语句没有生效,与正确答案擦肩而过。
从头排查,层层递进
编译成品打包失败了怎么办?笨办法验证,没有编译,直接把源代码和环境初始化,dart run xxx
命令搬过去,弄了个 1.9G 的超大镜像,这下终于可以了,哈哈哈。
至少说明了在 Linux 下是可用的。
接下来就是把 Dart 的部分给编译了,在新镜像里逐步删去 Prisma 相关的依赖。尝试过程中发现对引擎在不同系统对 openssl 的依赖也有差异,于是指定了对应的镜像版本号,最终移除了 node_moudels 目录,打包镜像大小降至 300 M 。
进一步减小体积
到这里基本确定了需要 node 环境和对应版本的 openssl,之前一直在默认镜像上打包,查资料时发现有个 Alpine 发行版体积非常小,官方镜像只有 5 M,立刻去替换了对应的 node 镜像和打包基础镜像。
一番折腾后,最终的镜像降低到了 100 M。
意外之喜
本来到这里就要结束,结果提前上传的 demo 被该库的作者 Seven Du 发现了,大佬为人和善,作为创建者主力维护这个开源库两年多,一眼发现我的打包尺寸过大,检查后给出了一份新的 Dockerfile 参考。
对比后发现,其实 Prisma 在此处的依赖并非是 node 环境,而是四个 so 文件。前面折腾的各种依赖只是恰好最终包含了这些运行时。
最终版的 Dockerfile (点击展开)
|
|
这下打包镜像只剩 34 M 了。分层如下:
Layer | Size |
---|---|
dart + prisma runtime | 9.2 M |
dart 编译成品 | 6.7 M |
prisma-query-engine | 18.2 M |
太牛了,只能说一句牛逼。这服务端尺寸不把 node 按在地上爆杀 😆
总结
借这个机会熟悉了一下 Docker,打包镜像从最初的 1.9 G 到最后的 34 M,可见选择合适的基础镜像,和只包含必须的依赖是多么重要。
其实本质上还是一个问题:如何确认一个可执行文件的依赖?请教了 Seven Du 大佬,回答也很简单:
- prisma-engines 的 cargo.toml 里面找 feat,几乎这些地方就是声明 so 库依赖。
- 在容器里面运行 prisma-query-engine 看报啥 so 库依赖的错误,逐个添加。
前者如果说不懂 rust 不知道,后者其实是可以想到的,只要简单的逆向思维一下就好。看来自己写代码还需要更多冷静的思考,而非忙碌于尝试。
参考
本文的 demo 仓库: Dart ORM (Prisma Client Dart) Docker Example
prisma-dart 官网: Prisma Client Dart (orm package) Doc
BTW,偶然听说了 Prisma 官方对 prisma-dart
项目的区别对待,有点难受。对外低调内敛并不意味着甘愿低人一头,官方应该对他们涉嫌歧视的行为做出解释,对所有的 Client 项目一视同仁,而不是只会向个别 maintainer 提要求。如果连社区的开拓贡献者都不被尊重,这样的社区又如何让人尊重呢?