14、基础设施代码开发中的高效实践

基础设施代码开发中的高效实践

在大型项目开发中,错误发现的时机至关重要。就好比在搭建乐高积木时,如果一开始就发现某个零件放错了位置,及时纠正就很容易;但如果等整个积木塔快搭建完了才发现,那纠正起来就困难重重。在项目开发中也是如此,若在项目接近尾声才发现错误,其影响会更糟糕。因为此时错误被掩埋在数周或数月的其他更改中,难以发现和调试,我们也会丢失最初更改时的上下文信息,而且错误很可能与大量其他更改相互交织,难以理清和修复。

持续集成(CI)

持续集成是在系统开发过程中,频繁地集成和测试所有更改的实践。这与传统软件开发在项目接近尾声才进行集成阶段的方法不同。传统方法中,不同人员或团队孤立地处理项目的各个部分,直到完成后才将这些部分整合并进行全面测试。其背后的理念是,各部分之间的接口可以定义得足够清晰,整合时不会出现问题,但事实证明这种想法往往是错误的。

后期集成的问题在于,通常在用户期望系统或更改准备好使用前不久才发现问题。这时,我们要么推迟发布日期,要么采用临时解决方案来推出产品。大多数情况下,我们会两者都做,并告诉自己稍后会妥善修复。然而,大多数 IT 团队都有大量积压的工作,很难抽出时间进行彻底修复。

持续集成则要求工程师频繁提交小的更改,一天提交多次更改,甚至每小时提交一两次更改都是很常见的。每次有人提交更改(如配置定义、脚本、测试等),CI 服务器就会拉取更改并运行测试,理想情况下这个过程不到一分钟,最坏情况也只需几分钟。

当 CI 中的测试失败时,会触发“停止生产线”的情况,开发团队称之为“构建失败”或“红色构建”。在错误修复之前,团队中的其他人不应提交任何更改,以便更轻松地解决问题。因为在构建失败的基础上继续添加提交,很难调试原始原因并将其与后续更改分开。触发失败的提交者应优先修复问题,如果更改不能迅速修复,则应在版本控制系统(VCS)中撤销更改,使 CI 作业能够再次运行并恢复到“绿色状态”。

团队需要即时了解 CI 作业的状态。通常会在团队工作区使用信息辐射器,以及在构建失败时弹出通知的桌面工具。电子邮件在这方面效果不佳,因为它们往往会被过滤和忽略。构建失败需要成为“红色警报”,在知道谁在负责修复之前,让每个人都停下来。

及时修复失败的构建对于有效的 CI 至关重要。不要养成忽略失败测试的习惯,一旦这样做,构建始终处于红色状态,CI 的价值就为零,还不如完全关闭 CI 以节省运行成本。另一个相关的坏习惯是暂时禁用或注释掉失败的测试,只是为了让事情继续推进。修复失败的测试应该是首要任务,虽然发布特定更改可能很重要,但测试套件是有效发布的保障。如果允许测试质量下降,就会损害交付能力。

还应消除不稳定测试,即那些经常失败一次,再次运行时又通过的测试。随机失败的测试会使团队习惯忽略红色构建,导致真正的问题不能及时被发现,之后还需要仔细检查各种提交来找出真正的失败原因。不稳定的构建是系统或测试存在问题的症状,要毫不留情地追查这些失败的原因,并改进测试技术以消除不稳定性。

测试驱动开发(TDD)和自测试代码对于 CI 至关重要。TDD 和 CI 就像一张安全网,确保我们在犯错时就能及时发现,而不是在数周后才被问题困扰。

持续交付(CD)

持续交付是在持续集成的基础上更进一步。CI 对每次提交进行集成和测试,而 CD 确保每次提交时整个系统都能准备好投入生产。更改会应用到类似生产环境中,使用与生产环境相同的工具和流程。在进行更改时持续这样做,将更改部署到生产环境就会变得轻而易举。

测试环境不必与生产环境完全相同,但重要的是要模拟生产环境的关键方面,包括各种复杂情况。基本上,任何在生产环境部署时可能出现的问题,都应在测试环境中得到充分模拟。测试环境应像生产环境一样进行严格管控,在测试环境中赋予生产环境不允许的特权,只会导致生产部署失败。如果生产环境的限制使得在测试环境中难以快速、频繁地进行更改,那么就需要找到解决方案,以便在这些限制内实现快速、频繁的部署。

例如,在某个银行项目中,部署到生产环境需要手动输入敏感密码。为此创建了一种机制,让某人可以在 Jenkins 控制台触发部署作业时输入密码。在不需要密码的测试阶段,自动化了这个过程,但脚本和作业运行的是完全相同的脚本,这样就可以通过一致、可重复的过程模拟生产环境的限制。这也说明手动审批可以融入 CD 过程,完全可以适应需要人工参与的敏感基础设施更改的治理流程。关键是要确保人工参与仅限于审查和触发更改的应用,而实际应用更改的过程应该是自动化的,并且在每个应用环境中应该完全相同。

有一种误解认为 CD 意味着每次提交的更改在通过自动化测试后立即应用到生产环境。虽然一些采用持续交付的组织确实采用了这种持续部署的方法,但大多数并非如此。CD 的重点不是立即将每个更改应用到生产环境,而是确保每个更改都可以应用。CD 使是否以及何时将更改应用到生产环境成为一个业务决策,而不是技术决策。将更改部署到生产环境不再是一个具有破坏性的事件,不需要团队停止开发工作,也不需要项目计划、交接文档或维护窗口,只需重复在测试环境中多次执行和验证过的过程即可。对于 IT 运维团队来说,持续交付意味着基础设施的更改在进行时会得到全面验证。用户所需的更改,如在生产环境中添加新服务器,无需 IT 运维团队参与即可完成,因为他们已经确切知道有人点击按钮将 Web 服务器添加到 Web 服务器池时会发生什么。

VCS 管理内容

版本控制系统(VCS)是基础设施即代码体系的重要组成部分,在使用 VCS 时,需要考虑基础设施团队应该管理哪些内容。

应该将构建和重建基础设施元素所需的所有内容纳入版本控制。理想情况下,如果整个基础设施消失,只剩下版本控制中的内容,应该能够检出所有内容并运行几个命令来重建一切,可能还需要拉取备份数据文件。以下是一些需要进行版本控制的内容:
- 脚本和编译实用程序及应用程序的源代码
- 配置文件和模板
- 配置定义(如 Cookbooks、Manifests、Playbooks 等)
- 测试

而以下内容可能不需要在 VCS 中进行管理:
- 软件工件应存储在仓库中,如 Java 工件的 Maven 仓库、apt 或 yum 仓库等,这些仓库应进行备份或有可重建它们的脚本(存储在 VCS 中)。
- 由 VCS 中已有元素构建的软件和其他工件不需要添加到 VCS 本身,因为可以从源代码重建。
- 应用程序管理的数据、日志文件等不属于 VCS,应根据需要进行存储、存档和/或备份。
- 密码和其他安全机密不应存储在 VCS 中,应使用用于在自动化基础设施中管理加密密钥和机密的工具。

分支策略与 CI/CD 的冲突

VCS 支持分支,允许人们同时处理同一代码的不同版本,这在软件开发中很常见,但对 CI 和 CD 来说存在问题。基础设施团队在采用 VCS 时,尤其是在使用分支进行软件开发的组织中工作的团队,可能会倾向于采用类似的分支策略。然而,分支是后期集成的一种变体,会增加风险,因为人们甚至有时是团队在处理相同的代码。集成分支称为合并,需要解决代码中被多个人或团队修改的区域,即合并冲突,这需要手动决定哪些更改应被接受并用于合并后的代码。

现代 VCS 工具(如 git)比旧工具(如 Subversion)更容易处理合并的技术方面,但无法神奇地解决合并的逻辑问题。即使合并不涉及对同一行代码的更改,更改之间仍可能存在功能交互,从而导致问题。

不过,这并不意味着分支是有害或愚蠢的。许多团队发现它是管理复杂软件项目的有效方法,一些高度成熟的团队可以有效地将非常短期的分支与 CI 结合使用。但长时间保留分支与持续集成相冲突,因为持续集成的定义是在开发过程中持续合并和集成所有代码。

以下是软件开发项目中常见的分支策略:
| 分支策略 | 描述 |
| — | — |
| 功能分支 | 当一个人或小团队开始对代码库进行更改时,可以创建一个分支,以便独立工作。这样,他们正在进行的工作不太可能破坏生产环境中的某些东西。更改完成后,团队将其分支合并回主干。 |
| 发布分支 | 当新的版本部署到生产环境时,创建一个分支以反映生产环境中的当前版本。在发布分支上进行错误修复并合并到主干。下一个版本的工作在主干上进行。 |
| 基于主干的开发 | 所有工作都在主干上进行并提交。使用持续集成确保每次提交都经过全面测试,并使用持续交付管道确保更改仅在经过全面验证后才应用到生产环境。 |

发布分支对于开发周期较长、发布不频繁的代码库可能有用,因为它便于在发布之间对生产环境进行紧急修复,但对通常进行持续小更改的基础设施团队不太适用。对于重大基础设施更改创建分支可能看似有吸引力,但实际上只会积累风险。随着在特殊分支中完成的工作越来越多,将其部署所需的工作量也会增加。即使在分支上完成了重大更改,将其发布也会成为一个重大项目,带来大量的工作和风险。

一些团队在使用功能分支时会结合 CI 服务器软件,当更改提交到分支时,CI 服务器会构建和测试该分支,使开发人员能够从代码的持续测试中受益。然而,他们实际上并没有持续集成,只有在合并更改后才会发现其他分支中的冲突更改。团队可以通过在 CI 测试之前将主干持续合并到功能分支来缓解这个问题,但这种冲突发现仍然比直接在主干上进行更改要晚。基于主干的开发是最快发现不同开发人员工作之间冲突的有效策略。

良好的编码实践

随着时间的推移,基础设施代码库会不断增长,难以保持良好的维护状态。这与软件代码的情况类似,因此可以采用许多相同的原则和实践来维护大型基础设施代码库。

近年来,“干净代码”和软件工艺的概念再次受到关注,这对基础设施编码人员和软件开发人员同样重要。许多人认为实用主义(完成工作)和工程质量(正确构建事物)之间存在矛盾,但这是一种错误的二分法。

低质量的软件和基础设施难以维护和改进。如果为了快速完成而选择编写可能存在问题的代码,会导致系统不稳定,问题难以发现和修复。在混乱的代码系统中添加或改进功能也很困难,通常进行一个简单的更改需要花费意想不到的长时间,并且会产生更多的错误和不稳定性。

软件工艺意味着确保所构建的东西能够正常工作,不留下任何未解决的问题。这意味着构建的系统能够让其他专业人员快速轻松地理解。当对一个构建良好的系统进行更改时,能够自信地知道更改会影响系统的哪些部分。

干净代码和软件工艺并不是过度工程化的借口,重点不是为了满足对结构的强迫症需求而使事情变得有序,也不需要构建一个能够处理所有未来可能场景或需求的系统。

基础设施代码开发中的高效实践

不同分支策略的应用场景分析

不同的分支策略适用于不同的开发场景,下面我们来进一步深入分析它们的应用场景。

功能分支策略适合于开发新功能或者进行实验性开发。在这种场景下,开发人员可以在不影响主干代码稳定性的前提下,自由地进行功能开发。当开发完成后,再将功能分支合并回主干。这种策略的优点是可以隔离新功能的开发,避免对现有系统造成影响;缺点是如果分支存在时间过长,合并时可能会遇到较多的冲突。

发布分支策略主要用于管理软件的发布版本。当一个新的版本要发布到生产环境时,从主干创建一个发布分支。在这个分支上进行最后的测试和修复,确保发布的版本稳定可靠。同时,在主干上可以继续进行下一个版本的开发。这种策略的优点是可以方便地对发布版本进行维护和修复,同时不影响后续的开发工作;缺点是需要额外的管理成本来维护发布分支。

基于主干的开发策略则强调所有的开发工作都在主干上进行。这种策略要求开发人员频繁地提交代码,并通过持续集成和持续交付来确保每次提交的代码都是可部署的。基于主干的开发策略的优点是可以快速地发现和解决代码冲突,减少集成的风险;缺点是对开发团队的技术水平和协作能力要求较高。

为了更直观地展示不同分支策略的应用场景,我们可以用一个 mermaid 流程图来表示:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始项目]):::startend --> B{选择分支策略}:::decision
    B -->|新功能开发| C(功能分支):::process
    B -->|版本发布管理| D(发布分支):::process
    B -->|快速迭代开发| E(基于主干的开发):::process
    C --> F(开发新功能):::process
    F --> G(合并回主干):::process
    D --> H(创建发布分支):::process
    H --> I(测试和修复):::process
    I --> J(发布到生产环境):::process
    E --> K(在主干开发):::process
    K --> L(持续集成和交付):::process
持续集成与持续交付的协同作用

持续集成(CI)和持续交付(CD)是相辅相成的,它们共同构成了现代软件开发的高效流程。

持续集成通过频繁的代码集成和测试,确保代码的质量和稳定性。它可以及时发现代码中的问题,避免问题在后期积累导致难以修复。持续集成的关键在于快速反馈,让开发人员能够及时了解自己的代码是否符合要求。

持续交付则是在持续集成的基础上,确保每次提交的代码都可以部署到生产环境。它通过模拟生产环境的测试和部署流程,保证代码在生产环境中的稳定性和可靠性。持续交付使得部署过程变得更加自动化和可重复,减少了人为错误的发生。

为了更好地理解持续集成和持续交付的协同作用,我们可以用一个表格来对比它们的特点:
| 特点 | 持续集成(CI) | 持续交付(CD) |
| — | — | — |
| 目标 | 频繁集成和测试代码,及时发现问题 | 确保每次提交的代码都可部署到生产环境 |
| 执行频率 | 每次代码提交 | 每次代码提交 |
| 测试范围 | 单元测试、集成测试等 | 更全面的测试,包括模拟生产环境的测试 |
| 输出结果 | 代码是否通过测试 | 可部署的代码包 |
| 对开发的影响 | 快速反馈,促进代码质量提升 | 减少部署风险,提高部署效率 |

版本控制系统(VCS)的最佳实践

在使用版本控制系统(VCS)时,除了明确管理内容和选择合适的分支策略外,还有一些最佳实践可以帮助我们更好地使用 VCS。

首先,要建立清晰的提交规范。每个提交都应该有明确的描述,说明这次提交做了什么更改。这样可以方便其他开发人员理解代码的变更历史,也有助于在出现问题时进行追溯。例如,提交信息可以遵循“[功能模块] 简要描述更改内容”的格式,如“[用户管理] 修复用户登录时的密码验证问题”。

其次,定期进行代码审查。代码审查可以发现代码中的潜在问题,提高代码的质量。可以通过 VCS 的功能,如拉取请求(Pull Request)来进行代码审查。开发人员在提交代码到主干之前,先创建拉取请求,让其他团队成员进行审查和反馈,只有通过审查的代码才能合并到主干。

另外,要做好备份和恢复工作。虽然 VCS 本身具有一定的可靠性,但为了防止数据丢失或损坏,还是需要定期对 VCS 仓库进行备份。同时,要制定好恢复计划,确保在出现问题时能够快速恢复数据。

总结

在基础设施代码开发中,持续集成、持续交付和版本控制系统是非常重要的实践。通过持续集成,我们可以及时发现代码中的问题,保证代码的质量;持续交付则让我们能够快速、稳定地将代码部署到生产环境;而版本控制系统则帮助我们管理代码的变更历史,提高团队的协作效率。

同时,选择合适的分支策略和遵循良好的编码实践也是至关重要的。不同的分支策略适用于不同的开发场景,我们需要根据项目的特点来选择合适的策略。而良好的编码实践,如编写干净代码和遵循软件工艺原则,可以让我们的代码更易于维护和扩展。

总之,在基础设施代码开发中,我们要充分利用这些高效实践,不断优化开发流程,提高开发效率和代码质量,从而更好地满足业务需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值