如何實現(xiàn)對 3000+ 軟件包的全鏈路自主研發(fā)與維護?
作者 | 趙振
Linux 發(fā)行版的自主維護工作一直面臨著巨大的挑戰(zhàn),軟件包規(guī)模巨大,涉及多個領(lǐng)域,要進行有效的自主維護,對人力、能力都有極高的要求。本文根據(jù)騰訊工程師、OpenCloudOS 社區(qū)技術(shù)專家趙振在 2024 年第十一屆開源操作系統(tǒng)年度會議(OS2ATC)上的分享整理,重點探討為打造全鏈路自研操作系統(tǒng),如何實現(xiàn)對 3000+ 大規(guī)模軟件包的全鏈路自主研發(fā)與自主維護。
一、整體介紹
一個 Linux 發(fā)行版標(biāo)準鏡像包含 3000 余個軟件包,具體涉及庫、工具、服務(wù)、語言運行時、圖形、音視頻等各個方面,再加上各種場景應(yīng)用,比如云原生、數(shù)據(jù)庫、AI 等,涉及軟件包數(shù)以萬計,如何維護如此大規(guī)模的軟件包,對團隊的人力、人員能力都是巨大的挑戰(zhàn)。
操作系統(tǒng)團隊在對軟件包分類、分層,按照領(lǐng)域分類、重要性等進行差異化維護之外,更構(gòu)建了一套全流程自動化的基礎(chǔ)設(shè)施和工具平臺,以提升維護效率和質(zhì)量,讓軟件包的維護者有更多的精力投入到重要包的掌握和能力建設(shè)中。
該工具平臺從上游跟蹤到代碼同步,各個流程環(huán)節(jié)盡可能自動化,主要包括以下 5 個部分及對應(yīng)的工具。
第一個工具 rpm-upgrade 用來跟蹤上游社區(qū)的發(fā)布情況,包括獲取新版本的 changelog 了解社區(qū)的動態(tài)。第二個工具 rpm-tracker 用來跟蹤重要包的 commits,扒取 bugfix、cve 相關(guān)的 patch。通過這兩個工具可以及時獲取上游最新的動態(tài)、修復(fù),按需同步到自主維護的版本,軟件包維護者就不用人肉跟蹤上游社區(qū);獲取到上游的更新、修復(fù)后,會嘗試自動提交 PR。
對于提交成功的PR,會通過第三個工具 rpm-check 進行變更識別和兼容性檢查,如果發(fā)現(xiàn)兼容性變化,會自動通過第四個工具 rpm-dep 來查找受影響的軟件包來進行重編、執(zhí)行受影響的包的用例,最后通過第五個工具 rpm-sync 來同步到其他分支。
以上是軟件包維護全過程,通過 CI 串聯(lián)起來實現(xiàn)全流程自動化。
二、具體實現(xiàn)
1.rpm-upgrade:上游新 Release 跟蹤查詢
問題:軟件包的上游社區(qū)形式多樣,有 Git、svn、hg 等不同的協(xié)議,github/gitlab、pypi、metacpan、Sourceforge 等不同接口,且軟件包最新的 Release 發(fā)布時間不定,有的發(fā)布頻繁,有的長期無新 Release,不同系列的軟件包選型的間隔也存在差異,如果無差異地對所有包執(zhí)行升級查詢,浪費資源和人力。
解決方案:團隊設(shè)計的 rpm-upgrade 工具,可通過多種查詢接口,覆蓋不同類型的上游平臺;通過綜合發(fā)布時間、頻率、選型時間、選型間隔內(nèi)的版本數(shù)等,多維度評估該版本是否需要自動升級。
效果:當(dāng)前主流平臺 Git/svn/pypi/perl 等都已覆蓋,3200+ 軟件包中的 98.5% 都能實現(xiàn)自動化查詢升級,基本不再需要人工跟蹤上游。
2.rpm-upgrade:上游新 Release 自動升級
問題:上游新 Release 查詢到后,符合條件的軟件包就可以進行自動升級。但在升級過程中,還存在多 source 源,tarball 需自行生成、上游未提供完整 tarball,補丁沖突等復(fù)雜情況導(dǎo)致自動化困難。
解決方案:工具先修改version、Release、chaneglog,再根據(jù)修改后的內(nèi)容獲取源碼包。如果是多源碼包情況,則根據(jù)宏解析自動下載;如果是 tarball 無法直接獲取等情況,則支持維護者自定義腳本處理。
對于升級過程中的補丁沖突,則根據(jù)補丁來源和補丁編號規(guī)則處理。上游補丁沖突時,視為上游已合入,移除該補丁并記錄; 發(fā)行版補丁沖突時,報警提示并記錄,由 maintainer 介入分析。
效果:升級軟件包平均節(jié)省 10 分鐘以上,并且自動解決補丁沖突可達 85% 以上,軟件包升級效率提升 80%+。
3.rpm-tracker:上游 commits 跟蹤扒取
問題:軟件分支、commit 信息多,傳統(tǒng) git clone 方式耗時長;主要關(guān)注 bugfix、cve,需要對 commtis 進行分類,人工費力、關(guān)鍵詞匹配方式不準確。
解決方案:rpm-tracker工具通過 Python 的 GraphQL API 爬取上游 commits 信息,支持 GitHub、GitLab 等主流平臺;并且選擇專門基于代碼訓(xùn)練的大模型,結(jié)合微調(diào),對 commits 進行分類,把 bug 修復(fù)、cve 修復(fù)等類型的 commits 識別出來,backport 到代碼中。爬取后自動提交 PR。
效果:當(dāng)前工具可以一次性查詢上百個軟件,模型在普通臺式機上無量化的情況下可以穩(wěn)定運行,輸出結(jié)果準確率 80% 以上,大大減少人工分析、回合的工作量。
4.rpm-check:兼容性檢查,軟件包變更的守門人
問題:上游新 Release、commits 扒取回合,提交 PR、完成編譯后,進行兼容性檢查。當(dāng)前業(yè)界已有的兼容性檢查開源工具主要是對 C/C++ 程序、Java 程序進行檢查,同時存在需要人工指定包以及庫、無法處理庫中部分特殊字符、無法判斷符號是否對外、結(jié)果可讀性差、速度較慢等情況。
解決方案:rpm-check 在 abicc 社區(qū)工具的基礎(chǔ)上解決了上述幾個問題,同時基于Python AST 模塊自研了 Python 兼容性檢查工具。
工具掃描目錄下所有 rpm 包及其 debuginfo 包、devel 包進行匹配成對后以篩選出需要檢查的 rpm 包,然后通過多進程方式對每對包進行處理,同時會在文件粒度上也進行并發(fā)操作,最大限度縮短檢查時間。
檢查項包括幾個方面:
- 子包列表:檢查子包是否有增刪
- rpm 的能力:(requires/provides/..),判斷是否有能力發(fā)生變化
- 文件列表:檢查重點位置的文件是否有增刪,同時排除無關(guān)信息(如版本號)以及無影響文件
- 動態(tài)庫的 ABI/API:根據(jù)代碼變化定位影響的結(jié)構(gòu)體、函數(shù)等
- 二進制可執(zhí)行程序比較:比較軟件包中存在的可執(zhí)行文件(工具、腳本等)的選項、參數(shù)是否發(fā)生變化
- 配置文件:支持多種配置文件格式,精確找到具體的變化項
多進程檢查結(jié)束后,會對檢查結(jié)果進行分析評估:比如 ABI/API 的變化,會先經(jīng)過內(nèi)外部符號判定,判斷該變化為內(nèi)部變化還是外部變化,然后經(jīng)過評估算法確定其影響等級,影響等級用來判斷該次變化的嚴重程度以及是否需要進一步判斷其影響范圍。
最后根據(jù)評估結(jié)果等級確認影響范圍,這一步通過在依賴包中進行符號搜索來完成,同時,搜索也會通過本地緩存進行加速。經(jīng)過以上檢查和精確查找,得到此次的變化影響的符號、軟件包以及受到影響的庫。
效果:支持 C、C++、Python、Java 等主流語言,特殊場景基本覆蓋;從符號粒度確認影響范圍,精確度 90% 以上;包及文件粒度并發(fā),本地緩存縮短檢查以及符號搜索耗時 50% 以上。
5.rpm-dep: 查詢包依賴與排序
問題:受兼容性變化影響的包,通過 rpm-dep 工具獲取。當(dāng)前 DNF 工具無法快速獲取發(fā)生變化的包所影響到的包,包括依賴當(dāng)前變化的包進行編譯的包,依賴當(dāng)前變化的包進行安裝的包,即反向依賴。
同時,獲取到反向依賴包列表后,列表中的包之間也存在層級關(guān)系,進行構(gòu)建時,需要先構(gòu)建底層的,后處理高層級的,這就需要排序。
解決方案:rpm-dep 工具初始化時,解析 repo 源的 repodata 文件,構(gòu)造出依賴關(guān)系表,將依賴關(guān)系表存入 Redis,提高查詢速度。
- 依賴查詢時,通過 key - value 查詢,以及 bfs 多層的檢索,即可獲取指定層數(shù)的依賴關(guān)系樹。
- 依賴排序時,首先建立包的依賴圖,對于存在循環(huán)依賴的情況,會統(tǒng)計循環(huán)鏈上的所有包的被引用情況,從被引用最少的節(jié)點拆開循環(huán)鏈條。
然后得到一個有向無環(huán)圖,接下來使用拓撲排序的思想,每一輪循環(huán)都取出無前向依賴的節(jié)點,即可對同層的 RPM 包排出優(yōu)先級。
效果:多種依賴場景秒級查詢多層依賴樹;包排序指導(dǎo)按依賴層級進行構(gòu)建。
6.自動 Release+1 重編受影響的包
需求:兼容性變化,需要準確無誤的重編受影響的軟件包,根據(jù)影響和風(fēng)險的不同,分為測試重編和正式重編,測試重編不 Release+1 提交 PR、只用來驗證變化會不會導(dǎo)致問題,如 API 頭文件變化,正式重編是指要 Release+1 提交 PR,如 soname 變化會導(dǎo)致找不到依賴,就要正式重編。測試重編、正式重編,都要保障編譯源依賴的是變化后的包,不能基于老包編譯。
解決:為了避免遺漏或者范圍過廣出現(xiàn)無效重編,我們根據(jù)兼容性變化的具體內(nèi)容和影響范圍,確定重編類型,如表格所示,然后使用 rpm-dep 工具找出受影響的依賴包。
- 對于測試重編,我們將 PR 編譯通過的軟件包結(jié)合當(dāng)前編譯源,制作成臨時編譯源;在這個臨時編譯源的基礎(chǔ)上,我們對受影響的包進行測試重編。
- 對于正式重編,創(chuàng)建 issue 進行跟蹤,按照依賴關(guān)系層級排序,自動依次發(fā)起正式重編 PR,上一層級的 PR 編譯成功、進入編譯源后,提交下一層級的 PR,直到所有需要 Release+1 重編的包都完成編譯。
效果:重編精準,無遺漏無冗余 Release+1;按依賴層級編譯、構(gòu)造編譯源,編譯依賴新包;人工只需確認,效率提升 100%。
7.精準全面測試+快速高質(zhì)量發(fā)布
需求:PR 編譯通過后,要對軟件包進行更新發(fā)布,既要保證軟件包更新的及時性,也要保證軟件包更新的質(zhì)量。
解決方案:我們通過消息機制保證編譯完成的軟件包能夠及時更新至測試平臺,立即獲取更新后的軟件包到測試 YUM 源,然后進行升級測試、安裝測試、服務(wù)啟停測試、功能測試,并根據(jù)前面提到的 rpm-dep 工具給出的受影響包列表執(zhí)行受影響包的測試,以此來做到精準測試,覆蓋全面,同時高效地得到質(zhì)量反饋。
此外,為了防止出錯軟件包阻塞其他通過測試的軟件包的正常發(fā)布流程,對于測試未通過的軟件包,會以單個軟件包的粒度回退,清理對應(yīng)軟件包及其重編包,并發(fā)起問題處理流程。然后繼續(xù)進行其他保證其他正常軟件包的及時發(fā)布。
效果:小時級軟件更新速度(代碼提交到更新發(fā)布);軟件升級沖突、功能等問題有效攔截。
8.rpm-sync:分支間高效同步
PR 合入后,后臺會根據(jù) PR 填入的 commit 模板信息,進行分類。識別到是 bugfix, 安全修復(fù),后臺會自動調(diào)整相關(guān) commit,向下游分支發(fā)起同步。如果同步失敗,會自動創(chuàng)建工單通知相關(guān)人員對同步的 PR 進行分析調(diào)整。
效果:多分支及時保持同步;流程自動化,形成完備的錯誤處理機制;減少人工同步、多分支維護的工作量。
9.整體數(shù)據(jù)流設(shè)計
問題:通過前面的介紹,可以看到,整個自動化流程涉及了上游源碼社區(qū)的更新監(jiān)控,代碼托管平臺的管理、下游編譯平臺、分發(fā)平臺以及測試平臺多個流程的不同組件,平臺、流程、環(huán)節(jié)多,交互復(fù)雜,包括事件通知、狀態(tài)等待、結(jié)果回傳等。
解決方案:基于這個問題,我們設(shè)計了一整套消息機制,通過消息隊列解耦不同平臺的數(shù)據(jù)依賴問題,并通過 CI 平臺的流水線驅(qū)動不同任務(wù)運行。
比如,當(dāng)我們監(jiān)聽到上游社區(qū)更新了新版本,這個消息會寫入消息隊列,等待對應(yīng)的代碼同步流水線處理更新。再將同步完成的消息寫入消息隊列,等待后續(xù)的編譯、測試、同步流水線的批次處理。
這套消息處理機制,解耦了不同流程間的依賴,僅通過統(tǒng)一的消息來完成整個流程的執(zhí)行。并且因為消息保存在消息隊列中,下游流程不依賴上游數(shù)據(jù)的實時更新,對于執(zhí)行失敗的下游任務(wù),我們可以重新從隊列中取得對應(yīng)的消息,然后從執(zhí)行失敗點繼續(xù)完成后續(xù)工作。
此外,消息隊列可以驅(qū)動流程運行,但它沒有持久存儲的能力,沒法記錄并追蹤某個更新的軟件當(dāng)前狀態(tài)(除非我們遍歷消息隊列),因此,我們通過數(shù)據(jù)庫來記錄某個軟件包當(dāng)前處在哪個流程中,來保證每個軟件包的可追蹤性。
效果:提升平臺開發(fā)和運行效率 50%+。
三、未來展望
以上通過自動化基礎(chǔ)設(shè)施、工具平臺的加持,軟件包自主維護效率得到了成倍的提升,質(zhì)量也得到一定保障,不過距離自主維護能力的成熟還有較大的差距。當(dāng)前部分重要包已經(jīng)掌握了代碼架構(gòu)、重要功能的實現(xiàn),但還有較多的軟件包需要逐步積累維護能力;自主可控版本的軟硬件生態(tài)與業(yè)界標(biāo)桿也有差距,隨著軟硬件生態(tài)的推進,版本的兼容、穩(wěn)定也會面臨一些挑戰(zhàn)。
項目在 systemd、glibc、gcc 等關(guān)鍵用戶態(tài)軟件包社區(qū)已經(jīng)有一些貢獻,獲得了 numactl 等社區(qū)的 maintainer,不過上游社區(qū)參與度遠遠不夠,聲量及影響力仍然很弱。
當(dāng)前 AI、異構(gòu)等新場景不斷涌現(xiàn),開發(fā)者、管理員、企業(yè)等需求日新月異,傳統(tǒng)的 OS 也需要與時俱進、不斷創(chuàng)新,才能保持生命力。千里之行始于足下,自主維護能力建設(shè)已經(jīng)邁出了堅實的一步,剩下的就是腳踏實地,一步一個腳印,把自主可控做實做深。