持续集成与持续交付(CI/CD)是推动 DevOps 的核心动力之一。如果你的组织正在考虑采用 DevOps 方法,那么 CI/CD 必将包含在其中。但 CI/CD 究竟意味着什么?为什么它如此关键?要与 IT 团队共同制定 DevOps 工具链和实施策略,理解 CI/CD 的基本概念至关重要。本文将探讨 CI/CD 所解决的痛点、所需工具以及可预期的收益。
我们先从宏观视角说起。DevOps 的目标是构建一条从左到右、尽可能减少交接环节并具备快速反馈机制的工作流(关于 DevOps 的入门介绍,请参阅我们之前的文章)。但这具体意味着什么呢?在我们的场景中,“工作”即代码,应始终向前推进(从左到右),理想情况下绝不应回退以进行修复。问题应在引入之处被识别并立即修复。要实现这一点,开发人员需要快速的反馈回路——这些反馈通过快速的自动化测试提供,用于在代码进入下一阶段前验证其是否按预期运行。
为了减少交接,小型团队将负责更小的功能单元(而非整个功能),并全程负责:创建需求、提交代码、质量保证(QA)和部署——从开发(Dev)到运维(Ops),或统称为 DevOps。重点在于快速推出小块代码。为什么?因为进入生产环境的变更越小,就越容易诊断、修复和补救。
这种从左到右的快速流动由持续集成(CI)实现,并通过持续交付(CD)延伸至实际的生产部署。我们常将其合称为 CI/CD。以上是万米高空的概览,接下来我们将深入细节。
持续集成:深入解析
首先,我们聚焦于 CI/CD 中的持续集成(CI)部分。这是你首先要掌握的内容。事实上,大多数公司仅实现了 CI。要实现 CD,通常需要企业已具备成熟的 DevOps 能力。
那么,什么是持续集成?这里的“集成”指的是程序员在本地机器上开发的代码(更新或新功能)被整合到主代码库(即整个应用程序的其余部分)中。这一过程带来三大挑战:
- 追踪所有变更:一旦发生错误,能够回滚到先前状态,以避免或最小化服务中断。
- 管理冲突:当多名开发者同时在同一项目上工作时,可能出现代码冲突。
- 在新代码合并前捕获错误。
我们将讨论三种应对这些痛点的工具。
1. 版本控制(Version Control)
随着代码从 Dev 流向 Ops,会根据测试结果不断调整。所有这些变更都必须由版本控制系统记录。版本控制是一种软件工具,帮助开发者随时间管理源代码的变更。它将所有变更存储在一个特殊的数据库中。
理想情况下,软件系统的所有组成部分都应纳入版本控制,包括:
- 源代码
- 资源文件(assets)
- 运行环境配置
- 软件开发文档
- 系统内所有文件的变更
2. 主干分支与开发分支(Master and Developer Branches)
通常,多个开发者会同时参与同一个项目,人数可能从几人到数百人不等——这极易造成混乱。为降低破坏主干代码或引入错误的风险,每位开发者会在本地机器上通过“分支”(branches)并行处理系统不同部分。
然而,仅使用分支本身并非解决方案,因为每位开发者所写的代码最终仍需集成到不断演进的主干代码库中。
开发者在分支上工作的时间越长,未向主干提交代码,后续与主干中他人变更的合并就越困难。分支数量越多、每个分支的变更越大,难度呈指数级上升。因此,逻辑上的解决方案是提高提交频率——最好做到持续提交。这正是“持续集成”的由来。
下图展示了不同分支的可视化示例:蓝色为主干分支(master),其他颜色为各个开发者在其本地工作站上的开发分支,他们每天多次或至少每天结束前将变更合并到主干。
当然,现实并非总是顺利。即使开发者每日提交代码,冲突仍会发生——其他成员可能已提交了未被考虑的变更。集成问题通常需要返工,包括手动合并冲突代码。然而,处理一天工作量内的冲突,远比处理一周甚至一个月积累的变更要容易得多。因此,虽然集成问题无法完全避免,但 CI 能显著减少其发生频率和修复成本。
3. 部署流水线与自动化测试(Deployment Pipeline and Automated Tests)
捕获错误并确保代码始终处于可部署状态,属于质量保证(QA)范畴。传统上,QA 由独立团队在开发完成后执行,往往每年仅测试几次。开发者可能在数月后才得知自己引入的错误,此时因果关系早已模糊,导致问题诊断愈发困难。自动化测试正是为解决这一问题而生。
通过部署流水线(一种软件工具),每次代码提交到版本控制系统时,都会自动触发一系列测试。流水线会自动构建并测试代码,确保其按预期工作,并在集成到主干后仍能正常运行。
需要注意的是,代码在测试环境中完美运行,并不代表在生产环境中也能成功。因为生产环境中的配置和依赖项会影响代码表现。依赖项是指应用本身不包含但运行所必需的组件,例如数据库、对象存储,或通过 API 调用的外部服务。因此,开发和测试环境必须尽可能模拟生产环境,并且代码必须在包含所有依赖项的情况下进行测试。
简而言之,代码部署包含三个测试阶段,复杂度逐级递增:
- 验证代码本身是否按预期工作;
- 验证代码在集成到主干后是否仍能正常工作;
- 验证在类生产环境中(含所有依赖)性能是否稳定。
如果代码每日提交,就能每日自动测试,任何构建、测试或集成错误都会在当天被发现并立即修复。这确保代码始终处于可部署、可发布状态,即所谓的“绿色构建”(green build)。
自动化测试使开发者能将测试和集成频率从“周期性”提升到“持续性”,并在约束最少时发现问题。最坏情况下,也仅损失一天的工作量。
旁注:版本控制中的敏感信息争议
关于是否应将访问令牌、密钥、密码等敏感信息存入版本控制,业界存在争议。一派认为“一切皆应纳入版本控制”,包括密钥;另一派则认为这是不良实践,主张敏感信息应单独存储。两种方法各有优劣,也都有支持者与反对者,且均有相应的安全实现技术。
版本控制系统中的所有变更都称为“提交”(commits)或“修订”(revisions)。
版本控制允许开发者比较、合并和恢复历史版本,在生产环境出现问题时可快速回滚至上一稳定版本。为实现这一点,所有变更(无论多小)都必须纳入版本控制。否则,生产环境代码将与开发/测试环境不一致,导致后续问题。
简言之,版本控制是系统的唯一真实来源(single source of truth),既包含系统当前的确切状态,也包含所有历史状态。通过将所有生产制品纳入版本控制,开发者可反复、可靠地复现整个软件系统的全部组件。这是实现所谓“不可变基础设施”(immutable infrastructure)的关键——我们稍后会再讨论这一点。
持续交付:延伸 CI,实现平滑部署
即使实现了持续集成,将代码部署到生产环境仍可能是手动、繁琐且易错的。若果真如此,部署频率必然降低。IT 人员和其他人一样,会尽量避免困难且高风险的任务。这会导致待部署代码与生产环境代码差异越来越大,形成一个危险的恶性循环。解决之道就是持续交付(CD)——CI/CD 中的“CD”部分。
CD 在 CI 基础上进一步确保代码在全面上线前已在生产环境中平稳运行。最常见的 CD 策略是金丝雀发布(Canary)和蓝绿部署(Blue-Green)。
- 蓝绿部署:IT 团队将新版本(绿色)与当前运行版本(蓝色)并行部署到生产环境。新版本在后台测试,而旧版本继续服务用户。若一切正常,则将所有流量切换至新版本。
- 金丝雀发布:同样存在两个版本。IT 先将一小部分用户请求(如 1%)路由到新版本,持续监控代码表现和用户行为。若错误率或投诉未增加,则逐步扩大比例(如 10% → 50% → 100%)。当全部流量切换后,旧版本即可退役或删除。
通过按需环境创建实现自助服务
在探讨完 CI 和 CD 及其工具后,我们来谈谈环境与基础设施。CI/CD 要求一种全新的方法。
如前所述,自动化测试使开发者能自行完成 QA。为确保代码在生产中正常运行,他们必须在类生产环境中进行开发和测试。
传统上,开发者需向运维团队申请测试环境,而后者需手动搭建——这一过程可能耗时数周甚至数月。此外,手动部署的测试环境常因配置错误或与生产环境差异过大,导致即使通过所有预部署测试,上线后仍出现问题。
CI/CD 的关键在于:为开发者提供按需创建的、可在本地工作站运行的类生产环境。为何重要?只有在相同条件下部署和测试,开发者才能准确预知代码在生产中的行为。若在不同环境中测试,兼容性问题可能直到上线才暴露,届时修复将直接影响客户。
不可变基础设施:从“宠物”到“牛群”
前文提到,需将环境配置与其他应用制品一同纳入版本控制。现在我们更具体地讨论这些环境。
当环境细节被定义并编码写入版本控制系统后,横向扩展(horizontal scaling)时复制环境就变得像“按一下按钮”般简单(如今通常由 Kubernetes 自动完成)。
云的弹性使扩展变得日益重要。例如,Netflix 的使用量每周五晚间达到峰值,午夜后回落。为保障无卡顿的视频体验,Netflix 会根据需求,复制其流媒体组件(这些组件已在版本控制中定义)。高峰过后,所有副本被销毁,容量回归常态。
要实现这一点,关键在于:任何基础设施或应用更新都必须自动同步到所有地方,并纳入版本控制。这样,无论何时创建新环境,都能与整个流水线(从开发到 QA 再到生产)中的环境保持一致。例如,若 Netflix 更新了流媒体服务却未将变更写入版本控制,高峰时段就会复制出有缺陷或过时的组件,可能导致服务中断。
因此,手动修改环境已非最佳实践——任何手动操作都易出错。正确做法是:将变更写入版本控制,然后从头重建整个环境(包括代码和基础设施)。这就是不可变基础设施(Immutable Infrastructure),其原则与 CI/CD 一脉相承。
你可能听过“牛群 vs 宠物”(Cattle vs Pets)的比喻——这里正适用。过去,基础设施被视为“宠物”:一旦出问题,我们会竭尽全力修复以保其存活。如今,基础设施被视为“牛群”:若运行异常或需更新,直接“宰杀”并启动全新实例。这种方法极为强大,大幅降低了问题潜入系统的风险。
解耦部署与发布
传统上,软件发布由市场推广日程驱动。新功能通常在宣传发布日的前一天部署到生产环境。但我们知道,向生产环境发布功能始终存在风险,尤其是一次性发布整个功能时。因此,将部署与发布绑定,等于让 IT 团队自陷险境。试想:若在大规模宣传后、发布前夕出现严重问题,IT 团队将面临何等压力?
更好的做法是解耦部署与发布。尽管二者常被混用,实则截然不同:
- 部署(Deployment):指将软件版本安装到任意环境(包括生产环境),不一定对外可见。
- 发布(Release):指将新功能开放给用户使用。
频繁在生产环境中部署(即使功能尚未对用户开放),是 IT 主导的降低风险的手段。而何时向用户开放新功能,则应是业务决策,而非技术决策。
部署前置时间(lead time)的长短,决定了新功能发布的频率。若 IT 能按需部署,那么“何时上线”就成为市场与业务的自由选择。
结论
总结而言,持续集成(CI)要求代码持续合并到主干,以便在问题发生时立即捕获,最大限度减少返工。实现 CI 需三大工具:
- 版本控制:追踪所有变更,确保团队始终使用最新代码;
- 主干分支模型:开发者在各自分支工作,并每日合并到主干;
- 部署流水线:自动触发一系列测试,实质上实现了自动化 QA。
持续交付(CD)则在 CI 基础上,验证代码是否处于可部署状态,并在满足条件时自动发布到生产环境。实现 CD 通常需要企业已熟练掌握 CI,具备成熟的 DevOps 能力。
若实施得当,CI(/CD)将显著提升 IT 团队生产力:系统持续改进,部署风险降至最低,形成生产力与员工满意度的正向循环。创新得以加速,新功能和更新更快、更频繁地为用户创造价值。其益处不胜枚举。
显然,随着越来越多组织采用 DevOps 方法,未采用者将面临日益加剧的竞争压力——传统模式已无法与 CI/CD 的效率和敏捷性抗衡。
附录:部署流水线中的测试套件
- 集成测试(Integration tests):检查应用与其他应用或服务的交互是否正常,确保代码能正确处理依赖项。可使用虚拟或模拟的远程服务,以精确复现生产环境。
- 验收测试(Acceptance tests):验证业务需求是否满足,确保功能或应用为终端用户带来预期价值。
- 性能测试(Performance tests):在类生产负载下,验证应用在整个技术栈(代码、数据库、存储、网络、虚拟化层)中的表现。架构决策或网络、数据库、存储等系统的隐性瓶颈应在此阶段暴露。
- 非功能性测试(Non-functional testing):包括可用性、可扩展性、性能、容量、安全性等。这些需求依赖于环境的正确配置,测试将验证环境是否按要求构建和配置。
- 冒烟测试(Smoke tests):验证应用能否连接所有支撑系统(如数据库、服务、消息系统等);通常为手动执行。
此外,还包括自动化安全测试,以及探索性测试等手动或资源密集型测试。目标是尽早捕获尽可能多的错误,而将这些耗时测试仅用于验证高层需求,并在尽可能接近生产的环境中完成全链路集成验证。