解决CI/CD中存储库的阻抗不匹配问题的指南

123 阅读13分钟

当两个组件之间存在一系列概念和技术上的困难时,就会发生软件架构中的阻抗不匹配。这实际上是一个从电气工程中借来的术语,电气输入和输出的阻抗必须匹配,电路才能工作。

在软件开发中,存储在图像库中的图像与存储在SCM中的其部署描述符之间存在阻抗不匹配。你怎么知道存储在SCM中的部署描述符是否真的是为了有关的图像?这两个仓库跟踪它们所持有的数据的方式并不相同,所以将一个镜像(单独存储在镜像仓库中的不可改变的二进制文件)与它的特定部署描述符(在Git中作为一系列变化存储的文本文件)进行匹配并不简单。

注意:本文假设至少对以下概念有一定的了解。

  • 源控制管理(SCM)系统和分支
  • 符合Docker/OCI标准的镜像和容器
  • 容器编排平台(COP),如Kubernetes
  • 持续集成/持续交付(CI/CD)
  • 软件开发生命周期(SDLC)环境

阻力不匹配。SCM和图像存储库

为了充分理解这成为一个问题的地方,考虑一组通常用于任何特定项目的基本软件开发生命周期(SDLC)环境;例如,开发、测试和prod(或发布)环境。

开发环境不存在阻抗不匹配的问题。最佳实践,包括使用CI/CD,决定了你的开发分支的最新提交应该反映在开发环境中部署的内容。因此,鉴于一个典型的、成功的CI/CD开发工作流程。

  1. 在SCM中对开发分支进行了提交
  2. 提交触发了一个镜像构建
  3. 新的、独特的映像被推送到映像库,并被标记为在开发中
  4. 镜像被部署到容器协调平台(COP)的开发环境中,并使用从SCM中提取的最新部署描述符。

换句话说,最新的镜像总是与开发环境中的最新部署描述符相匹配。回滚到以前的构建也不是问题,因为这意味着也要回滚SCM。

然而,最终,开发进展到需要进行更多正式测试的地步,所以一个图像--隐含地与SCM中的特定提交有关--被推广到测试环境中。同样,假设构建成功,这也不是什么大问题,因为从开发中推广的图像应该反映开发分支的最新情况。

  1. 最新部署到开发中的图像被批准推广,并触发了推广过程
  2. 最新的开发镜像被标记为处于测试状态
  3. 使用从SCM中提取的最新部署描述符,将镜像拉出并部署到测试环境中。

到目前为止,很好,对吗?但是,在以下两种情况下会发生什么?

场景A:镜像被推广到下一个下游环境,例如用户验收测试(UAT),甚至是生产环境。

场景B:在测试环境中发现了一个破坏性的错误,该镜像需要回滚到一个已知的良好镜像。

在这两种情况下,开发并没有停止,这意味着可能已经发生了对开发分支的一次或多次提交,这又意味着最新的部署描述符可能已经改变,最新的镜像与之前在测试中部署的不一样。部署描述符的变化可能适用于旧版本的图像,也可能不适用,但它们肯定是不可信的。如果它们已经改变了,它们肯定不是你到现在为止一直在用你想部署的镜像测试的部署描述符。

这就是问题的关键:**如果正在部署的图像不是图像库的最新版本,你如何识别SCM中哪些部署描述符特别适用于正在部署的图像?**简短的答案是,你不能。这两个存储库有一个阻抗不匹配。较长的答案是,你可以,但你必须为之努力,这将是本文的其余部分的主题。请注意,下面的方法不一定是这个问题的唯一解决方案,但它已经被投入生产,并被证明对几十个项目有效,而这些项目在生产中构建和部署已经超过一年了。

二进制文件和部署描述符

构建源代码产生的常见工件是一个Docker或OCI兼容的镜像,该镜像通常会被部署到容器协调平台(COP),如Kubernetes。部署到COP需要部署描述符,定义如何部署镜像并作为容器运行,例如,Kubernetes部署CronJobs。正是因为镜像是什么和它的部署描述符之间的根本区别,阻抗不匹配才表现出来。在这次讨论中,把镜像看作是存储在镜像库中的不可改变的二进制文件。源代码的任何变化都不会改变镜像,而是用一个独特的、新的镜像来取代它。

相比之下,部署描述符是文本文件,因此可以被认为是源代码和可改变的。如果遵循最佳实践,那么部署描述符就会被存储在SCM中,所有的变化都会首先提交到那里,以便正确跟踪。

解决阻抗不匹配的问题

建议的解决方案的第一部分是确保存在一种方法,将图像库中的图像与SCM中的源提交相匹配,SCM中拥有部署描述符。最直接的解决方案是用源提交的哈希值来标记图像。这将使图像的不同版本分开,易于识别,并提供足够的信息来找到正确的部署描述符,从而使图像可以在COP中正确部署。

再回顾一下上面的情景。

场景A.将一个镜像从一个下游环境推广到下一个环境。当映像从测试环境推广到UAT时,映像的标签告诉我们要从SCM的哪个源提交中提取部署描述符。

场景B当一个镜像需要在下游环境中回滚时。无论我们选择回滚到哪个镜像,都会告诉我们从SCM的哪个源提交中提取正确的部署描述符。

在每一种情况下,自从某个特定的镜像在测试中部署后,发生了多少次开发分支的提交和构建并不重要,因为每一个被推广的镜像都可以找到它最初部署时的准确部署描述符。

然而,这并不是解决阻抗不匹配问题的完整方案。请考虑另外两种情况。

场景C:在负载测试环境中,在不同时间尝试不同的部署描述符,以了解特定构建的表现。

场景D:一个图像被推广到下游环境,而该环境的部署描述符有一个错误。

在每一种情况下,都需要对部署描述符进行修改,但现在我们所拥有的只是一个源码提交哈希。请记住,最佳实践要求所有的源代码修改都要先提交回SCM。该哈希值的提交本身是不可改变的,所以显然需要一个比跟踪初始源码提交哈希值更好的解决方案。

这里的解决方案是在最初的源代码提交散列处创建一个新的分支。这将被称为部署分支。每当一个镜像被推广到下游的测试或发布环境时,你应该从之前SDLC环境的部署分支的头部创建一个新的部署分支。

这将允许在每个SDLC环境中以不同的方式重复部署相同的映像,也可以在每个后续环境中发现或应用该映像的任何更改。

注意:在一个环境的部署描述符中应用的更改如何应用到下一个环境中,无论是通过Helm Charts等能够共享值的工具还是通过手动剪切和粘贴跨目录,都超出了本文的范围。

所以,当一个镜像从一个SDLC环境推广到下一个环境时。

  1. 一个部署分支被创建
    1. 如果镜像是在开发环境中推广的,那么分支是由构建镜像的源提交哈希值创建的。
    2. 否则,部署分支将从当前部署分支的头部创建
  2. 镜像被部署到下一个SDLC环境中,使用该环境新创建的部署分支的部署描述符。

deployment branching tree

图1:部署分支

  1. 开发分支
  2. 第一个下游环境的部署分支,只提交一次
  3. 第二个下游环境的部署分支,只提交了一次

用部署分支作为解决方案,重新审视上面的情景C和D。

场景C:改变部署到下游SDLC环境的镜像的部署描述符

场景D:修复某个SDLC环境的部署描述符中的错误

在每个场景中,工作流程如下。

  1. 将对部署描述符的更改提交给 SLDC 环境和映像的部署分支
  2. 使用部署分支头部的部署描述符,将映像重新部署到 SLDC 环境中。

因此,部署分支完全解决了存储代表唯一构建的单一、不可变图像的图像库与存储用于更多下游 SDLC 环境的可变部署描述符的 SCM 存储库之间的阻抗不匹配问题。

实际考虑

虽然这似乎是一个可行的解决方案,但它也为开发人员和运营资源带来了几个新的实际问题,例如。

A.部署描述符应该作为源保存在哪里,以最好地促进部署分支管理,即在与构建镜像的源相同或不同的SCM存储库中?

到目前为止,我们一直在避免谈论部署描述符应该存放在哪个存储库中。我们建议将所有SDLC环境的部署描述符放入与镜像源相同的SCM存储库中,这一点无需太多细节。随着部署分支的创建,映像的来源也将随之而来,并作为一种易于查找的参考,以了解正在部署的容器中实际运行的内容。

如上所述,图像将通过其标签与原始源提交相关联。在一个单独的资源库中寻找特定提交的源码参考,会给开发者增加一定的难度,即使有工具,也没有必要把所有东西都放在一个资源库中。

B.是否应该在部署分支上修改构建镜像的源代码?

简单回答。绝不可以

更长的答案。不,因为镜像不应该从部署分支构建。它们是由开发分支构建的。改变部署分支中定义映像的源码,会破坏构建被部署的映像的记录,实际上并没有修改映像的功能。在比较不同版本的两个部署分支时,这也可能成为一个问题。它可能会对它们之间的功能差异产生误判(这是使用部署分支的一个小但额外的好处)。

C.为什么要用图像标签?难道不能使用图像标签吗?

对于存储在资源库中的图像,标签是很容易阅读和搜索的。读取和搜索一组图像上具有特定值的标签需要为每张图像提取清单,这增加了复杂性并降低了性能。另外,为不同版本的图像打上标签,对于历史记录和查找不同的版本还是很有必要的,所以使用源提交哈希是最简单的解决方案,既能保证唯一性,又能包含即时有用的信息。

D.创建部署分支的最实用方法是什么?

DevOps的前三条规则是自动化自动化自动化

依靠资源来统一执行最佳实践,充其量也就是一击即中,所以在实施用于镜像推广、回滚等的CI/CD管道时,应将自动部署分支纳入脚本。

E.对部署分支的命名惯例有什么建议吗?

<部署-分支-标识符>-<环境>-<src-commit-hash>

  • 部署-分支-标识符。每个部署分支都使用一个独特的字符串来识别它是一个部署分支;例如,"部署 "或 "部署"。
  • env:部署分支所涉及的SDLC环境;例如,"qa"、"stg "或 "prod "分别代表测试、暂存和生产环境。
  • src-commit-hash。源代码提交哈希值,它持有构建被部署的镜像的原始代码,这使得开发人员能够轻松找到创建镜像的原始提交,同时确保分支名称是唯一的。

比如说 部署-QA-ASDF78S部署-stg-asdf78s分别用于推广到QA和STG环境的部署分支。

F.你怎么知道环境中运行的是哪个版本的镜像?

我们的建议是用最新的部署分支提交哈希值和源提交哈希值来标记你的所有部署资源。这两个唯一的标识符将使开发人员和操作人员能够找到所有被部署的东西以及从哪里来的。这也使得在不同版本的部署中使用这些选择器清理资源变得非常简单,例如在回滚或前滚操作中。

G. 什么时候把部署分支的修改合并回开发分支比较合适?

这完全由开发团队决定,什么是合理的。

比如说,如果你出于负载测试的目的进行修改,只是想看看什么会破坏你的应用程序,那么这些修改可能不是合并回开发分支的最佳选择。另一方面,如果你发现并修复了一个错误,或者在下游环境中调整了一个部署,那么将部署分支的修改合并到开发分支中是有意义的。

H.是否有一个部署分支的工作实例可以先进行测试?

el-CICD已经在所有SDLC下游环境的一百多个项目的生产中成功使用了这个策略一年半了,包括管理部署到生产中的工作。如果你能访问OKD、红帽OpenShift实验室集群或红帽CodeReady容器,你可以下载最新的el-CICD版本,并通过教程运行,看看如何以及何时创建和使用部署分支。

总结

使用上面的工作实例将是一个很好的练习,可以帮助你更好地理解开发过程中围绕阻抗不匹配的问题。保持图像和部署描述符之间的一致性是成功管理部署的一个关键部分。