说到服务器带外管理,圈内人第一反应肯定是BMC。但真正在国产整机厂商里干过固件交付的人,才知道这件事有多磨人:BMC固件开发向来是“手搓编译、人工验证、靠文件夹命名管版本”的重灾区。代码冲突靠喊,测试靠串口一根线,产线烧录靠运气,出了问题想回溯到具体代码提交,基本等于大海捞针。这篇就是记录我们团队如何基于openUBMC社区,把开发流水线真正落地到长江计算的服务器产品交付中的过程。不是宣传稿,是把选型逻辑、工程细节、踩过的坑都摊开来讲。
如果你是做嵌入式固件、服务器BMC、或者任何“硬件产品软件化交付”相关工作的,这篇文章应该能给你一些可以直接抄作业的思路。尤其是那些准备从“作坊式开发”转向“工程化交付”的团队,里面很多决策背后的为什么,比流水线本身更重要。
1. 项目背景:openUBMC能解决什么,长江计算为什么选它
1.1 从OpenBMC到openUBMC:社区定位的差别
先说一个很多非内核玩家容易混淆的点:OpenBMC和openUBMC不是同一个东西。OpenBMC是Facebook开源出来的,面向超大规模数据中心,上游代码质量高,但拿来给国内服务器厂商直接用,还有一道不小的坎。
这道坎在哪?适配。国产服务器主板上那一套东西,从BMC管理芯片、传感器拓扑、FRU信息格式,到厂商私有的IPMI命令、Web界面中文化、乃至客户指定的带外管理规范,OpenBMC上游并不会替你做好。你拿上游代码,面对的是一堆“通用参考实现”,真正落地到自己的板卡,改动量以万行为单位。
openUBMC的定位恰恰就在这:它基于OpenBMC的底层框架,把国产服务器生态常用的适配工作——比如常见BMC芯片适配、鲲鹏等国产处理器平台的带外管理需求、Web UI的中文本地化、国内客户习惯的管理交互逻辑——沉淀成社区能力。长江计算作为整机厂商,加入openUBMC社区,不是简单拉代码下来闭门造车,而是把自己的产品需求直接做到社区里,让共性的东西被社区维护,个性的东西自己保留。
这个差别决定了后面所有工程化改造的方向:我们不是在维护一个私人fork,而是在一个社区底座上,构建自己的产品线交付体系。
1.2 固件开发最大的痛:交付靠手艺,质量靠运气
在把流水线搭起来之前,我们内部开发BMC固件的状态,用四个字形容就是“不堪回首”。代码在各自开发者本地仓库里,编译靠命令行,改了什么没人同步,版本管理靠文件夹命名“V1.0_final_真的final_2”,多人同时改同一个文件,合入靠微信群喊一声。
最要命的是交付环节。产线每块主板都要烧录BMC固件,烧进去能不能正常启动,很大程度上看烧录工装的稳定性和运气。一旦固件出厂之后才发现严重问题,要么批量召回,要么远程带外升级——而远程升级本身也有变砖风险,只是把损失从“返厂维修”变成“现场翻车”。
长江计算为什么愿意投入做openUBMC社区开发流水线?道理很简单:高质量交付不是口号,是止损。BMC固件质量每提升一个量级,产线返工、售后维修、客户投诉的成本就跟着降一个量级。所以这事从一开始就不是“技术情怀”,而是一笔算得过来的商业账。
1.3 架构选型:为什么我们用Jenkins而不是GitLab CI
先回答一个大家都会问的问题:CI引擎为什么选Jenkins?我们内部讨论过三条路。GitLab CI当时团队用得少,权限控制粒度不够细,私有化部署的运维成本也不低;自研一条流水线引擎,看着灵活,实际上要投入的人力够养一个开发小组;Jenkins虽然老,但团队最熟,插件生态完备,从编译、测试到发布归档,能找到大量现成方案。
选型逻辑就一条:流水线不是花架子,要让研发、测试、生产三个环节的人都愿意用、用得起。所以我们没有一上来就铺一个大而全的平台,而是先建一条主干——编译、测试、归档、发布——把核心链路走通,其余能力按需慢慢扩展。
2. 开发流水线核心架构:从拉代码到产线镜像全链路
2.1 多仓协同:manifest管理比单仓提交强在哪
openUBMC的代码结构不是一个大仓库,而是拆成十几个子仓:meta配置层、BMC守护进程、phosphor相关组件、Web UI、工具链等等。这种多仓结构对社区协作友好,但对企业内部版本管理就是个灾难——你没法用一个简单的git tag去标记“这一版软件”的完整状态。
我们用Google的repo工具做多仓版本管理。核心思路是维护一个manifest仓,里面记录每个子仓在某一时刻应该处于哪个commit。一个产品版本,就对应manifest里的一个快照。发布打tag,也是打在manifest仓上。这样研发、测试、产线拿到的“同一版本”,在代码层面就完全一致,不会出现“你测的是我的代码吗”这种经典扯皮。
这里有个实操细节:manifest的分支策略要清晰。我们main分支面向openUBMC社区,开放社区合入;release分支面向长江计算产品版本,只允许经过评审的修复合入;发布前再打一个带版本号的tag,例如v2.3.0-rc1。这样不管社区怎么演进,我们要做产品时随时可以基于某个稳定点拉出新分支。
2.2 BitBake构建与缓存策略:把90分钟的编译压到15分钟
openUBMC的构建体系基于Yocto/OpenEmbedded,核心工具是BitBake。构建流程简单说就是:BitBake根据recipe文件解析依赖关系,下载源码、应用patch、交叉编译、打包成镜像。这个体系自动化程度高,但第一次全量编译非常久,我们一台16核的构建机,冷启动全量构建要接近2小时。
2小时意味着什么?如果每次合入代码都要全量构建验证,PR门禁根本跑不动。我们把sstate cache(共享状态缓存)配到了构建集群的共享存储上。有了sstate cache,增量构建基本能压到15分钟左右——只编译改动涉及的组件,其余直接拉缓存。
但sstate cache是双刃剑。具体怎么踩的坑,我在第4章会细说。这里先给一个原则:共享缓存一定要配合定期的“干净构建”来做验证,否则缓存污染会让所有构建结果变得不可信。
构建产物也要仔细设计。除了烧录用的image文件,我们还强制保留:符号表、debug包、IPK包、以及SBOM(软件物料清单)。符号表和debug包是后来线上问题排查的救命稻草;而SBOM现在已经是供应链合规的硬性要求,出了安全漏洞能快速定位哪些镜像受影响。
2.3 板级自动化测试平台:一台机柜管八块板卡
光有编译流水线,顶多算“持续构建”,离“高质量交付”还差一大截。BMC终究是跑在硬件上的软件,编译过了不代表能启动,能启动不代表功能正常。所以板级自动化测试平台是整条流水线里投入最大、也是价值最明显的一块。
我们搭的测试平台其实不复杂:一个标准机柜,塞进去8块被测主板(都是长江计算的服务器主板)。每块板卡的串口接入带网络功能的串口服务器(console server),这样脚本可以通过TCP/IP连接任意一块板卡的串口;每块板卡的上电控制接在继电器PDU上,脚本可以远程断电、上电。整个机柜通过一台测试服务器统一调度。
自动化测试的流程是:脚本从流水线拿到新固件 → 通过串口/网口把固件烧录到某块板卡 → 上电 → 观察串口日志判断启动状态 → 通过IPMI或Redfish接口执行功能测试 → 收集结果 → 断电,换下一块板卡。
这个平台跑起来的价值立竿见影:以前人工验证一块主板从头到尾要1小时,现在8块板卡并行跑,十几分钟能出结果,而且每块板卡的串口日志、测试用例执行记录、失败时的REST API响应全部自动归档。出问题不再是“我这边测不出来啊”,而是直接把当时的现场数据甩出来。
2.4 发布归档与镜像溯源:每个固件都能追到commit
发布流程的工程化,是容易被忽视但其实极其重要的一环。以前产线用的烧录文件可能是某个工程师U盘里的“最终版”,完全没有追溯性。现在我们在流水线里把发布归档做成强制环节。
镜像命名有严格规范:openubmc-<产品型号>- -<构建编号>,例如openubmc-r2288-v2.3.0-rc1-87。每个镜像旁边生成一个SHA256校验文件。产线烧录工具下载固件时,第一步就是校验哈希,校验不过直接拒绝烧录。
到了发布阶段,候选镜像必须通过全部P0级冒烟测试,才能签入发布区。发布区里的镜像带有签名,产线工具在烧录前验签,防止镜像被非法替换。变更单(Release Notes)也是自动生成的,从PR标题和commit message提取变更点,没有人工再去翻代码历史这件事了。
这套机制最大的收益,是让“哪个固件出了问题”变成一个可以精确回答的问题。任何一个现场故障,只要拿到固件版本号,就能反查到代码commit、对应的测试报告、覆盖了哪些用例、漏了哪些用例。这是以前想都不敢想的。
3. 高质量交付的四个关键工程细节
3.1 PR门禁:把垃圾提交挡在合入之前
流水线跑起来之后,第一个要解决的问题是代码合入质量。以前团队里的习惯是,“先合了再说,有问题后面改”,这在社区协作模式下行不通——openUBMC社区本身对代码质量要求很高,每次提交要有Change-Id、要有Signed-off-by、要过CI检查、要有人Review。我们把这套规则固化到了流水线里。
PR门禁分三层。第一层是机器检查:格式检查(clang-format是否符合规范)、license头检查(开源合规要求)、静态检查(cppcheck和scan-build抓明显的代码缺陷)。第二层是编译冒烟:针对目标架构做一次增量构建,确保代码能编译通过。第三层是人工评审:至少两位维护者approve才能合入,而且作者不能approve自己的PR。
这三层门禁挡住了很多低级错误。以前常见的“编译挂了自己都没发现”的问题基本绝迹;代码风格统一之后,Review的讨论焦点从“这行缩进怎么不对”变成了“这个逻辑是否合理”。另外,commit message的规范也立了规矩:summary一行,body说明动机和影响,结尾必须有Change-Id和Signed-off-by。用社区的标准约束内部开发,一开始会有点痛苦,但坚持两个月后,团队的代码素养明显上了一个台阶。
3.2 测试用例分级:P0冒烟、P1功能、P2压力的取舍
板级测试用例怎么设计,直接决定了流水线的效率和质量的平衡。我们没有试图把测试用例做成一个大而全的集合,而是分成三级,各司其职。
P0级是冒烟用例,每个镜像发布前必须全部通过。内容不多但覆盖面足够判断“这块板子是否活着”:上电能否正常启动、串口是否可达、IPMI命令能否响应、基本传感器能否读到、FRU信息能否正确读回。P0全跑一次不超过10分钟。
P1级是功能用例,覆盖BMC的核心管理功能:用户管理、网络配置、固件升级、日志管理、风扇控制、电源管理、以及长江计算产品的私有管理命令。这些用例不需要每次合入都跑,但每个夜间都会全量跑一遍,第二天上班看报告。
P2级是压力与异常用例:断电恢复、频繁软复位、连续升级回滚、Flash写保护、长时间运行内存泄漏等。这些用例每周跑一次,有些需要数十小时,不追求频率,追求的是深度。
分级设计的核心逻辑是时间预算与测试覆盖的平衡。如果每次PR合入都跑全量P0+P1,等待时间会拖垮开发节奏;如果只有夜间跑P1,问题反馈又会滞后半天。让不同级别的用例跑在正确的频率上,这是测试工程化最值得花心思的地方。
3.3 产线烧录与研发验证的差异:出厂镜像要的是确定性
研发环境测试和产线烧录场景,完全是两回事。研发环境有KVM、有串口调试线、有网口、有各种工装,出问题可以随时介入调试。产线是什么?一个烧录工装,一个操作工人,流水线上几十块主板排队等着烧录。工人不会看串口日志,也不会执行调试命令,他们要的是:烧进去,绿灯亮,走人。
所以产线用的出厂镜像,设计原则和研发测试镜像不一样。最重要的一条是“确定性”:默认配置要固定,不能依赖DHCP环境变量;串口波特率固定;关闭一切可能因现场环境差异导致行为不一致的功能;烧录完成必须自动做读回校验,确保flash里的内容和镜像文件完全一致。
还有升级安全性设计。产线烧录最怕的是烧到一半断电,一旦出现这种情况,主板直接变砖。我们把BMC flash设计成双bank结构,烧录时先写备用bank,校验通过后再切换主bank,即使中途断电,原固件还在,还能重新烧录。这个设计后来证明是极有价值的——产线的意外断电是无法完全避免的,只能从架构上兜底。
3.4 质量数据闭环:用失败驱动流水线迭代
流水线跑起来之后,会积累大量测试数据。如果这些数据只是躺在数据库里当摆设,那自动化就失去了一半意义。我们做了很朴素的质量数据闭环:每晚全量P1测试结果入数据库,生成趋势图表;每周回顾失败用例,分析根因。
这个过程会暴露很多有意思的问题。比如某个IPMI命令经常偶发超时,不是功能挂了,而是网络栈在某种情况下重试策略不合理;某个传感器阈值配置在低温环境下误报,需要在recipe里调整默认配置。这些问题单靠人工测试很难发现——人工测试很难做到同一个用例跑几百遍去统计失败率,但自动化可以。
更关键的是从bug倒推流水线改进。生产环境发现一个严重bug,第一件事不是修代码,而是反问:这个bug为什么在流水线的测试用例集里没有被覆盖到?是测试用例缺失,还是执行频率不够?然后针对性地补用例、调策略。这样循环几个月,测试集的质量会越来越好,流水线本身也在持续进化。让流水线从“质检员”变成“质量驱动力”,这是我们认为最值得分享的经验。
4. 实测中踩过的坑与排查实录
4.1 sstate cache污染:构建成功但固件启动不了
这个坑是我们在流水线稳定运行一个月后遇到的。那段时间构建一直显示成功,但烧录到板卡上,BMC启动不起来,串口日志卡在u-boot阶段就不再动了。单独看构建日志、测试报告,全都没问题,这就让人很困惑。
排查过程大概花了两天。先怀疑bootloader参数,检查recipe发现配置是对的;再怀疑烧录工具出问题,换了工装重烧,现象依旧;最后用bitbake -e命令查构建时的实际变量,才发现某个配置项和当前代码不一致——这个不一致不是源码里写出来的,而是sstate cache里缓存下来的一份旧内容。
问题的根因是:某个recipe在之前的构建中缓存了一份“污染状态”——变量内容与当前代码不匹配,但缓存hash没有变,后续所有构建都直接拉缓存,把这个错误固化了下来。解决了这个问题后,我们做三件事:清理对应的sstate环境、调整共享缓存的挂载策略(不同release分支分开)、增加每天一次的干净构建作为校验基准。
这个教训让我对共享缓存有了敬畏心。缓存是提高构建效率的法宝,但它会让“成功”变得很廉价——消耗的不是编译时间,而是你对构建结果的信任。信任一旦崩塌,再快的构建也没有意义。
4.2 板卡并发测试的串口干扰
板级自动化平台跑起来之后,遇到一个很烦的问题:8块板卡同时测试时,偶尔有一两块板卡的串口输出变成乱码,或者连接直接断开。而且每次出问题的板卡不固定,重启串口服务后又能恢复,没有明确规律。
最开始以为是串口服务器硬件故障,换了一台新的,问题依旧。然后用示波器量串口波形,单独测试完全正常,但只要多块板卡并行上电,波形就开始出现毛刺。最后定位到根因:多块板卡的串口GND和调试服务器的GND之间存在共地环路,导致信号地电位不稳定。
解决方法是加装隔离型串口模块,把每块板卡的串口信号和测试服务器的GND做电气隔离,同时调整继电器PDU的上电顺序,避免多块板卡同时上电造成瞬间电流冲击。还有一个细节是测试脚本里每条串口命令之间加了几百毫秒的延时,让信号稳定后再读数据。
这件事给我最大的触动是:硬件自动化的坑,往往不在软件逻辑里,而在最不起眼的物理层。地线、电平平移、时序毛刺,随便哪一项都能让你的测试脚本在逻辑上完全正确、在物理上就是不通。做板级测试自动化,光懂软件是不够的,必须有一点硬件工程师的敏感度。
4.3 产线烧录成功率血泪教训
产线那边反馈烧录成功率不高,一整批主板里有将近7%烧录失败。烧录工具是我们自研的上位机脚本加普通USB转串口线。失败点主要出现在固件传输阶段,表现为中途断线或者校验不一致。
排查过程从软件到硬件层层剥。先看脚本逻辑:超时时间、重试机制、校验算法,都没有明显问题。然后换了不同批次的USB转串口线,发现失败率和线材质量高度相关——便宜线材的引脚焊接不牢固,批量生产时质量参差不齐。再看波特率设置,之前用460800的高波特率,虽然理论值没问题,但线材信号质量不够稳定,传输中途丢数据。
解决的组合拳是:换用工业级USB转串口线(带磁环,屏蔽层加固)、波特率降到115200、开启RTS/CTS硬件流控、传输完成后强制读回校验、校验失败自动重试最多3次。这样一套改造下来,烧录成功率从93%提到了99.8%左右。
这个坑现在说起来好像很简单,但当时在产线上排查了两天。教训就一条:产线的工装设备,永远不要省小钱。省掉那几块钱的线材差价,最后都会变成几倍的返工成本还给你。工装不单单是工具,它是产线质量体系的一部分。
4.4 常见问题速查表
把运维流水线过程中遇到的典型问题整理成一个速查表,给后来的人排障时参考:
| 现象 | 可能根因 | 解决方法 | 预防措施 |
|---|---|---|---|
| 构建成功但BMC启动失败 | sstate cache污染 | 清理对应sstate,用干净构建验证 | 定期干净构建,缓存跨分支隔离 |
| 串口输出乱码/连接断开 | 多板卡串口共地干扰 | 加隔离型串口模块,调整上电时序 | 物理层设计时就考虑隔离 |
| 产线烧录失败率高 | USB转串口线材质量差、波特率过高 | 换工业级线材,降波特率,开流控 | 工装设备标准化,进厂验收测试 |
| PR门禁偶发超时 | 多任务并行构建抢CPU资源 | 限制并发数,预留CI节点资源 | 构建资源配额管理 |
| Web UI构建产物不一致 | 前端代码缓存未清理 | 清理node_modules重新构建 | 前端构建独立job,缓存策略单列 |
| 测试用例偶发失败 | 板卡状态残留未清理 | 测试前强制下电复位,清空环境 | 测试脚本规范:Teardown必执行清环境 |
5. 落地半年后的真实体会:流水线如何改变交付质量
5.1 几个可以量化的改善指标
流水线从搭建到稳定运行半年后,我们内部复盘过一组数据。交付周期上,以前出一个可测试的固件版本大概需要两周,现在每天都能出候选版本,需求从提出到验证的周期压缩到3天左右。缺陷率上,出厂后发现的BMC固件严重缺陷数量,对比改造前有明显下降——虽然没有公布精确数字的必要,但团队内部感知是“从隔三差五救火变成了偶尔需要处理”。测试覆盖上,自动化板级测试用例从0增加到几百个,每晚全量跑一遍,以前人工测试需要两三天,现在第二天早上就能看到带失败用例详情的完整报告。
最让我满意的是可追溯性。现在任何一个固件问题,只要报出版本号,我们能在一分钟内找到对应的manifest快照、代码commit、测试报告、构建日志。这在以前是不可想象的,而它带来的直接价值是排障效率的倍数级提升——工程师不再花两三天去“回忆”自己改过什么,而是直接看数据和代码。
5.2 给同样在做固件工程化的团队几点建议
复盘下来,如果让我给正在做类似改造的团队提建议,我会说几条写代码之外的事。
第一,先跑通主干再扩展能力。不要一上来就搞复杂的矩阵构建、多平台并行、几十种镜像组合。先把“一次代码提交到产线烧录文件”这条最短路径走通,让所有人感受到这套系统的价值,后面自然有人推动扩展。
第二,测试自动化比编译自动化难十倍,但价值也高十倍。编译流水线是机械劳动,板级自动化测试才是质量防线。尽早投入硬件测试平台的建设,哪怕先从一个机柜、四块板卡开始。
第三,社区协作和内部开发要统一标准。openUBMC社区的评审文化、commit规范、编码风格,一开始内部团队会抵触,但坚持住,这些标准会成为团队技术债务的防火墙。用社区的标准要求自己,不是为了“开源形象”,而是为了长期可维护性。
第四,也是最容易被忽视的:流水线不是工具,是流程、人和工具的组合。再好的流水线,如果开发人员不遵守PR规范、测试人员不维护用例、产线工人不按标准操作,都是白搭。工程化改造的本质是改变人的习惯,这比写代码难,但也比写代码值钱。
最后说一个我个人的体会。做固件工程化这件事,最难的校验不是技术上把流水线搭起来,而是让团队真正相信“流程的价值”。当有工程师因为流水线的门禁而避免了生产事故,当他主动修复一个测试用例来防止同类bug再次出现,那一刻你就知道,这套东西真正落地了。openUBMC社区也好,Jenkins流水线也好,它们都只是载体。真正让交付质量发生质变的,是团队对“高质量交付”这件事本身的信念。
如果你也在做类似的事,希望这篇流水账能给你一些参考,少踩几个我们已经踩过的坑。