软件架构中的阻抗失配发生在两个组件之间存在一组概念和技术难题时。它实际上是一个借用自电气工程的术语,其中电气输入和输出的阻抗必须匹配才能使电路工作。
在软件开发中,镜像存储库中存储的镜像与其部署描述符(存储在 SCM 中)之间存在阻抗失配。 你如何知道存储在 SCM 中的部署描述符实际上是用于所讨论的镜像的? 这两个代码库以不同的方式跟踪它们所持有的数据,因此将镜像(以不可变的二进制文件单独存储在镜像库中)与其特定的部署描述符(以 Git 中的一系列更改存储的文本文件)进行匹配并非易事。
注意:本文假设您至少熟悉以下概念
- 源代码管理 (SCM) 系统和分支
- Docker/OCI 兼容镜像和容器
- 容器编排平台 (COP),例如 Kubernetes
- 持续集成/持续交付 (CI/CD)
- 软件开发生命周期 (SDLC) 环境
阻抗失配:SCM 和镜像库
为了充分理解这会变成什么问题,请考虑在任何给定项目中通常使用的一组基本软件开发生命周期 (SDLC) 环境; 例如,开发、测试和生产(或发布)环境。
开发环境不存在阻抗失配。 现在的最佳实践(包括使用 CI/CD)规定,对开发分支的最新提交应反映部署在开发环境中的内容。 因此,给定一个典型的、成功的 CI/CD 开发工作流程
- 对 SCM 中的开发分支进行提交
- 该提交触发镜像构建
- 新的、不同的镜像被推送到镜像库并标记为在开发中
- 该镜像使用从 SCM 中提取的最新部署描述符部署到容器编排平台 (COP) 的开发环境中
换句话说,最新的镜像始终与开发环境中的最新部署描述符相匹配。 回滚到以前的版本也不是问题,因为这也意味着要回滚 SCM。
但是,最终,开发进展到需要进行更正式的测试的程度,因此镜像(隐式地与 SCM 中的特定提交相关)被提升到测试环境。 同样,假设构建成功,这不是什么大问题,因为从开发提升的镜像应该反映开发分支中的最新版本
- 对开发的最新部署已获准提升,并且已触发提升过程
- 标记为在测试中的最新开发镜像
- 使用从 SCM 中提取的最新部署描述符,提取镜像并将其部署到测试环境中
到目前为止,一切都很好,对吧? 但是,在以下任一情况下会发生什么?
情景 A。 镜像被提升到下一个下游环境,例如,用户验收测试 (UAT) 甚至生产环境。
情景 B。 在测试环境中发现了一个破坏性错误,并且需要将镜像回滚到已知的良好镜像。
在任一情景中,开发并没有停止,这意味着可能已对开发分支进行了一次或多次提交,这反过来意味着最新的部署描述符可能已更改,并且最新的镜像与先前部署在测试中的镜像不同。 对部署描述符的更改可能适用于旧版本的镜像,也可能不适用,但无论如何都不能信任它们。 如果它们已更改,则它们肯定不是您之前一直使用要部署的镜像进行测试的相同部署描述符。
这就是问题的关键:如果正在部署的镜像不是来自镜像库的最新镜像,您如何识别 SCM 中哪些部署描述符专门应用于正在部署的镜像? 简短的答案是,您不能。 这两个代码库存在阻抗失配。 更长的答案是您可以,但您必须为此付出努力,这将是本文其余部分的主题。 请注意,以下不一定是解决此问题的唯一方法,但它已投入生产并证明对数十个项目有效,这些项目继而已经在生产中构建和部署了一年多。
二进制文件和部署描述符
从构建源代码生成的常见工件是 Docker 或 OCI 兼容镜像,并且该镜像通常会部署到容器编排平台 (COP),例如 Kubernetes。 部署到 COP 需要部署描述符,这些描述符定义了如何将镜像部署并作为容器运行,例如,Kubernetes Deployment 或 CronJob。 正是由于镜像及其部署描述符之间的根本差异,才导致了阻抗失配的出现。 在本次讨论中,将镜像视为存储在镜像库中的不可变二进制文件。 源代码中的任何更改都不会更改镜像,而是会用不同的新镜像替换它。
相比之下,部署描述符是文本文件,因此可以视为源代码并且是可变的。 如果遵循最佳实践,则部署描述符存储在 SCM 中,并且所有更改首先在此处提交以进行正确跟踪。
解决阻抗失配
建议的解决方案的第一部分是确保存在一种将镜像库中的镜像与 SCM 中的源代码提交相匹配的方法,SCM 保存着部署描述符。 最直接的解决方案是用其源代码提交哈希标记镜像。 这将使不同版本的镜像保持分离、易于识别,并提供足够的信息来查找正确的部署描述符,以便可以将镜像正确部署在 COP 中。
再次回顾上面的情景
情景 A。将镜像从一个下游环境提升到下一个下游环境:当镜像从测试提升到 UAT 时,镜像的标签会告诉我们从 SCM 中的哪个源代码提交中提取部署描述符。
情景 B。当镜像需要在下游环境中回滚时:我们选择回滚到的任何镜像也会告诉我们从 SCM 中的哪个源代码提交中提取正确的部署描述符。
在每种情况下,自从特定镜像已部署在测试中以来,已经发生了多少开发分支提交和构建都无关紧要,因为每个已提升的镜像都可以找到最初部署它的确切部署描述符。
但是,这并不是阻抗失配的完整解决方案。 考虑另外两种情景
情景 C。 在负载测试环境中,会尝试在不同时间使用不同的部署描述符,以查看特定构建的性能。
情景 D。 镜像被提升到下游环境,并且该环境的部署描述符中存在错误。
在每种情况下,都需要对部署描述符进行更改,但是现在我们所拥有的只是一个源代码提交哈希。 请记住,最佳实践要求首先将所有源代码更改提交回 SCM。 该哈希中的提交本身是不可变的,因此显然需要比仅跟踪初始源代码提交哈希更好的解决方案。
此处的解决方案是在原始源代码提交哈希处创建一个新分支。 这将被命名为部署分支。 每次将镜像提升到下游测试或发布环境时,都应该从上一个 SDLC 环境的部署分支的头部创建一个新的部署分支。
这将允许在每个 SDLC 环境中以不同的方式重复部署相同的镜像,并且还可以提取在该镜像的每个后续环境中发现或应用的任何更改。
注意:在一个环境中应用的更改如何应用于下一个环境的部署描述符,无论是通过启用共享值(例如 Helm Charts)的工具还是通过手动剪切和粘贴跨目录,都超出了本文的范围。
因此,当镜像从一个 SDLC 环境提升到下一个环境时
- 将创建一个部署分支
- 如果要从开发环境提升镜像,则从构建镜像的源代码提交哈希创建分支
- 否则,从当前部署分支的头部创建部署分支
- 使用该环境新创建的部署分支中的部署描述符将镜像部署到下一个 SDLC 环境中

图 1:部署分支
- 开发分支
- 具有单个提交的第一个下游环境的部署分支
- 具有单个提交的第二个下游环境的部署分支
使用部署分支作为解决方案再次回顾上面的情景 C 和 D
情景 C。 更改部署到下游 SDLC 环境的镜像的部署描述符
情景 D。 修复特定 SDLC 环境的部署描述符中的错误
在每种情景中,工作流程如下
- 将对部署描述符的更改提交到 SLDC 环境和镜像的部署分支
- 使用部署分支顶端的部署描述符,将镜像重新部署到 SLDC 环境中。
因此,部署分支完全解决了镜像仓库(存储代表唯一构建的单个不可变镜像)与 SCM 仓库(存储一个或多个下游 SDLC 环境的可变部署描述符)之间的阻抗不匹配问题。
实际考虑
虽然这看起来是一个可行的解决方案,但也给开发人员和运维人员带来了一些新的实际问题,例如:
A. 作为源代码,部署描述符应该保存在哪里,才能最好地促进部署分支的管理,即,与构建镜像的源代码位于同一个 SCM 仓库中,还是位于不同的 SCM 仓库中?
到目前为止,我们一直避免讨论部署描述符应该驻留在哪个仓库中。在不深入太多细节的情况下,我们建议将所有 SDLC 环境的部署描述符放入与镜像源代码相同的 SCM 仓库中。随着部署分支的创建,镜像的源代码将会随之而来,并作为对实际部署在容器中的内容的一个易于查找的参考。
如上所述,镜像将通过其标签与原始源代码提交相关联。即使使用工具,在一个单独的仓库中查找特定提交处的源代码参考也会给开发人员增加难度,而将所有内容保存在单个仓库中则可以避免这种不必要的难度。
B. 是否应该在部署分支上修改构建镜像的源代码?
简短回答:绝不。
更长的回答:不应该,因为镜像永远不应该从部署分支构建。它们是从开发分支构建的。更改部署分支中定义镜像的源代码会破坏正在部署的镜像构建记录,并且实际上不会修改镜像的功能。在比较来自不同版本的两个部署分支时,这也可能成为一个问题。它可能会给出它们之间功能差异的误报(这是使用部署分支的一个小的额外好处)。
C. 为什么使用镜像标签?不能使用镜像标签(labels)吗?
对于存储在仓库中的镜像,标签易于读取和搜索。对一组镜像读取和搜索具有特定值的标签需要拉取每个镜像的 manifest,这增加了复杂性并降低了性能。此外,为不同版本标记镜像对于历史记录和查找不同版本仍然是必要的,因此使用源代码提交哈希是最简单的解决方案,它保证了唯一性,同时还包含即时有用的信息。
D. 创建部署分支最实际的方法是什么?
DevOps 的前三条规则是自动化、自动化、自动化。
依靠资源来统一执行最佳实践充其量是碰运气,因此在实现镜像晋升、回滚等 CI/CD 管道时,请将自动化部署分支纳入脚本中。
E. 对于部署分支的命名约定有什么建议吗?
<部署分支标识符>-<环境>-<源代码提交哈希>
- 部署分支标识符: 每个部署分支用于将其标识为部署分支的唯一字符串;例如,'deployment' 或 'deploy'。
- 环境: 部署分支所属的 SDLC 环境;例如,'qa'、'stg' 或 'prod' 分别代表测试、暂存和生产环境。
- 源代码提交哈希: 保存构建正在部署的镜像的原始代码的源代码提交哈希,这使得开发人员可以轻松找到创建镜像的原始提交,同时确保分支名称是唯一的。
例如,对于提升到 QA 和 STG 环境的部署分支,分别为 deployment-qa-asdf78s 或 deployment-stg-asdf78s 。
F. 如何知道哪个版本的镜像正在环境中运行?
我们的建议是使用最新的部署分支提交哈希和源代码提交哈希标记所有部署资源。这两个唯一标识符将允许开发人员和运维人员找到已部署的所有内容以及来自何处。使用这些选择器对不同版本的部署进行清理也很简单,例如,在回滚或前滚操作中。
G. 何时应该将部署分支的更改合并回开发分支?
这完全取决于开发团队的判断。
例如,如果您仅为了查看什么会破坏您的应用程序而进行负载测试的更改,那么这些更改可能不是合并回开发分支的最佳选择。另一方面,如果您在下游环境中发现并修复了错误或调整了部署,那么将部署分支的更改合并回开发分支是有意义的。
H. 是否有可用的部署分支示例可以先进行测试?
el-CICD 一年半以来一直在生产环境中成功使用这种策略,用于跨所有 SDLC 下游环境的一百多个项目,包括管理到生产环境的部署。如果您可以访问 OKD、Red Hat OpenShift 实验室集群或 Red Hat CodeReady Containers,您可以下载 最新的 el-CICD 版本 并按照 教程 来了解如何以及何时创建和使用部署分支。
总结
使用上面的工作示例是一个很好的练习,可以帮助您更好地理解开发过程中围绕阻抗不匹配的问题。保持镜像和部署描述符之间的对齐是成功管理部署的关键部分。
评论已关闭。