更快的发布周期是微服务体系结构的主要优势之一。 但是如果没有制定合理的 CI/CD 流程,就无法实现微服务具备的敏捷性。 本文介绍具体的难题,并推荐一些问题解决方法。
什么是 CI/CD?
当我们讨论 CI/CD 时,其实是在讨论多个相关的过程:持续集成、持续交付和持续部署。
持续集成。 代码更改经常合并到主分支中。 自动化生成和测试过程确保了主分支中的代码始终保持生产质量。
持续交付。 通过 CI 过程的任何代码更改将自动发布到类似于生产的环境中。 部署到实时生产环境可能需要人工批准,否则可自动进行。 目标是让代码始终做好部署到生产环境的准备。
连续部署。 通过前两个步骤的代码更改会自动部署到生产环境中。
下面是微服务体系结构的可靠 CI/CD 过程的一些目标:
每个团队可以独立生成并部署自有的服务,而不影响或干扰其他团队。
新服务版本在部署到生产环境之前,会先部署到开发/测试/QA 环境进行验证。 在每个阶段强制实施质量控制。
新服务版本可以连同前一版本一起部署。
实施足够的访问控制策略。
对于容器化工作负载,可以信任部署到生产环境的容器映像。
为什么可靠的 CI/CD 管道很重要
在传统的整体应用程序中,有一个生成管道的输出就是应用程序可执行文件。 所有开发工作都会馈送到此管道中。 如果发现了高优先级 bug,则必须集成、测试然后发布修复程序,这可能会延缓新功能的发布。 运用合理构造的模块并使用功能分支来最小化代码更改造成的影响可以缓解这些问题。 但是,随着应用程序变得越来越复杂和更多功能的添加,整体应用程序的发布过程往往会变得更加脆弱且可能中断。
根据微服务的理念,永远不应出现一个要让每个团队遵守的冗长发布周期。 生成服务“A”的团队随时可以发布更新,而无需等到已合并、测试并部署服务“B”中的更改。
若要实现高发布速度,发布管道必须自动化且高度可靠,以最大程度地降低风险。 如果每天发布到生产环境一次或多次,则回归或服务中断的情况必须很少见。 同时,如果部署了错误的更新,则必须通过可靠的方式快速回滚或者前滚到以前的服务版本。
挑战
许多小型的独立代码库。 每个团队负责使用自己的生成管道生成自己的服务。 在某些组织中,团队可能会使用单独的代码存储库。 单独的存储库可能导致出现这种情况:各个团队对系统的生成方式各不相同,组织中没有人知道如何部署整个应用程序。 例如,在灾难恢复方案中,如果需要快速部署到新群集,会发生什么情况?
缓解措施:使用统一的自动化管道来生成和部署服务,以便不会在每个团队中“隐藏”这些知识。
多种语言和框架。 如果每个团队混合使用其自身的技术,则可能难以创建一套可在整个组织中运用的单一生成过程。 生成过程必须足够灵活,使每个团队能够根据所选的语言或框架来改编它。
缓解措施:容器化每个服务的生成过程。 这样,生成系统只需要能够运行容器。
集成和负载测试。 如果团队根据自己的步调发布更新,则可能难以设计可靠的端到端测试,尤其是当服务彼此依赖时。 此外,运行整个生产群集可能费用不菲,每个团队不太可能只是出于测试目的,以生产规模运行整个群集。
发布管理。 每个团队应该能够将更新部署到生产环境。 这并不意味着每个团队成员都有权这样做。 但是,设置集中式“发布管理员”角色可能降低部署速度。
缓解措施:CI/CD 过程的自动化程度和可靠性越高,对集中授权的需求就越少。 也就是说,可以对主要功能更新与次要 bug 修复的发布采用不同的策略。 分散并不意味着零治理。
服务更新。 将某个服务更新到新版本时,更新过程不应中断依赖于该服务的其他服务。
缓解措施:使用蓝绿或 Canary 发布等部署技术进行非中断性变更。 对于中断性 API 更改,请与以前的版本并行部署新版本。 这样,就可以更新使用之前版本 API 的服务,并针对新 API 进行测试。 请参阅下面的更新服务。
单存储库与多存储库
在创建 CI/CD 工作流之前,必须了解如何对代码库进行结构设计和管理。
- 团队是在多个单独的存储库中工作,还是在一个 monorepo(单存储库)中工作?
- 什么是分库策略?
- 谁可以将代码推送到生产环境中? 是否有发布经理角色?
首选单存储库方法,但二者各有优缺点。
单存储库 | 多存储库 | |
---|---|---|
优点 | 代码共享 更易于实现代码和工具的标准化 更易于重构代码 可发现性 - 代码的单一视图 |
按团队清除所有权 合并冲突可能会更少 有助于强制分离微服务 |
挑战 | 对共享代码进行的更改可能影响多个微服务 合并冲突可能会更多 工具必须缩放成大型代码库 访问控制 部署过程更复杂 |
更难以共享代码 更难以强制实施编码标准 依赖项管理 代码库分散,可发现性低 缺少共享的基础架构 |
更新服务
可通过多种策略来更新已部署到生产环境的服务。 下面介绍三个常用选项:滚动更新、蓝绿部署和 Canary 发布。
滚动更新
在滚动更新中部署服务的新实例,然后,新实例可开始立即接收请求。 启动新实例后,以前的实例将被删除。
示例。 在 Kubernetes 中,更新部署的 pod 规范时,滚动更新是默认行为。 部署控制器为更新的 pod 创建新的副本集。 然后,它会纵向扩展新的副本集并纵向缩减旧的副本集,以保持所需的副本计数。 在新 pod 准备就绪之前,它不会删除旧 pod。 Kubernetes 保留更新历史记录,因此,你可以根据需要回滚更新。
示例。 默认情况下,Azure Service Fabric 使用滚动更新策略。 此策略最适合部署具有新功能的服务版本,而无需更改现有 API。 Service Fabric 通过将应用程序类型更新到节点的子集或更新域来启动升级部署。 然后,它会前滚到下一个更新域,直到升级所有域。 如果升级域更新失败,则应用程序类型将跨所有域回滚到以前的版本。 请注意,具有多个服务的应用程序类型(如果所有服务都作为一个升级部署的一部分进行更新)很容易失败。 如果一个服务更新失败,则整个应用程序将回滚到以前的版本,其他服务不会更新。
滚动更新的一个难题在于,在更新过程中,旧版本和新版本将混合运行并接收流量。 在此期间,任何请求都可能路由到两个版本中的一个。
对于中断性 API 更改,一个好的做法是并行支持这两个版本,直到更新以前版本的所有客户端。 请参阅 API 版本控制。
蓝绿部署
在蓝绿部署中,需要连同以前的版本一起部署新版本。 验证新版本之后,可以一次性地将所有流量从以前的版本切换到新版本。 切换后,可以监视应用程序中的任何问题。 如果出现问题,可以切换回到旧版本。 如果没有任何问题,则可以删除旧版本。
对于较传统的整体或 N 层应用程序而言,蓝绿部署通常要预配两个相同的环境。 需将新版本部署到过渡环境,然后将客户端流量重定向到过渡环境 - 例如,通过交换 VIP 地址。 在微服务体系结构中,更新发生在微服务级别,因此通常将更新部署到同一环境中,并使用服务发现机制进行交换。
示例。 在 Kubernetes 中,不需要预配单独的群集即可执行蓝绿部署。 可以利用选择器。 使用新的 pod 规范和一组不同的标签创建新的部署资源。 无需删除以前的部署或修改指向该部署的服务,即可创建此部署。 运行新 pod 后,可以更新服务的选择器,以匹配新部署。
蓝绿部署的缺点是,在更新过程中,需要对服务运行 pod 两次(当前 pod 和下一个 pod)。 如果 pod 需要大量的 CPU 或内存资源,则可能需要暂时横向扩展群集以处理资源消耗。
Canary 发布
在 Canary 发布中,可将更新的版本推出到少量的客户端。 然后,可以监视新服务的行为,再将它推出到所有客户端。 这样,便可以通过受控的方式慢速推出,观察真实数据并发现问题,避免对所有客户造成影响。
Canary 发布比蓝绿部署或滚动更新更难于管理,因为必须动态将请求路由到服务的不同版本。
示例。 在 Kubernetes 中,可将服务配置为跨越两个副本集(每个版本各有一个),并手动调整副本计数。 但是,由于 Kubernetes 跨 pod 进行负载均衡的方式,这种方法不够精确。 例如,如果总共有 10 个副本,则只能以 10% 的增量切换流量。 如果使用服务网格,则可以使用服务网格路由规则来实现更复杂的 Canary 发布策略。