天天看點

百度資深靈活教練:深度解析持續傳遞之全面配置管理

作者介紹

張樂,百度資深靈活教練、架構師,intelligent software development團隊成員。超過13年項目管理和靈活實戰經驗,曾任職于惠普、埃森哲等大型外企,負責大規模團隊靈活轉型和工程效率提升,積累了豐厚的知識體系和實戰案例。加入百度後,作為公司内先進軟體工程方法和生産力的踐行者、布道者,主導了百度雲、百度金融、雲安全等新技術産品的靈活轉型和devops實施,幫助團隊從業務、過程、技術、組織和工具等多個層面建立起全面的持續傳遞能力。gdevops全球靈活運維峰會、tid中國品質競争力大會等演講嘉賓。

一、持續傳遞與配置管理

持續傳遞和devops是目前靈活開發和運維圈讨論最多和最熱門話題之一,雖然距離這些概念的提出已經有段時間了,但是國内很多企業仍處于對相關實踐如何落地和實施的探索階段。

在今年舉辦的一些技術大會上,我分享過關于持續傳遞方法與實踐的話題,從網際網路時代對軟體研發和運維的挑戰出發,分析了行業普遍面臨的問題和痛點,并提出了持續傳遞體系化的實施模型。但限于分享時間的限制,很多内容無法充分開展詳細介紹。今天這篇文章,我們就來詳細談談持續傳遞中非常關鍵的一個實踐,隻有這個實踐做到位了才能確定持續傳遞的實施效果,同時它也是後續一系列其它實踐的基礎,這就是『全面配置管理』。

在正式開始之前,我們先來快速回顧下持續傳遞的實施模型。

百度資深靈活教練:深度解析持續傳遞之全面配置管理

上圖是我整理的持續傳遞體系實施模型,我在公司内部都是用這個模型來做推廣和實施的。這個模型從業務、過程、技術、組織四個大的方向入手,對實作持續傳遞的關鍵要素及其内部邏輯關系進行整理。實作思路是通過在業務層面形成需求分解和疊代機制,在過程層面實作可靠、可視化、可管控的全自動化傳遞流水線和一系列關鍵實踐,在技術層面提供基礎架構和應用架構的支撐,在組織層面強調跨職能團隊和資料驅動改進機制,進而全面地建立起快速、低風險、持續的向使用者傳遞價值的能力。

持續傳遞是對整個軟體傳遞模式的變革,涉及到的内容非常多、非常廣,在這個模型中大概有二十多個關鍵點。今天我們就聚焦一下,僅僅來談模型中一個關鍵實踐『配置管理』。

二、全面配置管理的組成部分

配置管理的概念大家肯定非常熟悉了,這是一個非常廣泛使用的名詞,但也經常容易被狹義的了解,比如被當做版本控制的同義詞等。在持續傳遞領域,我們強調的是『全面配置管理』,也就是對項目所有的相關産物及其之間的關系都要進行有效管理,通過這種方式管理項目中的一切變化,實作項目中不同角色成員的高效協作,能夠在任何時刻、使用标準化的方法,完整而可靠的建構出可正常運作的系統(而不僅僅是可工作的軟體),并且整個傳遞過程的所有資訊能夠互相關聯、可審計、可追蹤,最終實作持續、高效、高品質的傳遞。

為了做到全面配置管理,并為持續傳遞後續實踐奠定良好的基礎,一般來講至少要做好以下三個方面:

代碼和建構産物的配置管理:包括制定有效的分支管理政策,使用高效的版本控制系統,并對建構産物及其依賴進行管理;

應用的配置管理:對應用的配置資訊進行管理,包括如何存取配置、如何針對不同環境差異提升配置的靈活性;

環境的配置管理:對應用所依賴的硬體、軟體、基礎設施和外部系統進行管理,確定不僅傳遞了可工作的軟體,而且整個應用系統能夠正常、穩定地運作;

三、代碼和建構産物的配置管理

1、制定有效的分支管理政策

我們首先要強調,需要進行版本控制的不僅是源代碼,還有測試代碼、資料庫腳本、建構和部署腳本、依賴的庫檔案等,并且對建構産物的版本控制也同樣重要。隻有這些内容都納入版本控制了,才能夠確定所有的開發、測試、運維活動能夠正常開展,系統能夠被完整的搭建。

制定有效的分支管理政策對達成持續傳遞的目标非常重要。持續傳遞建議的方式是頻繁的送出代碼,并且最好工作在主幹上,這樣一來修改對所有項目成員都快速可見,然後通過持續內建的機制,對修改觸發快速的自動化驗證和回報,再往後如果能通過各種次元的驗證測試,最終将成為潛在可釋出和部署到生産環境的中版本。那持續傳遞為什麼這樣建議呢?下面我們來分析下幾種常見的模式及其各自的優缺點。

在實際工作中,普遍采用的分支管理政策無外乎以下兩種:

基于分支的開發

百度資深靈活教練:深度解析持續傳遞之全面配置管理

這正是很多團隊經常預設使用的模式,具體表現為接到需求後拉出分支,後面的開發都在分支上送出,每個分支生命周期較長,并且可能有多個并行分支,直到快要上線時甚至上線後才合并到主幹。

分支開發模式,根據具體使用場景,還可以進一步細分為:

分支開發、分支釋出、釋出後合入主幹

分支開發、分支測試、主幹回歸、主幹釋出

優勢:

我曾接觸過的一些團隊,開始時會選擇分支開發模式,因為多個功能可以完全并行開發,互不幹擾。還可以按每個功能特性拉出分支,那麼每次送出都是完整的功能特性,分支劃分明确、版本控制的記錄也會比較清晰易懂。并且由于不同需求的開發進度不同,可以選擇某個先開發完成的功能特性進行合并、釋出,而不會被其它分支上未完成的功能特性阻塞。

缺點:

那這種模式有沒有問題呢?引用電影《無間道》中的一句話,“出來混,總有一天要還的”,我覺得用在分支開發模式中還挺貼切的。因為雖然使用分支暫時隔離了不同功能的代碼,但系統的多個功能或者多個組成部分最終還是要內建在一起工作的。如果不同分支代碼之間有互動,合并時可能會有大量沖突需要解決,包括文本沖突和語意沖突(更難發現和解決)。在實際項目中,進行代碼合并時通常很容易出錯,解決沖突也非常耗時。我曾見到一個團隊有多個并行分支,在開發後期因為沖突太多,不得不指定兩名對系統了解非常全面的進階開發人員專職合并代碼,結果他們基本每天工作到後半夜,而且經常出現合并錯誤甚至遺漏合并的問題,這種方式非常痛苦。

分支開發模式,其實從本質上就是與持續內建的理念互相沖突的。持續內建是希望每次修改都盡早的送出到主幹,主幹總是處于最完整和最新的可用狀态,充分驗證後就可以用它來進行生産部署。而使用分支開發模式時,由于無法及時合并到主幹,那麼時間越長與主幹差别越大,風險就越高,最終合并的時候就越痛苦。是以持續傳遞不推薦使用分支開發的模式。

權衡和建議:

在某些情況下,有時迫不得已要采用分支開發的模式,比如并行需求太多且互相幹擾,或者在需求開發的同時有大塊的重構工作要做,或者針對特定的使用者開發特殊的功能,以及需要進行與主線無關的試驗等等。

在這些場景下,拉出分支其實意味着已經在持續內建/持續傳遞上做出了妥協,那麼我們建議至少要使用一些折中的方案:

盡量縮短分支的周期,最長也不要超過疊代周期;

每個分支上運作單獨的測試流水線,保證品質。雖然這種方式浪費資源,而且其實也沒進行”真正的“內建;

分支隻與主幹合并代碼,分支彼此之間盡量不做合并;

分支定期合并主幹上的變更;

分支定期檢查與主幹的偏離度。有這樣一種方式,就是每次分支送出時,由工具自動嘗試與主幹進行合并,并在這個臨時的合并版本上運作自動化測試,用來檢查是否可以合并以及合并後是否可正常工作,持續內建工具bamboo好像已經提供了類似功能;

當然,以上這些僅僅是經過權衡的折中方案,隻能一定程度上緩解問題,解決問題的最好方式還是改變分支管理政策。

基于主幹的開發

百度資深靈活教練:深度解析持續傳遞之全面配置管理

持續傳遞更傾向使用基于主幹的開發(trunk based development,tbd)模式,所有項目成員把代碼都送出到主幹上,送出後自動觸發持續內建進行驗證和快速回報,通過頻繁的內建和驗證,在保證品質的同時提升效率。google就在堅持使用主幹開發模式,所有人的所有更改直接送出到trunk上。

主幹開發模式,根據具體使用場景,還可以進一步細分為:

主幹開發、主幹釋出

主幹開發、分支釋出

相比于分支開發,主幹開發模式有很多優勢。首先是代碼送出到主幹,可以做到真正的持續內建,在沖突形成的早期發現和解決問題,避免後期的”合并地獄”,這樣的整體成本才是最低的。另外主幹會一直保持健康的狀态,每次合入代碼并驗證後都可進行安全的釋出。

難點:

主幹開發模式有很多優勢,但在實際使用場景中,也存在一些實施難點。

主幹上功能開發完成時間有先有後,如果遇到有未完成的功能但又需要釋出時,就需要一種方法屏蔽掉未完成功能,才能進行安全的釋出;

主幹上功能開發完成後,如果需要比較長的時間進行驗收測試,那麼此時為了確定釋出功能的穩定性且所有功能是經過驗證的,可能會限制新功能的送出,有的團隊采用的封版、當機主幹的做法就是這種情況,這樣的确會影響開發效率;

如果修改的功能非常複雜,或者要進行架構上的大範圍重構,以上問題就更加明顯和難以解決了;

如果團隊規模比較大,同時工作在主幹上的開發人員比較多,那麼沖突的機率會比較大,持續內建的失敗率可能比較高;

優化和建議:

其實上面提到的實施主幹開發的難點,在實踐過程中大多會遇到,但也基本都能找到對應的優化和解決方案。

增量式進行開發。通過功能拆解,把大需求分解成小的story,每個story很小可獨立釋出。把大的修改分解為一系列小的變更,增量修改的方式更可靠,風險也更低,這也與精益思想中小批量生産的理念一緻;

主幹開發模式也并不完全排斥使用分支,比如可以建立以釋出為目的的釋出分支,這樣在執行釋出測試和缺陷修複的時候,主幹就可以進行下一個疊代的開發了,相當于通過并行工作提升了效率;

隐藏未完成的功能。常見的實踐比如後端先行,後端系統可以先開發上線,這時由于沒有前端流量,也可以實作隐藏功能的目的;另外可以引入功能開關(比如google的gflags,java領域的togglz等),通過配置來控制功能對使用者的可見性。但需要注意的是,使用開關也是有成本的,包括加開關和清理開關的成本、規則的制定和遵循等;

有時還需要從架構的角度考慮解決方案,比如将大的系統分解為一系列小的元件或服務,對不同修改頻率的部分進行解耦,每個元件或服務獨立開發和部署,這是處理複雜系統比較好的方式;

另外需要強調,主幹開發模式非常強調代碼送出習慣,包括頻繁、有規律的代碼送出(比如每人每天送出一次),而送出前需要進行充分的本地驗證和預測試,以保證送出品質,降低污染主幹代碼的機率。

2、使用高效的版本控制系統

版本控制系統是軟體研發和運維過程中最常見的工具之一,經過多年的發展,湧現了很多優秀的開源工具或商業軟體。目前公司内部正經曆将已服務的多年的版本控制系統svn加速轉化為分布式版本控制系統git的過程,這也與整個業界的趨勢保持一緻。

百度資深靈活教練:深度解析持續傳遞之全面配置管理

相比于svn,git在功能和效率方面擁有衆多優勢,比如對離線代碼庫的操作、本地分支的管理、本地多任務并行開發、分支建立/切換/合并/diff的效率等等,大家應該都比較熟悉,我們就不詳細展開介紹了。在這裡我們重點強調在其之上的代碼托管平台。

常見的git代碼托管平台包括github、gitlab等,基本都是依托于git代碼庫的底層能力,在其基礎上提供一套web界面,以可視化的方式管理代碼送出、拉分支等常用操作,并且提供wiki用于存儲文檔,提供內建的問題跟蹤器等。當然,這裡面還有一個我最看重的功能,就是對開發協作工作流的支援。近兩年公司内部也基于業界實踐和一些開源工具,開發了自己的git代碼托管平台,對于研發效率的提升作用是非常顯著的。

我們再回頭來看一下剛才介紹過的主幹開發模式,在git工具及其托管平台中的落地執行個體。

百度資深靈活教練:深度解析持續傳遞之全面配置管理

上圖展示了在git代碼托管平台上,一種基于主幹開發的協作流程。其中标記了十多個相關活動,分别由開發人員、測試人員和自動化執行的job協作完成。首先是開發人員通過git clone方式将代碼檢出到本地,并進行開發和送出(git add、git commit),在經過了充分的本地建構和驗證(local build)之後,将代碼push到refs/for/master分支。代碼變更的推送(change request)首先後觸發一系列靜态檢查(比如代碼規範、安全政策檢查等),通過後進一步觸發一個自動化測試的job(比如編譯和單元測試),都通過後需要進行人工的code review。在人工評審确認代碼無問題後,由具備子產品owner權限的tech lead一鍵式合入master branch。同樣,代碼的合入會觸發主幹上的一系列自動化測試job,以及随代碼送出或每日定時執行的內建測試作業。在準備釋出時,會拉出釋出分支進行釋出前最後的測試。此時如果主幹上送出了其它重要功能或者重大缺陷修複,則可以cherry-pick到釋出分支上,最終從釋出分支完成代碼的釋出操作。

那麼,git及其代碼托管平台在其中發揮哪些作用呢?我們來梳理一下:

對于操作的簡化。比如通過界面進行代碼庫的建立、權限的設定、代碼庫的搜尋、一鍵拉分支、一鍵合并代碼、比較代碼、沖突顯示和解決等;

對于代碼送出規則的設定和校驗。比如可以設定送出前必須經過代碼規範檢查,送出後必須經過自動化測試驗證和code review檢查等;

與研發相關系統的打通。打通code review系統、需求管理系統(代碼與需求建立映射關系)、編譯系統(用于擷取代碼并編譯和打包)、自動化測試系統等。系統的打通非常重要,比如code review系統如果不能和代碼托管平台有機整合,相關評審制度就很容易堅持不下來。google對代碼評審要求就非常嚴苛,他們的做法就是通過系統無縫對接,從技術上保證了隻有評審通過的代碼才能被送出進代碼倉庫。

對于分支的關系智能計算和提示。比如計算多個分支之間的版本差異和代碼差異,以及在主幹版本領先于釋出分支時,提示分支需要合并最新主幹代碼後才能釋出等;

還有最重要的,對于協作流程的支援和定制。以上展示了一種主幹開發模式的協作工作流,實際項目中常用的還有git flow工作流、pull request工作流、feature branch工作流等等,這些不同的工作流也許是基于某些特定場景的最優選擇,那麼最好都可以在git代碼托管平台上進行支援和定制。

通過以上分析可以看出,使用高效的版本控制系統,配合可進行協作流程支撐的代碼托管平台,對于配置管理的優化、讓複雜場景以最小代價實作,其作用是非常顯著的。

3、對建構産物及其依賴進行管理

持續傳遞強調要對所有内容進行版本控制,除了對源代碼、測試代碼等配置項做好管理,還有一點非常重要,就是對建構産物進行有效管理。建構産物一般是指在編譯或打包階段,生成的可用于部署的二進制包。一般情況下,我們不推薦将建構産物存放在版本控制庫中,因為這樣做效率較低也确實沒有必要。通常我們使用的開源制品庫包括nexus或artifactory,或者自開發的制品庫(最簡單的可能就是一個共享存儲),通過版本辨別等資訊對建構産物進行管理。

制品庫在應用時有一些有用的實踐:

在編譯階段建立二進制部署包,并上傳到制品庫。為了保證一緻性,二進制部署包一般隻在編譯階段生成一次,後面的所有測試和部署上線都複用這個包,而不要重複編譯;

制品庫可用來管理依賴,比如java領域的maven,python領域的pypi等,都提供了全面的依賴管理機制,可以按gav三元組(groupid/artifactid/version)唯一辨別和引用一個對象,這樣當建構項目時,會自動先下載下傳指定的依賴;

制品庫可以按用途分為臨時制品庫和正式制品庫,其中的制品儲存周期可以不同。一般來講,在傳遞流水線中進行釋出時,會執行以下三個動作:(1)制定釋出的四位版本号;(2)将目前代碼按版本打tag;(3)将二進制包從臨時制品庫遷移到正式産品庫。

四、應用的配置管理

1、部署包的結構和應用配置

為了能夠傳遞可正常運作的系統,我們需要把一切應用程式需要的内容進行标準化,并且注入到部署包中。作為應用程式正常運作的關鍵因素,應用的配置與程式檔案、資料和各類部署和控制腳本缺一不可。

百度資深靈活教練:深度解析持續傳遞之全面配置管理

上圖展示了某種部署包的結構。如果産品線需要部署的伺服器規模比較大,我們就需要通過标準化部署包的封裝,幫助我們實作部署過程的全自動化。比如大家熟悉的docker,它為什麼能夠做到build once,run anywhere?本質上就是做了封裝,把應用程式和相關依賴打在一起,生成一個鏡像去部署。我們可以參考這種方式,把部署包設計為一個全量包,它不僅包含了二進制的可執行程式檔案,還包含應用的配置、子產品資料,也同時包含運作時依賴。我們把它打包一起,這樣才能做到在任何一個标準化的環境裡面,能夠快速的将應用部署起來。另外,在部署包中還需要提供一個穩定的控制接口,用來描述程式怎麼啟動、怎麼停止、怎麼重新開機,怎麼監控健康狀況,告知部署系統如何進行部署和運維等。

接下來我們重點談一下應用配置相關的問題。

2、應用配置的注入方式

我們經常遇到的問題是,應用配置在不同環境中是不同的,比如資料庫的ip位址和端口、是否打開cache、加密所用的密鑰等,這些參數與應用程式邏輯無關,而隻與環境相關。那麼同一個部署包如何适配不同環境所需的應用配置呢?為了解答這個問題,我們先來看一下應用配置資訊的幾種注入方式。

百度資深靈活教練:深度解析持續傳遞之全面配置管理

如上圖所示,應用配置的注入一般有三種方式:

打包時注入

在打包的時刻,建構腳本可以将配置資訊與二進制檔案一起,注入到部署包中。比如j2ee規範中就要求配置資訊與應用程式要一起打包到war或ear包中。

場景:少量靜态配置檔案,或配置每次随二進制程式變更

優點:打包和部署的過程比較簡單,不同環境使用特定的部署包

缺點:環境、應用、配置緊耦合,靈活性低

部署時注入

在進行部署時,部署腳本擷取基礎配置以及不同環境特定的配置項,動态生成每個環境所需的配置資訊。相當于基于配置模闆檔案,在部署時再執行個體化為将要部署應用的具體環境的配置。

場景:大量的配置項,一些配置項在不同環境中存在差異

優點:可以使用部署腳本或工具,動态生成特定環境配置資訊

缺點:需要維護應用與配置版本的比對關系,增加了複雜度

運作時拉取

在應用應用啟動或運作時,通過外部的配置服務拉取應用配置(如通過rest風格的接口)。現在很多使用容器部署的微服務架構系統,配置中心或配置服務都是其非常核心的元件之一。

場景:頻繁變更配置項,或者需要動态加載應用配置

優點:部署包與配置徹底解耦,具備較高的靈活性

缺點:需要考慮配置中心的高可用性和配置變更的原子性

從持續傳遞的理念來看,不推薦在打包時注入配置的方式,因為我們希望在不同的環境中使用相同的部署包,以確定釋出的版本就是我們充分測試過的。在一些團隊中曾經發現,為了适配測試環境,測試人員使用源代碼自己進行編譯和打包,那麼即使這個版本測試通過了,也無法確定生産環境部署的應用版本是沒有問題的。是以我們更傾向于對應用配置單獨進行版本控制,并獨立于部署包之外進行管理。

3、部署時注入配置的技術

部署時注入配置的技術,從實作原理上就是把通用的配置資訊作為預設配置項,然後定義一系列占位符,用于替代那些特定環境有差異的配置項。然後在部署時,通過适當的方法用實際的配置項覆寫掉這些占位符。但需要注意的是,要盡量減少差異化的配置項,隻保留與應用系統運作環境緊密相關的配置項。并且,最好能對配置項的覆寫過程進行校驗,防止因配置失誤導緻整個部署失敗。

有一個經常使用的開源工具autoconfig,類似于maven filtering的工作方式,該工具與應用所采用的技術、架構完全無關,對應用完全透明,具有良好的通用性。值得一提的是,這個工具成功解決了maven filtering在替換配置時需要重新build的問題,即不需要重新擷取源碼并build,就可改變目标檔案中所有配置占位符的值,達到部署時動态修改配置的目的。詳細介紹如下表所示:

百度資深靈活教練:深度解析持續傳遞之全面配置管理

在公司内部,針對一些定制化的場景,我們也自己開發了配置注入的相關工具,從實作的原理和效果來看都是比較類似的。

百度資深靈活教練:深度解析持續傳遞之全面配置管理

五、環境的配置管理

上面我們介紹過,持續傳遞的産出應該是可正常運作的應用系統而不僅僅是可工作的軟體,系統運作起來除了應用程式本身,還依賴于硬體、作業系統、中間件,以及各種庫檔案等。前面介紹了分支和代碼、建構産物、應用配置的管理,下面我們重點介紹環境的配置管理。

在環境配置管理的領域,随着所管理伺服器的規模不斷增長以及業界新技術和新工具的應用,其發展過程中一般會經曆以下三種模式:

百度資深靈活教練:深度解析持續傳遞之全面配置管理

雪花片伺服器

這可能是很多公司都經曆過的,比較原始的伺服器管理狀态。任何一片雪花看起來差不多,但實際上細節都是不一樣的。對于伺服器的管理也是這樣,我們的伺服器經過長年累月的運作,各種作業系統和軟體的更新、更新,以及手工執行的各種黑科技的更新檔,都會造成伺服器間有細微的差别。如果某一天一台伺服器當機了,其實很難再建出一台一模一樣的出來,因為配置過程已經無從追溯了。是以這種模式的主要問題是:反複修改帶來不确定性和風險,環境的重建困難。

自動化、配置化的環境管理

上圖中在這個模式下列舉了很多環境配置管理工具,如puppet、chef、ansible、saltstack等,它們能夠以自動化的方式管理作業系統及其之上的整個運作時環境。通過這些工具,可以用易于了解、聲明式的方式定義環境中需要安裝什麼軟體、啟動什麼服務、修改什麼配置等等,這些聲明具備幂等性的特點,反複運作是安全的。并且,我們可以将這些聲明式的定義儲存在版本控制庫中,這樣就記錄了每次變更的完整過程,相當于對環境的所有修改具備了完整可追溯的能力。下表對這幾款常見的環境配置管理工具進行的簡單對比。

百度資深靈活教練:深度解析持續傳遞之全面配置管理

不可變伺服器(immutable server)

随着容器技術的廣泛應用,很多應用的部署演進到了不可變伺服器的模式,就是任何的變更都打在鏡像裡面,用鏡像進行各級測試,最終通過測試後使用鏡像上線,上線後不做任何變更,直到被新的執行個體替換掉。這種模式更這也符合持續傳遞原則:隻建構一次,在所有環境運作。docker提供了一種應用封裝的标準格式,可以用來建立在開發、測試和生産中可重用的容器,并且在單機層面上實作了輕量級的資源隔離,而在管理大型伺服器叢集時,可以應用如docker swarm、kubernetes、mesos+marathon等叢集管理系統。關于環境管理的話題本文中就暫不展開了,有機會我在持續傳遞模型中另外一個關鍵實踐『環境管理』中再做詳細介紹。

感謝你堅持看到了這篇文章的最後:)

總結一下,剛才我們重點介紹了持續傳遞模型諸多實踐的其中一個:『全面配置管理』,分别從代碼和建構産物的配置管理、應用的配置管理、環境的配置管理三個層面進行了展開說明。配置管理非常重要,隻有配置管理做到位了,後續的建構管理、持續內建、測試管理、環境管理、部署管理以及整個傳遞流水線才能夠比較順利地建設起來。而配置管理想要做到位,不僅僅是使用工具,還在于對上述介紹的一系列實踐的落地和堅持。

希望本文對大家奠定持續傳遞的實施基礎,以及未來真正走上持續傳遞之路能有所幫助,後面有機會再跟大家繼續分享持續傳遞模型中的其他相關實踐。

<b></b>

<b>本文來自雲栖社群合作夥伴"dbaplus",原文釋出時間:2016-10-28</b>