流水線即代碼
2016年11月份的技術(shù)雷達(dá)中給出了一個簡明的定義:流水線即代碼(Pipeline as Code)通過對持續(xù)集成/持續(xù)交付(CI/CD)運行工具進(jìn)行編碼而非配置的方式定義部署流水線。其實早在2015年11月份的技術(shù)雷達(dá)當(dāng)中就已經(jīng)有了類似的概念:
- The way to avoid programming in your CI/CD tool is to extract the complexities of the build process from the guts of the tool and into a simple script which can be invoked by a single command. This script can then be executed on any developer workstation and therefore eliminates the privileged/singular status of the build environment.
大意是將復(fù)雜的構(gòu)建流程納入一個簡單的腳本文件,然后用一條命令調(diào)用。這樣,任意的開發(fā)者都能在自己的工作區(qū)中執(zhí)行腳本重建一套一模一樣的構(gòu)建環(huán)境,從而消除CI/CD環(huán)境由于散亂配置腐化而成的特異性。
這么做的原因很好理解,使用CI/CD工具是為了暴露產(chǎn)品代碼中的問題,如果它們自身已經(jīng)復(fù)雜到不穩(wěn)定的地步,我們還使用它就是自找麻煩。
從某種程度上看,實施流水線即代碼是不證自明的。在CI/CD的實踐過程中,凡是可以被編碼的東西都已經(jīng)被代碼化了,比如:構(gòu)建、測試、數(shù)據(jù)庫遷移、部署和基礎(chǔ)設(shè)施/環(huán)境配置(Infrastruture as Code)等。說得爛俗點,流水線已經(jīng)是CI/CD實踐過程中的“***一公里”,讓流水線變成軟件開發(fā)中的“一等公民”(即代碼)是大勢所趨、民心所向。
不過,這種論斷畢竟欠缺說服力,我們接著從實踐的痛點出發(fā)總結(jié)當(dāng)前流水線遇到的問題。
實踐中的痛點
我給客戶搭建和配置過不少CI/CD流水線(被同事戲謔地稱為“CI/CD搭建獸”),***的痛苦莫過于每次都得從頭來過,即便大部分情況下所用的工具和配置都大同小異。
其次是手工操作產(chǎn)生的配置漂移(configuration drift)。以Jenkins為例,暫且不談1.0版本無法直接支持流水線這一問題,為了支持構(gòu)建、測試和部署等,我們一般會先手工安裝所需插件,在多個文本框中粘貼大量shell/batch腳本,下載依賴包、設(shè)置環(huán)境變量等等。
久而久之(實際上用不了多久),這臺Jenkins服務(wù)器就變成無法替代(特異化)的“怪獸”了,因為沒人清楚到底對它做了哪些更改,也不知道這些更改對系統(tǒng)產(chǎn)生了哪些影響,這時的Jenkins服務(wù)器就腐化成了Martin Fowler口中的雪花服務(wù)器(snowflake server)。雪花服務(wù)器有兩點顯著的特征:
- 特別難以復(fù)現(xiàn)
- 幾乎無法理解
***點是由于以往所做的更改并沒有被記錄下來,所以做過的操作都是七零八落的,沒有辦法復(fù)現(xiàn)同樣的操作,也無法復(fù)制一個同樣的系統(tǒng)。
第二點則是由于絕大部分情況下散亂的配置是沒有文檔描述的,哪部分重要、哪部分不重要已經(jīng)無從知曉,改動的風(fēng)險很大。
這些問題會在流水線的演化過程中惡化得越來越嚴(yán)重。
一般來講,除非不再使用,否則流水線不會保持一成不變。具體實施過程中,考慮到項目,尤其是遺留項目當(dāng)前的特點和團隊成員的“產(chǎn)能”,我們會先將構(gòu)建和部署自動化;部署節(jié)奏穩(wěn)定后,開始將單元測試和代碼分析自動化;接著可以指導(dǎo)測試人員將驗收測試自動化;然后嘗試將發(fā)布自動化。
在這之后,并未結(jié)束,團隊還要持續(xù)優(yōu)化流水線,包括CI的速度和穩(wěn)定性等。換句話說,流水線的演化階段其實是和項目的當(dāng)前進(jìn)展密切相關(guān)的,保證這樣的對應(yīng)關(guān)系有時是有必要的,比如:在多分支的版本控制下,發(fā)布分支所需流水線和主干分支會存在不同。發(fā)布分支是主干分支某個時刻分出去的,它需要在那時的流水線上才能正常工作。由于前面所說雪花服務(wù)器的特征,重建這樣一條流水線并不是一件容易的事情。
如何解決
其實,流水線即代碼本身已經(jīng)回答了這個問題。當(dāng)前實現(xiàn)這一概念的CI/CD工具大體遵循了兩種模式:
- 版本控制
- DSL(領(lǐng)域特定語言)
對于特別難以復(fù)現(xiàn)、沒有保證對應(yīng)關(guān)系的痛點,我們就把流水線寫成代碼放到版本控制工具中管理起來。這樣一來,每一次更改都能被記錄下來,而且它會始終和此時的項目進(jìn)展保持同步。
對于幾乎無法理解、沒有文檔支持的痛點,我們就選用領(lǐng)域特定語言描述整條流水線。舉個Jenkins2.0例子,它允許我們在項目的特定目錄下放置一個Jenkinsfile的文件,內(nèi)容如下:
- node('master') {
- stage('Checkout') {…}
- stage('Code Analysis') {…}
- stage('Unit Test') {…}
- stage('Packing') {…}
- stage('Archive') {…}
- stage('DEV') {…}
- }
- stage('SIT') {
- timeout(time:4, unit:'HOURS') {
- input "Deploy to SIT?"
- }
- node('master') {…}
- }
- stage('Acceptance Test') {
- node('slave') {…}
- }
Jenkins2.0使用Groovy實現(xiàn)了一套描述流水線的DSL,我們即便不了解Groovy語言,只要對流水線稍微熟悉,就能按照文檔中的例子編寫出符合要求的代碼。
類似的工具還有Concourse.ci、λCD(LambdaCD)等。 Concourse.ci使用了基于yaml的DSL,獨立抽象出Resource(外部依賴,如:git repo)、Job(函數(shù),對Resource進(jìn)行g(shù)et或put操作)以及Task(純函數(shù),必須明確定義Input/Output)模型。效果圖如下:
而λCD則使用Clojure語言實現(xiàn)了DSL,抽象出Pipeline和Step模型,使用了Lisp特有的宏(macro)擴展和自定義普通函數(shù),編寫起來簡單明了。如下:
- (def pipeline-def
- `(
- (either
- manualtrigger/wait-for-manual-trigger
- wait-for-repo)
- (with-workspace
- clone
- (in-parallel
- run-some-tests
- run-smokeing-tests)
- run-package
- deploy)))
上述的pipeline-def就是這條流水線的定義,極為優(yōu)雅得是,它的代碼和UI事實上構(gòu)成了——映射的關(guān)系,簡單到***。
值得一提的是,λCD有別于其它同類型的工具,它本身就是一份用Clojure寫就的微服務(wù)。換句話說,其它的工具可能需要借助基礎(chǔ)設(shè)施即代碼完成自身的安裝,但λCD不用,它完全可以采用其它微服務(wù)的部署方式,比如用λCD部署它自己,類似于編譯器的自舉(bootstraping)。這個時候,我們就需要兩套λCD服務(wù),一套用于部署λCD自身,另一套部署開發(fā)中的工程。
小結(jié)
流水線即代碼是個新概念,也就意味著我們還需要花時間去探索與之相關(guān)的實踐,比如,調(diào)試和測試(既然是代碼就需要測試)。一旦有了這些實踐,我們就可以把流水線本身作為產(chǎn)品放到流水線上運作起來,那時將會看到一種很好玩的現(xiàn)象——舊的流水線會構(gòu)建并部署新流水線,發(fā)生上文所說自舉(bootstraping)現(xiàn)象,這也表明流水線是不斷進(jìn)化的。
此外,當(dāng)流水線成為代碼,它在最終的交付物中必然占據(jù)一席之地,其潛在的價值還等待我們挖掘,至少從精益的角度,流水線能做的事情還有很多。
【本文是51CTO專欄作者“ThoughtWorks”的原創(chuàng)稿件,微信公眾號:思特沃克,轉(zhuǎn)載請聯(lián)系原作者】
































