Docker、CI/CD 与可重复交付
可重复交付要求同一份经过验证的不可变产物,从测试环境逐级晋升到生产。每个环境重新编译、使用浮动基础镜像或手工修改容器,会让测试结果无法证明生产版本。
1. 镜像由只读层和运行配置组成
Dockerfile 每个构建步骤产生可缓存层,镜像最终由内容摘要标识。tag 便于人类阅读,但可以被重新指向;部署记录应保存不可变 digest。
构建上下文只包含必要文件,使用 .dockerignore 排除 Git 历史、密钥、测试输出和本地缓存。把秘密 COPY 进层后,即使后续删除,也可能留在旧层中。
2. 多阶段构建分离工具和运行时
第一阶段使用 JDK、Maven 和编译工具生成 jar,最终阶段只复制运行所需产物到较小运行时镜像。这样减少镜像体积和攻击面,也避免生产容器携带编译器与仓库凭据。
基础镜像按 digest 或受控版本锁定,并由自动化流程定期更新安全修复。永久锁死一个旧 digest 会获得可重复性,却错过漏洞修复;更新应通过新的构建和测试进入。
容器以非 root 用户运行,使用只读文件系统和最小 Linux capabilities,临时写入放在明确挂载目录。
3. CI 从干净提交生成唯一产物
一次流水线可以依次执行:
- 检出确定 commit,验证依赖与许可证。
- 编译、单元测试和静态检查。
- 在隔离环境运行集成与契约测试。
- 构建镜像,生成 SBOM、漏洞报告和 provenance。
- 对镜像 digest 签名并上传受控仓库。
- 记录 commit、构建 ID、配置 schema 和 digest。
失败阶段不发布可晋升产物。生产凭据只给部署阶段,普通 PR 构建不能取得。
4. CD 晋升同一个 digest
测试、预发和生产使用相同镜像 digest,环境差异通过外部配置、Secret 和资源声明注入。不要在进入生产前重新运行 mvn package,否则得到的是另一份未验证二进制。
晋升包含审批、变更窗口或自动策略,但输入仍是已有产物。部署系统记录谁在何时把哪个 digest 和配置版本发布到哪个环境。
5. Readiness 与排空保证无损替换
新容器先启动并通过 startup/readiness,再接收流量;旧容器先从端点摘除,停止接收新请求,等待在途请求和消息处理结束后退出。
进程要响应 SIGTERM、停止领取新任务,并在 grace period 内完成或保存检查点。只设置 rolling update,不实现应用关闭协议,仍会中断请求。
6. 数据库变更需要独立兼容步骤
代码镜像回退不能撤销数据库 migration。使用 expand-contract:先增加新结构,发布兼容读写代码,回填并校验,最后在没有旧版本后删除旧结构。
迁移脚本有版本、锁、超时和进度,避免每个应用实例启动时同时执行重型 DDL。不可逆变更准备正向修复,而不是承诺简单 rollback。
7. 发布策略限制影响范围
滚动、蓝绿和 canary 都应绑定 readiness、业务指标和停止条件。自动回滚触发时,系统切回已知 digest 与配置版本,并保留失败实例、日志和 trace 供取证。
回滚只能阻止后续流量,已产生消息、数据和外部副作用仍需补偿或兼容。发布成功标准还包括旧实例排空、临时路由清理和数据核对。
8. 构建与部署同样需要故障演练
验证基础仓库不可用、镜像扫描失败、证书过期、节点拉取失败、readiness 永不成功和回滚时数据库已升级等情况。流水线应能安全停止,并显示当前实际版本。
可重复交付的证据是从 commit 到 digest、签名、配置和运行实例的完整关系,而不是“同一条命令通常能成功”。
9. 常见问题
9.1 为什么不直接使用 latest tag
latest 可以指向不同内容,无法准确回滚和审计。可以保留易读 tag,但部署清单最终解析并记录镜像 digest。
9.2 镜像越小是否一定越安全
较少组件通常减少攻击面,但仍要看包含的软件、更新来源和运行权限。极小镜像也可能缺少调试或证书能力,应以经过扫描、可维护的最小运行集为目标。
10. 面试题
10.1 如何设计从提交到生产的可重复交付流水线
出现公司:Shopee
考察重点
- 不可变镜像 digest、多阶段构建和构建输入。
- 同产物晋升、签名、SBOM 与审计。
- readiness、排空、数据兼容和回滚边界。
相关内容:第 1 节“镜像由只读层和运行配置组成”至第 8 节“构建与部署同样需要故障演练”。
参考回答
CI 从确定 commit 和固定工具链开始,在干净环境完成测试后用多阶段 Dockerfile 构建非 root 运行镜像,生成 SBOM、provenance 和签名,并以 digest 上传。测试、预发和生产晋升同一个 digest,配置与 Secret 在运行时注入,部署记录 commit、digest 和配置版本。
发布时新实例先通过 startup/readiness,旧实例先摘流再处理 SIGTERM 和在途请求。数据库采用 expand-contract 保持新旧版本兼容。灰度指标达到停止条件时切回已知 digest,但数据副作用要另行补偿。最后通过镜像拉取、探针失败和数据库已变更等演练验证流水线能安全停止与恢复。