3. CI/CD概念入门:持续集成(CI)是什么、持续交付(CD)是什么、CI/CD流水线的基本构成
好,咱们进入第三个话题。CI/CD,这个词在嵌入式圈子里,这几年越来越热。但说实话,我见过不少团队,嘴上说着上CI/CD,实际上就是把代码扔到一个共享文件夹里,然后手动编译。嗯,这不能叫CI/CD,顶多算个“手动集成”。
今天我就把这两个概念掰开揉碎了讲清楚。CI是什么?CD又是什么?它们俩到底有啥区别?一条正经的CI/CD流水线,又该长什么样?
3.1 持续集成(CI)——别让代码“烂”在本地
持续集成,英文叫Continuous Integration。说白了,就是让开发人员频繁地把代码合并到主干。多频繁?我个人习惯是每天至少一次。你想想看,如果你写了一个星期的代码,然后一次性往主干上合,那冲突得有多恐怖?
我在项目中遇到过最惨的一次,一个同事闷头写了两个月的驱动代码,合并的时候发现和底层的硬件抽象层完全对不上。最后整个团队花了一周时间帮他重构。从那以后,我就定了个规矩:代码在本地停留不能超过24小时。
CI的核心,其实就三件事:
- 自动构建:代码一提交,服务器自动拉下来编译。编译不过?立刻报警。
- 自动测试:编译通过后,跑单元测试、静态检查。我见过很多嵌入式项目,编译过了就觉得万事大吉,结果一跑测试全是红的。
- 快速反馈:整个流程必须在15分钟内完成。超过这个时间,开发者的注意力就散了。
CI的核心价值:尽早发现集成问题。问题发现得越早,修复成本越低。这个道理,做过嵌入式的人都懂——硬件板子就那么几块,排期排得死死的,等板子到手才发现代码有问题,那叫一个欲哭无泪。
3.2 持续交付(CD)——让发布变成“家常便饭”
持续交付,Continuous Delivery。很多人把CD和持续部署搞混,我简单说一下区别。
持续交付的意思是:你的代码任何时候都处于可发布状态。但要不要真的发布,由人来决定。而持续部署是:代码一通过测试,自动部署到生产环境。
在嵌入式领域,我强烈建议先做到持续交付。为什么?因为嵌入式产品的发布流程太复杂了。你要考虑OTA升级策略、要考虑硬件版本兼容、要考虑法规认证。这些不是自动化工具能完全搞定的。
我曾经在一个车载项目里,把持续交付做到了极致。每天凌晨,流水线自动构建出固件镜像,然后跑完所有的回归测试。早上我来上班,第一件事就是看测试报告。如果全是绿的,我就知道昨天的代码没问题,可以随时发版。这种感觉,真的很踏实。
持续交付要解决的核心问题,其实是“发布恐惧”。你想想看,如果一个项目半年才发一次版,那发布那天肯定全员紧张,通宵熬夜是常事。但如果每周都能发一次,甚至每天都能发,那发布就变成了一个很平常的操作。
我的建议:嵌入式项目可以先从“持续交付”做起。先保证代码随时可发布,至于要不要自动部署到生产环境,看你们的产品形态和风险承受能力。
3.3 CI/CD流水线的基本构成
好,概念讲完了。咱们来看看一条完整的CI/CD流水线,到底长什么样。我把它分成几个阶段,每个阶段都有明确的输入和输出。
| 阶段 | 输入 | 输出 | 典型工具 |
|---|---|---|---|
| 代码提交 | 开发者本地代码 | 远程仓库的commit | Git、SVN |
| 静态检查 | 源代码 | 代码规范报告 | PC-Lint、Cppcheck |
| 编译构建 | 源代码+依赖库 | 可执行文件/固件 | GCC、ARMCC、CMake |
| 单元测试 | 可执行文件 | 测试报告 | Ceedling、Unity |
| 集成测试 | 固件镜像 | 功能验证报告 | Jenkins、自定义脚本 |
| 制品归档 | 固件镜像 | 版本化制品 | Nexus、Artifactory |
| 部署发布 | 制品 | 生产环境/测试环境 | Ansible、自定义OTA |
这张表,基本覆盖了一条嵌入式CI/CD流水线的所有环节。我重点说几个容易踩坑的地方。
3.4 嵌入式CI/CD的特殊之处
做嵌入式CI/CD,和做Web的CI/CD,完全是两码事。Web那边,代码编译完就能跑,环境都是标准化的。嵌入式这边呢?你编译出来的东西,得烧到板子上才能验证。而板子,就那么几块,还经常被不同的人抢着用。
我曾经在一个物联网项目里,为了解决板子不够用的问题,搞了一套“硬件池”的方案。简单说,就是把所有的开发板都连到服务器上,通过USB Hub控制电源。流水线跑的时候,自动给板子上电、烧录、跑测试、然后下电。这样一块板子一天能跑几十次测试,利用率大大提高。
另外,嵌入式项目的编译时间通常很长。一个大型的MCU项目,全量编译可能要半个小时甚至更久。这时候,增量编译和缓存技术就特别重要。我建议在流水线里加上ccache,能把重复编译的时间缩短80%以上。
注意:嵌入式CI/CD最忌讳的就是“全量重编”。每次提交都从头编译一遍,不仅浪费时间,还浪费服务器资源。一定要做好增量编译和依赖管理。
3.5 一个简单的CI/CD流水线示例
说了这么多,咱们看个实际的例子。这是一个基于Jenkins的嵌入式CI/CD流水线,针对STM32平台。
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Static Analysis') {
steps {
sh 'cppcheck --enable=all --suppress=missingIncludeSystem .'
}
}
stage('Build') {
steps {
sh 'mkdir -p build && cd build && cmake .. && make -j4'
}
}
stage('Unit Test') {
steps {
sh 'cd build && ctest --output-on-failure'
}
}
stage('Firmware Packaging') {
steps {
sh 'cp build/firmware.bin artifacts/'
sh 'md5sum build/firmware.bin > artifacts/firmware.md5'
}
}
stage('Deploy to Test Board') {
steps {
sh 'python3 flash_board.py --port /dev/ttyUSB0 --firmware build/firmware.bin'
sh 'python3 run_smoke_test.py --port /dev/ttyUSB0'
}
}
}
post {
success {
archiveArtifacts artifacts: 'artifacts/*.bin'
}
failure {
email to: 'team@example.com', subject: 'Build Failed'
}
}
}
这个流水线看起来简单,但已经覆盖了CI/CD的核心环节。从代码检出,到静态检查,到编译,到测试,再到部署到测试板。每一步都有明确的产出物。
我个人建议,刚开始做CI/CD的团队,不要追求大而全。先把这个基础流水线跑通,然后再逐步加东西。比如加个代码覆盖率检查,加个性能测试,加个OTA升级验证。一步一步来,稳扎稳打。
总结一下:CI/CD不是银弹,但它能帮你把嵌入式开发中的很多“脏活累活”自动化。持续集成解决的是“代码能不能合”的问题,持续交付解决的是“代码能不能发”的问题。把这两件事做好了,你的开发效率至少提升50%。
下一章,我会讲怎么在嵌入式项目里搭建第一条CI/CD流水线。从工具选型到环境配置,一步步带着你走。到时候咱们再细聊。