已有 Dockerfiles 经验时,Buildpacks 相比 Dockerfiles 有哪些优缺点?
对于已经能稳定维护 Dockerfiles 的团队,采用 Buildpacks 的主要收益是减少多个项目中重复的构建维护工作、统一镜像策略,以及集中更新运行环境。能否写出 Dockerfile,通常已不是决定因素。
如果服务不多,现有镜像在构建速度、体积和安全方面都能满足要求,继续使用 Dockerfiles 完全合理。Buildpacks 不保证生成的镜像比团队手写的更小、更快或更安全,需要用实际应用验证。
这里讨论的是 Cloud Native Buildpacks(CNB),以 Paketo 为例。具体能力取决于所选的 builder、buildpacks 和运行时基础镜像。某个实现的默认行为,不能视为所有 Buildpacks 的保证。
分层、镜像体积与安全默认值,需要修正哪些预期?
| 预期 | 更准确的理解 |
|---|---|
| 自动实现接近最优的分层 | CNB 提供层管理和复用机制,分层效果由具体 buildpack 决定,没有“最优”保证 |
| 自动采用 multi-stage builds | CNB 区分构建环境与运行环境,并选择哪些层进入最终镜像;这与 Dockerfile 的 multi-stage builds 目标相近,但机制不同 |
| 代码变化只需重建少数层 | 未变化的依赖层可能复用,但编译器仍可能重新编译整个应用;镜像层复用与编译增量是两回事 |
| 镜像一定更小 | 取决于基础镜像、运行时和附带组件,精心编写的 Dockerfile 可能更小 |
| 自动满足安全最佳实践 | 可以获得较好的默认配置,但仍需检查最终产物和部署配置 |
CNB 的层可以分别标记为构建所需、运行所需以及可缓存。仅用于构建的层可以不进入最终镜像。某个依赖层能否复用,需要 buildpack 根据依赖、环境和版本等信息判断。CNB 层复用文档
支持缓存不代表 CI 一定更快。临时 CI runner 是否保留缓存、下载依赖需要多久、构建脚本是否每次都会执行,都会影响构建耗时。
是否默认使用 non-root 用户,也要看具体实现。Paketo 的基础镜像使用专用非 root 用户构建和运行应用,这是有用的默认配置,Dockerfile 也可以通过 USER 实现。部署时是否覆盖用户、挂载目录的权限是否合适,以及应用是否需要写文件,仍要单独验证。Paketo 基础镜像说明
对熟悉 Dockerfiles 的团队,主要收益在哪里?
Buildpacks 的收益往往体现在持续维护中。例如,多个服务需要升级语言运行时、调整证书处理方式,或者修复相同的打包问题。使用 Dockerfiles 时,团队需要通过共享基础镜像、模板或批量修改来推广变更;使用 Buildpacks 时,可以把这些规则集中在 builder 和 buildpacks 中,让应用通过统一的构建流程获得更新。
builder 包含构建所用的基础环境、buildpacks、负责协调构建阶段的 lifecycle,以及运行时基础镜像的引用。CNB 构建机制
Buildpacks 更适合有以下需求或条件的团队:
- 有较多使用相似技术栈的服务,需要统一维护。
- 应用团队希望只声明运行时版本和少量构建配置。
- 有明确的维护者负责 builder 的测试、升级与回滚。
- 经常遇到不同项目打包方式不一致的问题。
如果共享基础镜像和 Dockerfile 模板已经解决了这些问题,Buildpacks 的额外收益就会缩小。比较时,应评估现有的完整构建体系,文件行数本身不足以判断维护成本。
reproducible builds 有什么实际价值?
可复现构建指相同输入产生相同输出。它能帮助团队判断镜像变化的原因:源代码变了、依赖变了,还是构建环境发生了漂移。它也能支持独立重建和产物核验。
使用 Buildpacks,并不保证相同代码一定生成相同的镜像 digest。还需要控制以下输入:
- builder、buildpacks 和运行时基础镜像的具体内容。
- 语言工具链、应用依赖及下载来源。
- 构建参数、目标架构,以及构建过程中的时间戳等非确定性输入。
CNB 会处理部分镜像时间戳问题,但无法修复 buildpack 或应用构建过程生成的非确定性文件。例如,编译产物如果写入当前时间,仍可能破坏可复现性。CNB 可复现构建说明
如果团队目前只要求构建稳定成功,未必值得单独为了严格的可复现性迁移。可复现构建也不等于可信构建,来源记录、签名和构建环境的可信程度仍需另外考虑。
已经在 registry 扫描漏洞,SBOM 还有多少价值?
SBOM 记录镜像或应用包含哪些组件,漏洞扫描则把识别出的组件与漏洞数据库匹配。两者有重叠,用途也有区别。
保存与发布镜像关联的 SBOM,可以用于:
- 在新漏洞公布后,查询哪些历史版本包含受影响组件。
- 盘点依赖版本、来源和许可证信息。
- 向其他工具或交付对象提供标准格式的组件清单。
Paketo 支持的 SBOM 格式包括 Syft、SPDX 和 CycloneDX,具体取决于 buildpack。Paketo SBOM 说明
如果 registry 已经保存完整组件清单、支持持续重新扫描,也满足许可证与交付要求,Buildpacks 提供 SBOM 的额外价值可能有限。还需要核实清单的覆盖范围,不能默认应用依赖、构建依赖和基础镜像 OS 包都包含在同一份输出中。Paketo 文档也区分了构建时依赖与运行时依赖的获取方式。Paketo SBOM 获取方法
Dockerfile 配合 BuildKit 同样可以生成 SBOM attestations,无需仅为这一项能力迁移到 Buildpacks。Docker SBOM 文档
每次发布都重建并测试,rebase 还有用吗?
如果每次发布都要重新构建并测试,rebase 的收益可能较小。它用兼容的新 run image 替换已有应用镜像底部的运行时基础镜像层,保留应用层,省去应用构建过程。CNB rebase 说明
rebase 更适合应用代码没有变化、需要为大量已发布应用更新基础镜像 OS 组件,而且重新编译成本较高的场景。使用时需要注意:
- rebase 只更新被替换的基础镜像层。
- buildpack 添加的语言运行时、应用依赖等,如果位于其他层,就无法通过 rebase 更新。
- 新 run image 必须满足兼容性要求,不能随意换成另一种发行版。
- rebase 后得到的是新镜像,仍需按团队要求扫描、测试和发布。
团队可以在 rebase 后运行集成测试,省去编译步骤。如果流程要求每次发布必须重新编译应用,而且编译成本很低,就无需为了使用 rebase 改变流程。
社区维护能省下什么,又留下哪些责任?
社区维护可以减少团队自行跟踪和打包基础组件更新的工作,但已经部署的应用仍需团队更新。
修复基础镜像漏洞,通常要经过上游提供修复、镜像维护者发布更新、团队获取并验证更新,再重新构建或 rebase,最后测试和部署。应用依赖中的漏洞,则需要更新相应依赖。
采用 Paketo 等实现之前,应确认维护范围、支持周期和更新渠道。社区活跃并不意味着所有组件都有固定的修复时限,团队仍需有人接收更新并完成发布。
采用 Buildpacks 有哪些成本?
对“黑盒”的顾虑有道理。Dockerfile 通常能直接看出操作顺序,使用 Buildpacks 排障则需要理解检测规则、buildpack 执行顺序、层缓存和启动过程。
此外,还要考虑这些成本:
- 定制需要适配已有机制。安装特殊系统包、加入原生库或支持非标准构建工具时,可能需要额外的 buildpack、自定义 builder 或基础镜像。
- 共享 builder 能统一修复问题,也可能让同一变更影响多个应用,因此需要分批验证,并安排回滚。
- CI 集成仍需维护,私有依赖、代理、证书、凭据和缓存都要配置。
- 特定语言版本、目标架构和运行环境是否受支持,需要根据所选实现逐项确认。
- 构建配置有迁移成本。虽然最终产物是 OCI 镜像,构建参数和约定仍可能依赖特定 buildpacks。
临时调整启动命令、环境变量等,往往已有配置入口;需要任意修改 OS 环境时,则更容易遇到限制。如果经常要维护自定义 buildpack 和基础镜像,这些工作可能会抵消采用 Buildpacks 节省的维护成本。
如何用一个试点判断是否值得采用?
选择一个有代表性的应用,保留现有 Dockerfile,比较同一提交、同一目标架构下的两套构建。
假设已安装 pack 和 Docker,并将 BUILDER_IMAGE 设置为支持该应用技术栈的 builder 镜像引用:
docker build -t faq-app:dockerfile .
pack build faq-app:buildpacks \
--path . \
--builder "$BUILDER_IMAGE"
docker image inspect \
faq-app:dockerfile faq-app:buildpacks \
--format '{{.RepoTags}} size={{.Size}} user={{json .Config.User}}'
pack build 的 --path 指定源码目录,--builder 指定 builder。pack build 命令文档
如果 builder 支持 SBOM,还可以导出清单,检查实际覆盖范围:
pack sbom download faq-app:buildpacks \
--output-dir ./sbom
这里的镜像大小是 Docker 报告的本地大小,不等于 registry 中的压缩传输大小。Config.User 也只能说明默认用户配置,不能代替运行验证。
试点至少要完成以下检查:
- 分别测量冷构建、热缓存构建、仅修改源码和修改依赖后的构建。
- 对两种镜像执行相同的漏洞扫描和集成测试。
- 检查启动、退出信号处理、文件权限,以及团队已有的运行时限制。
- 完成一次基础镜像或语言运行时升级,记录所需修改。
- 尝试一个真实的定制需求,确认排障和扩展成本。
对于已经能稳定维护 Dockerfiles 的团队,迁移应带来可观察的收益,例如维护多个服务更省事、升级更一致,或者明显减少重复配置。如果试点只省下了一个已经很稳定的 Dockerfile,却增加了定制和排障工作,保留 Dockerfiles 就是合适的选择。也可以让符合约定的服务采用 Buildpacks,有特殊需求的服务继续使用 Dockerfiles。
备注:内容仅供参考。
