上期文章我們介紹了需求分解與應用對應的管理方式,以及送出環節的開發協作模式,今天我們詳細介紹一下送出階段的建構環節,也就是我們經常提到的代碼的編譯打包。
建構環節
由于靜态語言從過程上要比動态語言複雜一些,代碼送出後,對于Java和C++這樣的靜态語言,我們要進行代碼編譯和打包。而對于PHP和Python這樣的動态語言,就不需要編 譯,直接打包即可。
同時,編譯過程就開始要依賴多環境以及多環境下的配置管理,并根據不同的環境擷取不同的配置,然後打包到最終的軟體釋出包中。 下面我就結合自己的實踐經驗,以Java為例,對建構環節做下介紹。
建構過程中我們要用到以下4種工具:
Gitlab,代碼管理工具,也是版本管理工具;
Maven,依賴管理和自動化建構工具,業界同類型的工具還有Gradle等;
Docker,用來提供一個幹淨獨立的編譯環境; 自動化腳本和平台,
自動化建構的任務我們使用Python腳本來實作代碼擷取、編譯執行、軟體包生成等。
具體整個建構過程圖示如下:

我們以Java為例描述如下。
1.首先準備好JDK的編譯鏡像,這個鏡像環境與線上運作環境保持一緻,比如OS版本、核心參數以及JDK版本等基礎環境。當需要啟動一個建構任務時,就建立一個對應 的Docker執行個體,作為獨立的編譯環境。
2.建構任務會根據應用配置管理中的Git位址,将代碼克隆下來放到指定的編譯目錄。Docker執行個體啟動後,将編譯目錄挂載到Docker執行個體中。
3.執行mvn package指令進行編譯打包,最終會生成一個可釋出war的軟體包。同樣的,對于C++、Go、Node.js,也會準備好類似的編譯鏡像。不同的是,打包時,對于C++中 的cmake和make,Go中的go install等等,最終也會生成一個可釋出的軟體包。
4.建構完成後,生成軟體包放到指定構件庫目錄,或者直接釋出到maven的構件庫中管理,然後将Docker執行個體銷毀。 上述就是一個完整的建構過程。在這裡,你一定會有一些疑問,那麼,我先回答幾個比較常見的問題,歡迎你留言和我繼續讨論。 幾個關鍵問題
1.配置檔案如何打包?
這個問題,我們在前面持續傳遞的多環境配置管理文章中,已經詳細介紹過。這裡我們結合建構過程,再介紹一下。
在上述第3個步驟中,我們要進行代碼編譯。按照持續傳遞理念,軟體隻需打包一次就可以各處運作,這對于代碼編譯是沒有問題的,但是對于一些跟環境相關的配置就無法滿足。
比如,我們前面講到,不同的環境會涉及到不同的配置,如DB、緩存。而且,其他公共基礎服務在不同環境中也會有不同的位址、域名或其他參數配置。
是以,我們就需要建立環境與配置之間的對應關系,并儲存在配置管理平台中,至于如何來做,大家可以參考前面多環境配置管理的文章。
這裡我們回到打包過程上來。
在做建構時,我們是可以确認這個軟體包是要釋出到哪個環境的。比如,按照流程,目前處于線下內建測試環境這個流程環節上,這時隻要根據內建測試環境對應的配置項,生成配置檔案,然後建構進軟體包即可。如果是處于預發環境,那就生成預發環境對應的配置檔案。
在我們的實際場景中,多個環境需要多次打包,這與我們持續傳遞中隻建構一次的理念相悖。這并不是有意違背,而是對于Java建構出的傳遞件,最終無論生成的是war包,還 是jar包,上述提到的跟環境相關的配置檔案,是要在建構時就打入軟體包中的。
而且在後續啟動和運作階段,我們是無法修改已經建構進軟體包裡的檔案及其内容的。這樣一來,配置檔案無法獨立釋出,那麼就必須跟軟體包一起釋出。是以,在實際場景下,我們要針對不同環境多次打包。
那麼,我們如何確定多次打包的效果能夠和“隻建構一次”理念的效果相一緻呢? 這就還是要依賴我們前面介紹的各個環節的建設過程,主要有以下3個方面:
代碼送出。通過分支送出管理模式,每次建構都以master為基線,確定合入的代碼是以線上運作代碼為基礎的。且前面的釋出分支代碼未上線之前,後續分支不允許進入線上發 布環節,確定釋出分支在多環境下是同一套代碼。
編譯環境統一。上述過程已經介紹,編譯環境通過全新的Docker容器環境來保證。
配置管理。前面介紹到的多環境配置管理手段, 通過模闆和auto-confg的配置管理能力,確定多環境配置項和配置值統一管理。
至此,一個完整的軟體建構過程就完成了。可以看到,如果充分完善前期的準備工作,在做後期的方案時就會順暢很多。
2.為什麼用Docker做編譯環境的工具?
Docker容器很大的一個優勢在于其建立和銷毀的效率非常高,而且每次新拉起的執行個體都是全新的,消除了環境共用帶來的交叉影響。而且對于并發打包的情況,Docker可以快速創 建出多個并行的執行個體來提供編譯環境,是以無論在效率上還是環境隔離上,都有非常好的支援。
你可以嘗試一下我的這個建議,确實會非常友善。
3.為什麼不直接生成Docker鏡像做釋出?
在使用Docker容器做編譯的過程中,我們最終取得的傳遞件模式是一個war包,或者是一個jar包,這個也是我們後續釋出的對象。
可能有讀者會問:為什麼不直接生成Docker鏡像,後續直接釋出鏡像?
這确實是一個好問題。如果單純從釋出的次元來看,直接釋出鏡像會更友善,更高效。不過,在現實場景下,我們應該更全面地看問題。
早期我們曾有一段時間使用OpenStack+Docker的模式進行實體機的虛拟化,以提升資源使用率。這實際上是将容器虛拟機化。
也就是說,雖然Docker是一個容器,但是我們的使用方式仍然是虛拟機模式,要給它配置IP位址,要增加很多常用指令比如top、sar等等,定位問題需要ssh到容器内。
這裡一方面是因為基于Docker的運維工具和手段沒有跟上,當時也缺少Kubernetes這樣優秀的編排工具;另一方面,我們之前所有的運維體系都是基于IP模式建設的,比如監控、 釋出、穩定性以及服務發現等等,完全容器化的模式是沒有辦法一步到位的。
是以,這裡我們走了個小彎路:容器虛拟機化。那為什麼我們不直接使用虛拟機,還能幫我們省去很多為了完善容器功能而做的開發工作?是以一段時間之後,我們還是回歸到 了KVM虛拟機使用方式上來。
這樣也就有了上述我們基于虛拟機,或者更準确地說,是基于IP管理模式下的持續傳遞體系。 經過這樣一個完整的持續傳遞體系過程後,我們總結出一個規律: