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流水线。从工具选型到环境配置,一步步带着你走。到时候咱们再细聊。