《從頭搭建持續整合 DevOps 流水線》由資深敏捷教練、極限程式設計學院高階講師、CODING 特邀敏捷顧問李小波老師主講,將基於 CODING 展示如何編寫 Jenkinsfile 搭建 CI/CD 流水線,包括單元測試,端到端測試,程式碼規範檢查,製品庫,Docker 化部署。
大家好,今天課程的主要內容為如何從頭搭建 DevOps 流水線以及其在研發工作中的意義,最後是 DevOps 流水線實踐與敏捷開發的關係的總結。
最開始是在極限程式設計裡提出了持續整合,然後 ThoughtWorks 又提出了持續交付,之後又提出了DevOps 這個概念,為什麼要做這些呢?我認為原因是隨著時間的增長,生產力會不斷的下降。團隊剛開始時效率很高,從 0 到 1,功能上線很快,但是到了後期速度就會越來越低,直到最後開發停滯。系統開發的後期往往會出現三個難點:第一,增加新特性難。隨著系統功能的累積,會出現很多重複的程式碼以及不合理的設計,導致增加一個新特性時要改的地方非常多,改動成本非常高;第二,修復缺陷難。使用者可能會報一些缺陷或 Bug,但是因為程式碼太亂、太複雜導致很難定位到缺陷;第三,引出新的缺陷。做新 Feature 時,很容易出現打地鼠的現象,按下一個 Bug 結果又冒出來三個 Bug,給團隊帶來困境。
出現這些現象的原因首先是在需求演變的過程中,有的程式碼會不斷地腐壞,方法會越來越長,類會變得越來越大,程式碼出現大量的重複。雖然有些團隊會制定程式碼規範,但在實際應用中,可能基本上都在 Word 或 PDF 裡“躺著”,檢查執行難以長期堅持。
其次是架構也會腐壞。專案開始時架構師通常會根據業務設計好架構,有多少個模組、物件,分到幾層,哪層可以調,哪層不能調,怎麼依賴關係,這些都會很清楚,但在不斷的演變過程中,架構往往會變得亂七八糟。
戴明(William Edwards Deming)提出了質量內建的概念,即產品的質量在建設過程中就已經嵌入,並不是靠後期的檢測來發現的。後期的檢測並不能增進程式碼質量,也不能提升產品質量,問題發現的越早修復的成本越低。所以我們希望每一次往程式碼庫提交程式碼時,都能夠馬上獲得反饋:這次修改是好的還是不好的,是不是增加了重複的程式碼,是不是降低了測試覆蓋率,是不是破壞了某一些功能等等。有了這樣的評判標準,就能夠始終保證每個人每次提交程式碼都是在產品上增加價值,而不是破壞它。在這個背景下就引入了流水線這個概念。
流水線是一個隱喻,意思是將軟體研發的各個環節銜接起來。我認為流水線在研發管理過程中扮演了三個角色:不辭辛勞的臨時工、鐵面無私的守護者以及快速精準的操作員。
流水線是不辭辛勞的臨時工。現在的構建流水線都可以按需建立。比如說 CODING,這麼多的企業在用它的持續整合功能,不可能給每一個使用者分配固定的計算、儲存等資源。如果要能線性的增長,策略應該是當使用者需要構建時會按需進行建立,並且用完之後進行銷燬。從這一點上看,流水線就很像一個臨時工。除此之外,流水線還可以不厭其煩地做重複的事情,尤其是持續整合的團隊每天都要提交很多次程式碼,每一次提交都人工做一次檢查就很痛苦,但機器就能重複機械運動。
流水線是鐵面無私的守護者。首先是程式碼規範。很多開發團隊的程式碼規範都“活”在 Word 或 PDF 裡,即使有資深教練或者技術 Leader 偶爾會做一些程式碼評審,這種執行力度也是遠遠不夠的。但是通過整合到流水線中的方式,比如指定一個方法不能超過多少行,一個類不能超過多少行,程式碼重複率不能超過多少,程式碼的寬度及命名等,進行自動化、標準化的檢測,就可以有效的保證程式碼規範的落地;第二是測試覆蓋率。在日常的開發工作中會有單元測試、元件測試、介面測試、整合測試、端到端測試等多種測試,每一次提交程式碼都需要檢查測試覆蓋率有沒有下降。但很多團隊這一塊是缺失的,他們在流水線上只是做了構建、打包、部署等動作,並沒有跑測試覆蓋率,而是靠大規模的手工測試來保證質量,導致無法快速迭代;第三是架構約束。程式碼一般有分層,每一層裡應該放什麼檔案,哪個檔案能夠調哪個檔案,這些都是有約束的,需要一套自動化的機制來保證落地;其次還有安全性檢查。比如做 Web 開發會引入一些第三方的開原始碼,這些開原始碼往往會有安全缺陷,需要在每次引入新內容時進行安全性檢查。
流水線是快速精準的操作員。越複雜的系統,環境就越多,包括開發聯調環境、測試環境、預釋出環境等,到正式的環境還會有多個例項。每個環境上訪問資料庫的 URL 不一樣,訪問其他服務的環境也會不一樣,如何保證在操作過程中都不出錯?可以依靠流水線來標準化、流程化、自動化地完成這些動作,每次程式碼提交時都檢查規範,針對不同的環境打出不同的包,持續的部署到不同的環境上面。
那麼如何搭建一條流水線?有一種方式叫做流水線即程式碼(Pipeline as Code),即把流水線放到程式碼裡。我認為這樣做的好處是版本化,傳統的搭建方式問題在於操作沒有記錄,也無法強制 Review,當一臺伺服器掛掉,換一臺伺服器時需要把原來流水線的配置重新操作一遍。如果將流水線變成程式碼,就可以跟蹤及重複建立,提高生產效率。另外補充一點,流水線在構建時主要有兩種方式,一個叫宣告式,一個叫指令式。宣告式就是規定好環節與步驟,需要怎樣的東西。而指定式需要寫很多的條件判斷,是邏輯式的,維護成本也會高一些,所以宣告式是目前普遍採用的一種方式。
一條典型流水線應該包含四個關鍵環節。第一是構建。前端、後端的程式碼都需要編譯,前端比如 HTML、JS、CSS 等,可能還會用到一些模板,需要做編譯工作將其轉成瀏覽器能夠支援的格式;第二是檢查。編譯完成之後需要檢查程式碼是不是符合規範,是不是有很多的重複程式碼,重複率超過了多少等等;第三是測試。測試環節很重要,會影響團隊對於產品釋出的信心。這裡講一個測試金字塔理論:底層是大量的單元測試,中間是元件測試或者介面測試,頂部是端到端測試。因為大量的邏輯都是在各種 If else 分支裡,單元測試可以覆蓋到這些分支,那麼上層的測試就不需要再覆蓋下層已經覆蓋過的邏輯了。而上層測試的價值在於把這些程式碼整合起來,站在使用者的角度去使用它,看看能否正常工作。上層和下層的測試關注點不一樣,解決的問題也不一樣;第四是部署。最後需要構建映象,並推到製品庫裡面去,更新伺服器,做完這一系列的事情之後,流水線例項就會銷燬。
最後總結一下,為什麼會衍生出 DevOps 實踐及其跟敏捷開發的關係:
第一,我們最重要的目標是通過持續不斷地及早交付有價值的軟體使客戶滿意。我認為這跟敏捷是一脈相承的。敏捷的原則裡說可工作的軟體高於詳盡的文件,客戶更希望看到的是可用的軟體,而不僅僅是文件說明,但是由於開發過程的不透明,造成了客戶喜歡做微觀管理的現象,同時也會讓開發團隊變得很被動。敏捷裡有個價值觀叫尊重,這種尊重是需要團隊自己去贏得的。通過建立一套流程,提高開發過程的透明度,從而建立客戶與團隊之間的信任。另外跟敏捷原則相契合的一點是響應變化高於遵循計劃。這不是一句口號,而是一種能力,它包含:專案管理能力、需求管理能力、配置管理能力以及質量保障能力。這四種能力建設起來之後,團隊才能擁有響應變化的能力。持續整合、自動化測試、自動部署等這些核心能力,搭配上程式碼規範、CodeReview、TDD等這些實踐,才能真正提升開發團隊的實力,而不是僅僅把 Scrum 匯入進來,開開計劃會、站立會就行了。
第二,可工作的軟體是進度的首要度量標準。不是每天站會或者每週寫個郵件告訴客戶這周完成了多少工作,而一定是部署完成後,變成了可以看到的、可以體驗的功能才算是真正的進度。
第三,堅持不懈的追求技術卓越和良好的設計,敏捷能力由此增強。開發團隊擁有了程式碼規範檢查、自動化測試等這些質量門禁以後,才有底氣去不斷的做優化,得到可持續的、快速迭代的速率。實踐都會隨著技術的變化而變化,團隊能力也在持續的變化,但能不能持續地保持敏捷,那就要看價值觀、原則是不是能夠持續地符合。