天天看點

持續內建和傳遞流水線的反模式

原文發表于:https://www.rea-group.com/blog/continuous-integration-and-delivery-pipeline-mistakes/

CI/CD & Pipeline

随着DevOps的理念在衆多公司的采納,CI/CD也漸漸落地。

**CI(**Continuous Integration)持續內建,是把代碼變更自動內建到主幹的一種實踐。CI的出現解決了內建地獄的問題,讓産品可以快速疊代,同時還能保持高品質。它的核心措施是,代碼內建到主幹之前,必須通過一系列自動化測試,比如編譯、單元測試、lint、代碼風格檢查。

CD包括持續傳遞和持續部署。持續傳遞(Continuous Delivery)指的是團隊自動地、頻繁地、可預測地傳遞高品質軟體版本的過程,可以看做持續內建的下一個階段,強調的是無論代碼怎麼更新,軟體都是随時可以傳遞的;持續部署(continuous deployment)更強調的是使用自動化測試來保證變更的正确性和穩定性,以便在測試通過後立即部署,是持續傳遞的更進一步。二者的差別是,持續傳遞需要人為介入,需要確定可以部署到生産環境時,才去進行部署。

圖1 持續內建 & 持續傳遞 & 持續部署

CI/CD Pipeline是軟體開發過程中避免浪費的一種實踐,展現了從代碼送出、建構、部署、測試到釋出的整個過程,為團隊提供可視化和及時回報。Pipeline推薦的實施方式是,把軟體部署的過程分為不同的階段(Step),其中任務(job)在每個階段中運作。在同一階段,可以并行執行任務,幫助快速回報,隻有一個階段中所有任務都通過時,下一階段的任務才可以啟動。比如圖中,從git push到deploy to production的整個流程,就是一條CD Pipeline。可以利用Pipeline工具,如Jenkins、Buildkite、Bamboo,來幫助我們更友善的實施C/ICD。

圖2 CI/CD Pipeline

###CI/CD Pipeline的反模式

雖然有Pipeline廣泛的應用,但我們卻會聽見開發人員抱怨糟糕的Pipeline對他們的傷害,如阻塞開發流程,影響變更的部署效率,降低傳遞品質。我們收集了項目上經常出現的Pipeline的八大反模式,按照出現頻率排序,分别闡述這些壞味道,分析可能産生的原因、影響及解決方式,希望能夠減少抱怨,讓Pipeline更大程度上提升工作效率。

1. 沒有代碼化

反模式:Pipeline的定義沒有完全代碼化,進行版本控制,存儲在代碼倉庫,而是在Pipeline 工具上直接輸入shell腳本定義Pipeline的運作過程。

原因:由于早期的CI工具不支援代碼化,一直能夠保留到現在,沒有做重構和更新。

影響:Pipeline的建立和管理都是通過CI工具的界面互動來的,難以維護,是以需要專門的管理者來維護,而有人工操作的部分就會出錯,是以會降低Pipeline的可靠性。如果Pipeline因為一些原因丢失就沒有辦法很快恢複,就會影響傳遞速率。

解決方案:Pipeline as code這個理念已經提了很多年了,在ThoughtWorks 2016年的技術雷達裡就已經采納了,需要強調的是,用于建構、測試和部署我們應用程式或基礎設施的傳遞Pipeline的配置,都應以代碼形式展現。随着組織逐漸演變為建構微服務或微前端的去中心化自治團隊,人們越來越需要以代碼形式管理Pipeline這種工程實踐,來保證組織内部建構和部署軟體的一緻性。

通常,針對某個項目的Pipeline配置,應和項目代碼放在項目的源碼管理倉庫中。同業務代碼一樣要做code review。這種需求使得業界出現了很多支援Pipeline工具,它們可以以标準的方式建構、部署服務和應用,如Jenkins、Buildkite、Bamboo。這些工具用大多有一個Pipeline的藍圖,來執行一個傳遞生命周期中不同階段的任務,如建構、測試和部署,而不用關心實作細節。以代碼形式來完成建構、測試和部署流水線的能力,應該成為選擇CI/CD工具的評估标準之一。

2. 運作速度慢

反模式:一條Pipeline的執行時間超過半小時,就屬于運作速度慢的Pipeline。(這裡的運作速度與傳遞的産品有關,在不同的項目中,運作時長的限定也有所不同)

很多原因都會導緻運作一次Pipeline時間很長,比如:

  • 該并行的任務沒有并行執行,等待的任務拉長了執行時間;
  • 執行Pipeline的agent節點太少,或者性能不足,導緻排隊時間太長,效率太低;
  • 執行的任務太重,相同測試場景被不同的測試覆寫了很多次。比如同樣的邏輯在不同測試中都測了一遍;
  • 沒有合理利用緩存,比如每個任務裡都要下載下傳全部依賴,在建構Dockerfile時沒有合理利用layer,每次都會建構一個全新的image。

影響:這是開發人員抱怨最多的一個反模式。靈活開發模式需要Pipeline快速回報結果,受這一反模式制約,在特性開發過程中,經常出現開發人員改一行代碼,等半天CI的效果。如果出現一個線上事故需要修改一行代碼來修複,最終需要很長的周期才能讓這一更改應用在生産環境。

解決:不同的原因導緻的Pipeline速度慢,有不同的解決方法。比如針對上面的問題,我們可以去:

  • 檢查Pipeline的設計是否合理,盡可能讓任務并行;
  • 對代碼的各種測試深入了解,讓測試盡量正交,避免過多的重複;
  • 檢查代碼中的依賴,合理利用好緩存。包括Docker Image、Gradle、Yarn、Rubygem的緩存,以及Dockerfile是否合理的設計,最大化的将不可變的layer集中的開始階段;
  • 檢查執行建構的節點資源是否充足,能否在任務量大時做彈性伸縮,減少等待和執行時間。

####3. 執行結果不穩定

圖3 執行多次結果不穩定

反模式:建構相同代碼的Pipeline運作多次,得到結果不同。比如,基于同一代碼基線,一條Pipeline建構了5次,隻有最後一次通過了。

原因:出現執行結果不穩定的原因也多種多樣,比如測試用例的實作不合理,導緻測試結果時過時不過;代碼中使用了不可靠的依賴源,比如來自國外的依賴源,下載下傳依賴經常逾時;由或是在Pipeline運作過程中沒有合理設計各個階段,導緻有些任務同時運作沖突了。

影響:Pipeline作為代碼釋出的最後一道防火牆,最基本的特性是幂等性,即在一個相同的代碼基線,執行Pipeline的任意任務,不管是10次、100次,得到的結果都相同。Pipeline不穩定會直接導緻代碼的部署速率降低。更重要的是,影響開發人員對Pipeline的信任。如果不穩定Pipeline不及時解決,慢慢這條Pipeline會失去維護,開發最後會轉向手工部署。

解決:要建構幂等的、可靠的Pipeline,就要分析這些不穩定因素出現的原因。

  • 提升測試的穩定性,比如用mock替代不穩定的源。
  • 采用Pipeline的重試功能,或者采用穩定的鏡像源,或者提前建構好基礎鏡像。
  • 引入Pipeline的插件保證任務不會并行執行。

4. 濫用job處理生産環境資料

反模式:使用Pipeline的定時任務的特性,運作生産環境的負載。比如經常會定期做資料備份、資料遷移,資料抓取。

原因:由于對Pipeline的認識不夠清晰,将重要的任務交由Pipeline做。Pipeline一旦有了某個生産環境的通路權限,做這些資料處理相關的任務就很友善,減少了很多人為的操作。

影響:Pipeline是用來做建構、部署的工具,不能用于業務邏輯的執行。由于Pipeline是一個内部服務,他的SLO/SLI必定和生産環境不同,如果強依賴勢必影響生産環境的SLO。假如某天Pipeline挂掉了,生産環境就無法得到想要的資料。另外,任務和Pipeline緊密耦合,是我們後面會讨論的另一個反模式。

解決方法:用生産環境自身的工具解決這種資料問題,比如 采用AWS的lambda,定時觸發資料處理任務。

5. 複雜難懂

圖4 Pipeline的定義邏輯複雜

反模式:Pipeline的定義包含了太多的邏輯,複雜難懂。隻有在一條Pipeline運作起來才能知道這裡會運作哪些步驟,會将這個版本部署到哪些環境。

原因:Pipeline的代碼不夠整潔。有人認為Pipeline隻是給CI工具提供的,就随意編寫,認為能完成指定的工作就夠了。

影響:Pipeline的複雜性,會直接提升學習成本。如果想重複執行上一次建構,會花費較長時間。

解決:Pipeline的代碼要簡潔,把複雜性放在部署腳本或代碼側。通過每個階段的的标題可以直接了解所要執行的任務。如果存在很多相同的邏輯,可以通過開發Pipeline的Plugin來簡化配置。

6. 耦合太高

圖5 (左)耦合太高的Pipeline定義 (右)期待的Pipeline定義

反模式:Pipeline跟運作它的CI工具緊密耦合,以至于無法在本地重複相同的步驟。

表現可能多種多樣:

  • Pipeline的定義跟建構工具緊密耦合,包含了Pipeline工具特有的參數以及CLI指令。比如在配置中使用BUILDKITE_BUILD_NUMBER,BUILDKITE_QUEUE等等。結果就是本地運作的方式或結果和Pipeline上運作的方式以及結果不一緻。
  • 在Pipeline的任務中寫了一大段腳本,或者直接使用指令加上一堆參數,以至于在本地想跑測試需要在Pipeline的配置中找指令并且在本地粘貼。
  • 不做環境隔離, 測試,編譯,部署等都依賴于運作時環境。可能出現Pipeline 因依賴的軟體/庫等版本不一緻而導緻的不一緻的情況,通常還很難排查。

影響:因為本地不友善調試,所變更的失敗機率會大大增加。如果變更用來修複一個Bug,由于不做環境隔離,會導緻故障修複周期拉長。

解決:Pipeline的每個step都用腳本封裝起來,腳本裡不使用Pipeline工具特有的參數,并且保證本地運作時和Pipeline上保持一緻。

7. 僵屍Pipeline

反模式:一條Pipeline年久失修,很久沒有執行過,而且最後一次的建構是失敗的。

原因:這種反模式通常出現于不再活躍開發的項目上,是以很久沒有執行過Pipeline。

影響:Pipeline的結果反應的是一個項目的狀态。由于軟體産品疊代速度快,這個軟體的依賴可能已經發生了巨大的變化,一旦運作,大機率會出錯。假如這個項目目前出現了一個事故,需要送出代碼,就得先修複項目的Pipeline,才能確定送出修複代碼。

解決:針對常年沒有送出的Pipeline,我們建議讓Pipeline周期的執行,出現問題立即修複。如Github的Dependabot,能保證項目的依賴始終是是最新的,而且能讓Pipeline執行,提早發現問題。

8. 需要人工介入

反模式:通常項目上會有一個專職Ops,在項目可以釋出的時候手動觸發部署流程,或者需要傳遞很多參數,讓Pipeline運作起來。

原因:包括項目的流程繁瑣,需要反複确認;DevOps成熟度不夠,沒有實作持續部署;或者CI的測試覆寫不夠,CI通過後還要進行更多的測試才能部署。

影響:這些Pipeline需要專人盯着,去點某些按鈕。會直接影響産品的傳遞速率和代碼部署頻率。

解決:讓項目的運作更加靈活,減少Pipeline定義中的阻塞按鈕,将手工測試自動化後內建到Pipeline中。

最後

希望通過本篇文章,意識到項目中CI/CD Pipeline的問題,使其發揮更大的價值。

文/Thoughtworks馮炜

原文連結:

https://insights.thoughtworks.cn/continuous-integration-and-delivery-pipeline-mistakes/

更多精彩洞見,請關注微信公衆号:Thoughtworks洞見

繼續閱讀