基础设施的软件工程实践:CI、CD与VCS的应用
1. 乐高集成游戏与早期发现错误的重要性
乐高集成游戏是一种有趣的方式,可用来演示后期集成和持续集成之间的差异。在大型项目(如实施新服务)中犯错时,我们希望在进行更多工作之前发现错误。若直到项目接近完成才进行全面测试,才发现错误,其影响会更糟。因为错误被掩埋在数周或数月的其他更改中,更难发现和调试,我们也会失去最初更改时的上下文,且错误可能与大量其他更改交织在一起,难以理清和修复。
2. 持续集成(CI)
2.1 持续集成的定义与优势
持续集成是在系统开发过程中,频繁集成和测试所有更改的实践。这与传统软件开发在项目接近尾声时进行集成阶段的方法不同。在集成阶段方法中,不同人员或团队孤立地处理项目的各个部分,完成后才将这些部分组合并进行全面测试,认为各部分之间的接口可以定义得很好,组合时不会有问题,但事实并非如此。
后期集成的问题在于,通常在用户期望系统或更改准备好使用前不久才发现问题,此时不得不选择推迟发布日期或采用临时解决方案。而持续集成和持续交付提供了更有吸引力的替代方案,虽然不能保证项目完美,但正确使用时比集成阶段更有效。
2.2 持续集成的工作流程
在持续集成中,工程师频繁提交小更改,每天提交多次更改甚至每小时提交一两次更改都很常见。每次有人提交更改(如配置定义、脚本、测试等),CI服务器会拉取更改并运行测试,理想情况下在一分钟内完成,最坏情况也只需几分钟。
2.3 处理构建失败
当CI中的测试失败时,会触发“停止生产线”的情况,开发团队称之为“构建失败”或“红色构建”。在错误修复之前,团队中其他人不应提交任何更改,以方便解决问题。触发失败的提交者应优先修复问题,如果更改不能迅速修复,则应在版本控制系统(VCS)中回滚,使CI作业能够再次运行并恢复到“绿色状态”。
2.4 构建监控
团队需要即时了解CI作业的状态,常见做法是在工作区域使用信息辐射器,以及在构建失败时弹出通知的桌面工具。电子邮件不太有效,因为容易被过滤和忽略。及时修复失败的构建对有效的持续集成至关重要,不要忽视失败的测试,也不要为了推进工作而禁用或注释掉失败的测试。同时,要消除不稳定的测试(即经常失败一次,再次运行时又通过的测试),因为这会使团队习惯忽略红色构建,导致合法问题不能及时被发现。
2.5 TDD与持续集成的关系
测试驱动开发(TDD)和自测试代码对持续集成至关重要。TDD和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(CI服务器拉取更改):::process
B --> C(运行测试):::process
C --> D{测试通过?}:::decision
D -->|是| E([继续开发]):::startend
D -->|否| F(标记为构建失败):::process
F --> G(停止提交更改):::process
F --> H(触发修复流程):::process
H --> I(修复错误):::process
I --> J(重新提交更改):::process
J --> B
3. 持续交付(CD)
3.1 持续交付的概念
持续交付在持续集成的基础上更进一步,确保每次提交时整个系统都能准备好投入生产。更改会应用到类似生产的环境中,使用与生产环境相同的工具和流程。持续进行此操作后,将更改部署到生产环境就变得轻而易举。
3.2 测试环境的要求
类似生产的环境并非与生产环境完全相同,重要的是模拟生产环境的关键方面,包括各种复杂情况。测试环境应像生产环境一样严格锁定,若测试环境允许生产环境不允许的权限,只会导致生产部署失败。若生产约束使测试环境难以快速频繁地进行更改,应找到在这些约束内实现快速频繁部署的解决方案。
3.3 手动审批与持续交付
手动审批可以融入持续交付过程,关键是确保人工参与仅限于审查和触发更改的应用,实际应用更改的过程应自动化,且在每个应用的环境中都完全相同。
3.4 持续交付与持续部署的区别
持续交付并不意味着每次提交的更改在通过自动化测试后立即应用到生产环境。大多数使用持续交付的组织并非采用持续部署的方式,持续交付的重点是确保每个更改都可以应用,使是否以及何时将更改应用到生产环境成为业务决策,而非技术决策。对于IT运维团队来说,持续交付意味着基础设施的更改在进行时会得到全面验证,用户所需的更改(如向生产环境添加新服务器)可以在无需IT运维团队参与的情况下进行。
4. 版本控制系统(VCS)用于基础设施管理
4.1 VCS中应管理的内容
基础设施团队应将构建和重建基础设施元素所需的一切纳入版本控制。理想情况下,若整个基础设施消失,仅保留版本控制的内容,应能通过检出所有内容并运行几个命令来重建一切,可能需要根据需要引入备份数据文件。需要进行版本控制的内容包括:
- 编译实用程序和应用程序的脚本和源代码
- 配置文件和模板
- 配置定义(如食谱、清单、剧本等)
- 测试
4.2 VCS中可能不需要管理的内容
- 软件工件应存储在存储库中(如Java工件的Maven存储库、apt或yum存储库等),这些存储库应备份或有脚本可重建。
- 由VCS中已有元素构建的软件和其他工件无需添加到VCS本身,因为可以从源代码重建。
- 应用程序管理的数据、日志文件等不属于VCS,应根据相关情况进行存储、存档和/或备份。
- 密码和其他安全机密不应存储在VCS中,应使用自动化基础设施中管理加密密钥和机密的工具。
4.3 分支与持续集成和持续交付的冲突
版本控制系统支持分支,允许人们同时处理同一代码的不同版本,这在软件开发中很常见,但对持续集成和持续交付存在问题。分支是后期集成的一种变体,会增加风险,因为人们甚至团队可能在同一代码上工作。合并分支时会出现合并冲突,需要手动决定接受哪些更改。现代VCS工具(如git)虽能更轻松处理合并的技术方面,但无法解决合并的逻辑问题。
4.4 常见分支策略
常见的分支策略如下表所示:
| 分支策略 | 描述 |
| ---- | ---- |
| 功能分支 | 当个人或小组开始对代码库进行更改时,可以创建一个分支,以便在隔离环境中工作,这样他们的正在进行的工作不太可能破坏生产中的某些内容。更改完成后,团队将该分支合并回主干。 |
| 发布分支 | 当新的版本部署到生产环境时,创建一个分支以反映生产中的当前版本。在发布分支上进行错误修复并合并到主干。下一个版本的工作在主干上进行。 |
| 基于主干的开发 | 所有工作都在主干上进行和提交。使用持续集成确保每次提交都经过全面测试,并使用持续交付管道确保更改在完全验证后才应用到生产环境。 |
发布分支适用于开发周期较长、发布不频繁的代码库,对通常进行持续小更改的基础设施团队不太适用。为重大基础设施更改创建分支可能看似有吸引力,但会累积风险,随着分支中的工作增多,部署所需的工作量也会增加,即使完成更改,发布也会成为一个大项目,带来大量工作和风险。
以下是不同分支策略的对比 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
C --> D(在分支上开发):::process
D --> E(完成开发):::process
E --> F(合并回主干):::process
B -->|发布分支| G(创建发布分支):::process
G --> H(在发布分支修复错误):::process
H --> I(合并到主干):::process
B -->|基于主干的开发| J(在主干开发):::process
J --> K(持续集成和交付):::process
5. 良好的编码实践:编写干净的代码
随着时间推移,基础设施代码库会增长,难以维护。近年来,“干净代码”和软件工艺受到更多关注,这对基础设施编码人员和软件开发人员同样重要。许多人认为实用主义(完成工作)和工程质量(正确构建事物)之间存在矛盾,但这是错误的二分法。
低质量的软件和基础设施难以维护和改进,快速拼凑可能存在问题的代码会导致系统不稳定,问题难以发现和修复,在混乱的代码系统中添加或改进功能也很困难。软件工艺意味着确保所构建的系统正常工作,不留隐患,构建其他专业人员能快速轻松理解的系统。编写干净的代码和注重软件工艺并非过度设计,重点不是为了满足对结构的强迫症而使事物有序,也不需要构建能处理所有未来可能场景或需求的系统。
6. 不同分支策略的详细分析与适用场景
6.1 功能分支策略
功能分支策略给予开发者一个相对独立的环境来开展新功能的开发工作。开发者在功能分支上可以自由进行代码的修改和测试,而不用担心对生产环境造成影响。
优点
:
- 隔离性强:开发者能够专注于自己负责的功能开发,不会受到其他开发任务的干扰。
- 减少冲突:由于工作在独立分支上,与主干代码的冲突相对较少。
缺点
:
- 合并成本高:当功能开发完成后,将分支合并回主干时,可能会遇到大量的合并冲突,需要花费时间和精力去解决。
- 延迟集成:在分支开发期间,代码没有及时与主干集成,可能会导致一些潜在的集成问题在合并时才被发现。
适用场景 :适用于开发周期较长、功能复杂的项目,或者多个开发者同时进行不同功能开发的情况。
6.2 发布分支策略
发布分支策略主要用于管理软件的发布版本。当新的版本部署到生产环境时,创建一个发布分支,用于处理该版本的错误修复。
优点
:
- 版本独立:可以在发布分支上对特定版本进行独立的维护和修复,不会影响到主干上的开发工作。
- 便于紧急修复:在生产环境出现问题时,可以快速在发布分支上进行修复,并将修复合并到主干。
缺点
:
- 管理复杂:需要同时维护多个分支,增加了分支管理的复杂性。
- 同步问题:发布分支和主干之间的同步可能会出现问题,导致代码不一致。
适用场景 :适用于开发周期较长、发布不频繁的项目,尤其是对生产环境稳定性要求较高的项目。
6.3 基于主干的开发策略
基于主干的开发策略强调所有开发工作都在主干上进行,通过持续集成和持续交付来确保代码的质量和可部署性。
优点
:
- 快速集成:代码能够及时集成到主干,减少了集成问题的发生。
- 简化管理:只需要管理一个主干分支,减少了分支管理的复杂性。
缺点
:
- 错误传播风险:如果在主干上引入了错误,可能会影响到整个开发团队。
- 对团队协作要求高:需要团队成员之间密切协作,及时发现和解决问题。
适用场景 :适用于开发周期短、发布频繁的项目,以及团队协作能力较强的情况。
以下是不同分支策略的对比表格:
| 分支策略 | 优点 | 缺点 | 适用场景 |
| ---- | ---- | ---- | ---- |
| 功能分支 | 隔离性强,减少冲突 | 合并成本高,延迟集成 | 开发周期长、功能复杂的项目 |
| 发布分支 | 版本独立,便于紧急修复 | 管理复杂,同步问题 | 开发周期长、发布不频繁的项目 |
| 基于主干的开发 | 快速集成,简化管理 | 错误传播风险,对团队协作要求高 | 开发周期短、发布频繁的项目 |
7. 持续集成和持续交付的实施步骤
7.1 环境搭建
- 选择合适的工具 :根据项目需求选择合适的持续集成工具(如Jenkins、GitLab CI/CD等)和持续交付工具(如Ansible、Terraform等)。
- 配置服务器 :搭建CI/CD服务器,并进行必要的配置,确保其能够正常运行。
- 集成版本控制系统 :将CI/CD服务器与版本控制系统(如Git)集成,以便实时获取代码变更。
7.2 定义流程
-
编写脚本
:编写自动化脚本,用于构建、测试和部署代码。脚本应包含以下步骤:
- 拉取代码:从版本控制系统中拉取最新的代码。
- 环境准备:安装必要的依赖和工具。
- 代码编译:将代码编译成可执行文件或库。
- 单元测试:执行单元测试,确保代码的基本功能正常。
- 集成测试:进行集成测试,验证不同模块之间的协作是否正常。
- 部署:将代码部署到测试环境或生产环境。
7.3 测试策略
- 单元测试 :在代码编写阶段进行,对每个函数或类进行独立测试,确保其功能正确。
- 集成测试 :在代码集成阶段进行,验证不同模块之间的协作是否正常。
- 端到端测试 :在部署到生产环境之前进行,模拟用户的实际操作,确保整个系统的功能正常。
7.4 监控与反馈
- 建立监控系统 :对CI/CD流程进行实时监控,及时发现和解决问题。
- 反馈机制 :建立有效的反馈机制,让开发者能够及时了解代码变更的结果。
以下是持续集成和持续交付的实施流程 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(CI服务器拉取代码):::process
B --> C(环境准备):::process
C --> D(代码编译):::process
D --> E(单元测试):::process
E --> F{单元测试通过?}:::decision
F -->|是| G(集成测试):::process
F -->|否| H(标记为失败):::process
H --> I(通知开发者):::process
G --> J{集成测试通过?}:::decision
J -->|是| K(端到端测试):::process
J -->|否| H
K --> L{端到端测试通过?}:::decision
L -->|是| M(部署到生产环境):::process
L -->|否| H
M --> N([完成部署]):::startend
8. 总结
在基础设施的软件工程实践中,持续集成(CI)、持续交付(CD)和版本控制系统(VCS)起着至关重要的作用。持续集成通过频繁集成和测试,能够及时发现和解决代码中的问题,提高代码质量;持续交付则进一步确保了系统在每次提交时都能准备好投入生产,减少了部署的风险;版本控制系统则为代码的管理和协作提供了有力的支持。
同时,良好的编码实践和合理的分支策略也是保证项目顺利进行的关键。编写干净的代码能够提高代码的可维护性和可读性,而选择合适的分支策略则能够根据项目的特点和需求,有效地管理代码的开发和发布。
通过遵循这些实践和策略,我们可以构建出更加稳定、可靠的基础设施,提高开发效率,降低运维成本。在实际应用中,我们应根据项目的具体情况,灵活运用这些方法,不断优化和改进我们的开发流程。
超级会员免费看
26

被折叠的 条评论
为什么被折叠?



