當前位置: 妍妍網 > 碼農

豬八戒網 CI/CD 實踐總結

2024-02-23碼農

文章來源:八戒技術團隊

本文旨在介紹ZBJ DevOps團隊傾力打造的DevOps平台中關於CI/CD流水線部份的實踐。歷經三次大版本叠代更新的流水線,完美切合ZBJ各種業務發展需求,在滿足高頻率交付的同時,提高了研發效率,降低了研發成本,保證了交付品質。


持續整合(Continuous Integration)簡稱CI,持續整合強調開發人員送出了新程式碼之後,立刻進行構建、(單元)測試。根據結果,我們可以確定新程式碼和原有程式碼能否正確地整合在一起。持續整合過程中很重視自動化測試驗證結果,對可能出現的一些問題進行預警,以保障最終整合的程式碼沒有問題。持續交付(Continuous Delivery)簡稱CD,持續交付在持續整合的基礎上,將整合後的程式碼部署到更貼近真實執行環境的「類生產環境」(test,testing)中,然後交付給品質團隊,以供評審。如果評審透過,程式碼就進入生產階段。持續交付並不是指軟體每一個改動都要盡快部署到產品環境中,它指的是任何的程式碼修改都可以在任何時候實施部署。有的人也把CD稱為Continuous Deployment(持續部署),持續部署是指當交付的程式碼透過評審之後,可以部署到生產環境中。這裏需要註意的是,持續部署應該是持續交付的最高階段,持續交付是一種能力,持續部署是一種持續交付的表現方式。


CI/CD過程示意圖

背景介紹


在提到ZBJ DevOps流水線之前,先交代一下歷史背景。2015年前,豬八戒網80%的計畫都是用PHP語言開發的,剩下的少部份使用的是Nodejs和Java。2015年,ZBJ研發中心進行了自發性的「工業革命」——騰雲七號行動——使用Java語言將核心業務程式碼進行了重構和拆解,建立了以Dubbo為核心的SOA微服務框架,使用ZooKeeper+Swoole為核心的業務呼叫提供機制。滿足新業務使用Java語言編寫、老業務仍然使用PHP編寫,同時支持兩種語言(Nodejs&PHP)呼叫Dubbo服務的能力。



之後,開始全面推行前後端分離,於是流行了沿用至今的主流架構:


  • Nodejs:負責前端

  • Java:負責後端

  • PHP:負責老計畫維護


  • 剩余部份小系統或者邊緣化的工具使用其他語言開發,或者在此三種語言基礎上的一些變種:



    隨著業務的重構和拆解,以前一個個龐大的系統被拆解成若幹個獨立的小系統,使得交付變得更加容易。



    而隨著計畫工程數量的快速增長,交付開始變得頻繁,傳統開發模式的一兩個月交付一次遠遠不能滿足交付要求,改變迫在眉睫。


    2016年Q3季度,感受到改變迫在眉睫,ZBJ研發中心經充分準備後決定抽調部份運維同學和開發同學組建一支名為「DevOps取經團」的團隊,力圖打造屬於ZBJ自己的以提高研發效率為目標的平台。


    ZBJ CI/CD發展史


    第一階段:2015年以前


    2015年以前,此時「工業革命」還未開始,ZBJ所謂的流水線先後經歷了「大鍋飯年代」、「公交車模式」。


    大鍋飯年代(本圖由DevOps團隊提供)


    公交車模式(本圖由DevOps團隊提供)


    可以看到,無論是「大鍋飯年代」,還是「公交車模式」都會面臨很多問題:


  • 計畫耦合度太高,容易導致合並沖突,環境沖突等。

  • 整合過程中未對程式碼進行審查,錯誤程式碼釋出到測試環境後,會影響依賴方的測試。

  • 釋出受限制,必須在專門時間由專人釋出。

  • 釋出異常時,回滾工作異常艱難。


  • 第二階段(2016-2017):私家車模式


    透過「工業革命」的變革,對系統的重構和拆解,同時引入工程責任制,給每個工程指定負責人,對工程的各種許可權進行了管控,業務範圍和邊界變得更加清晰,使得計畫耦合度太高的問題得到了很大的緩解。


    解決了計畫耦合度高的問題,接下來解決如何實作隨時由開發團隊自主發程式碼的問題。


    用Jira做研發流程管理,制定針對ZBJ需求上線的流程:



    需求上線流程每個環節對應流水線的每個環境,只有到達某個環節,才能推播對應環境,每個環節會制定對應的準入準出,保證每走到的下一步都離成功部署更近一步。


    推播環境示意圖


    每個環境制定不一樣的執行任務,基本包括Jira狀態校驗,程式碼審查(單元測試),編譯構建,上傳包到制品庫,拉取制品庫中的包部署到對應環境。


    值得註意的是,我們引入了Docker釋出,所以我們的流水線是支持容器和虛擬機器的混合釋出的。虛擬機器釋出方式的制品是存放在一個叫做檔伺服器的地方,容器釋出方式的制品是push到Harbor倉庫的。


    容器釋出&虛擬機器構建打包示意圖


    采用的分支策略是:branches開發,master釋出,tags存檔(我們使用GitLab作為源碼管理工具)。在測試環境通常為非master分支,測試完畢後合並到master,推播預釋出,並針對目前版本打一個tag。針對這種情況,我們的「一次構建,處處使用」指的是測試環境用測試環境構建好的包,其他環境用預釋出構建好的包。


    各環境使用制品示意圖


    在推播每一個環境(test環境除外)時,都會校驗目前版本的程式碼是否為前置環境推播過的最新程式碼,保證不會將沒有經過審查的程式碼交付到線上。


    在測試環境每一次編譯構建之前,都會對程式碼進行一次安全掃描,Java語言的工程透過解析pom檔對其所有的依賴進行遞迴掃描,Nodejs語言的工程透過對node_module裏下載的包進行遞迴掃描,確保有安全漏洞的程式碼不會被帶入到生產環境。


    編譯構建時,會根據開發語言的不同,執行不同的編譯指令碼,根據釋出方式的不同(虛擬機器釋出或者容器釋出),執行不一樣的後續操作步驟。


    我們將Jenkins作為後台編譯伺服器,采用的是多master多slave的架構,我們並未直接使用Jenkins的流水線,而是開發了一個叫Pipeline的系統,與Jenkins做對接(此時的對接方式是呼叫Jenkins的API),由Pipeline系統提供Jenkins作業所需要的全部資訊,另外編寫了整個過程需要具體執行操作的指令碼,透過Jenkins的job配置「Execute shell」的方式每次在構建之前匯入到工作空間。



    流水線標準生產過程大體如下:


    流水線標準生產過程示意圖


    另外,針對回滾的情況,因為每次在上線前都會在預釋出會構建一個穩定的版本,並打一個tag,並且記錄下tag對應的制品(包或者映像)版本,所以在回滾的時候,只要選擇要回滾的版本,便能找到對應版本的制品,進行重新釋出,以此達到回滾的目的。


    回滾流程


    至此,第二階段大體實作了以下功能:


  • 透過用Jira需求上線流程和流水線做整合,以及多種推播前的校驗,保證了上線過程的每一步都是可靠的

  • 每一個環境的整合和釋出都是自動化的

  • 因為過程變得可靠且自動化,使得將釋出過程開放給研發團隊成為了可能,達到了隨時自主上線的目的。


  • 然而這樣的流水線也有諸多問題:


  • 不夠靈活,如在推播測試環境時,整個過程執行的步驟是固定的的,即第一步做什麽,第二步做什麽都是固定的,不能新增也不能刪減,如某些團隊需要進行單元測試,有的不需要,但流水線都會去執行單元測試,通常情況下單元測試過程是一個花費時間比較長的過程,這對於需要頻繁更改和部署的業務是不友好的。

  • 推播成功率不高,因為整個過程是串聯的,某一個環節出現錯誤,將會導致本次推播失敗,而某些環節本不應該影響構建結果的,最後導致了構建失敗。


  • 第三階段(2017-2019):擁有靈活車道的私家車模式


    考慮到前面提到的兩點,ZBJ DevOps團隊在17年底對流水線做了二次改造:


    1、所有執行步驟拆解成獨立原子任務,建立原子任務庫;


    2、將原子任務根據功能性分為兩種,校驗類以及執行類,校驗類原子任務主要是是做準入準出的判斷,執行類主要是編譯構建,打映像,上傳hub倉庫以及部署。


    3、將原子任務根據執行載體分為兩種,Java類和Jenkins類,直接用Java程式執行的任務為Java類,需要Jenkins執行的任務為Jenkins類。


    4、根據開發語言、釋出方式、業務型別的不同,從原子任務庫中選取不同原子任務組成一條標準有序的執行流水線。


    5、提供工程特殊配置,如有些工程需要增強校驗,有的工程需要減少校驗,則可以透過啟用和禁用的方式進行特殊配置,如下圖所示,根據1、2和3步驟後可最終生成一條本次執行的流水線任務列表。


    原子任務一覽表


    執行效果圖


    6、根據原子任務的制定,我們將Jenkins執行的job也拆分成了對應的幾類,每一類擁有足夠多數量的job進行任務的執行。




    7、升級了Pieline系統和Jenkins通訊架構,透過編寫一個RabbitMQ的外掛程式植入到Jenkins Master上,從原來呼叫API的方式,改成用RabbitMQ的方式進行通訊,大大提高了效率和成功率。


    通訊示意圖


    8、升級了Jenkins架構,構建一個能適配ZBJ所有開發語言的映像,利用Jenkins Master的Kubernetes外掛程式,將原來的虛擬機器slave節點全部替換成容器slave節點,並且這個slave集群完全由Jenkins Master的Kubernetes外掛程式排程,不論在高並行和低並行的時候都能及時擴縮容,滿足業務需求。


    第四階段(2020-至今):智慧駕駛模式


    可以看到,到第三個階段為止,我們的每一次編譯過程,都需要研發同學「推播一下」,而且這個過程也是需要花費一些時間的,比如一個正常的Nodejs工程平均編譯時長至少需要花費100+s以上,一個正常的Java工程平均編譯時長至少也是需要30s以上,由於我們提供了推播過程「視覺化」的功能,且沒有執行結果的通知,導致使用者必須關註推播過程以確保本次推播是成功的,大大浪費了研發同學的時間。


    在此基礎上,我們進一步做出了以下最佳化:


    1、為每一個GitLab上的工程添加一個Webhook,每當開發人員向倉庫push一次,便會觸發Webhook,呼叫Pipeline系統介面進行一次快速構建。



    註意:並不是每次push都會進行一次快速構建,為了防止開發同學頻繁修改少量程式碼送出到版本庫,我們規定了一個「暗號」,只有當開發同學在commit message中添加這個「暗號」,才會觸發一次快速構建。


    auto_trigger_build就是暗號內容



    2、快速構建的結果是構建一個包或者一個映像,存放在前文提到過的檔伺服器或者Harbor倉庫。在下一次使用者「推播」的時候,便會根據分支和版本判斷是否存在已編譯過的包或者映像,如果存在,則直接使用,跳過編譯過程。



    3、增加快速構建結果通知,因為整個快速構建過程是後台執行的,所以流水線系統透過企業微信的方式通知到使用者本次快速構建的結果。


    快速構建前通知


    快速構建完成後結果通知


    4、除了增加快速構建的通知,我們還增加了推播的通知。使用者再也不用關註推播過程,只需要在推播後繼續做其他事情,推播結果由企業微信通知到使用者。




    值得註意的是,當推播失敗後,流水線系統也會通知到使用者,進行對應問題的排查。



    至此,ZBJ的CI/CD實踐之路基本介紹完畢,當然,其中也還有很多細節方面,因為涉及的東西太多,不便鋪開來講。

    總結三次重大改造的結果

    第一次改造:奠定了ZBJ的CI/CD基礎,打造了一條標準的流水線,解放了運維勞動力(過程全自動化),提高了研發效率,降低了研發成本(運維同學由最多時候的三四十個減少到了不到十個人)。


    第二次改造:流水線實作了高可用,同時其靈活的配置能完美滿足不同業務團隊的需求。


    第三次改造:提升了流水線效率,弱化推播過程,增強以人為本的體驗,使推播過程更加智慧化。

    談談未來

    CI/CD實踐之路還在繼續,因為不同公司有不同的業務場景,而同一公司的業務也會隨著時代的發展不斷變化,只有適合自己的才是最好的,只有能擁抱變化的才是最好的,但萬變不離其宗的,我覺得應該有一下幾點:


  • CI/CD應該是以提高研發效率為目標的實踐,一切脫離這個目標只是為了迎合什麽口號而做什麽的是都是耍牛氓。而實作這個目標是一個比較漫長的過程,一開始會比較容易,後面就會越來越難,這需要不斷思考和學習的過程。

  • CI/CD應該是緊貼業務的,因為業務的不同,要求的技術架構也會有所不同,隨之而來,要求的交付方式也會有所不同。

  • CI/CD應該是以人為本的,我們應該盡可能地將一切繁瑣的過程交給程式去執行,而人只需要「坐享其成」或者做少量的決策即可。

  • 往期推薦


    點亮,伺服器三年不宕機