1. 程式人生 > >持續集成與持續部署寶典Part 1:將構建環境容器化

持續集成與持續部署寶典Part 1:將構建環境容器化

成熟 curl命令 設置 doc 包括 探討 完成 2.7 mage

介 紹


隨著Docker項目及其相關生態系統逐漸成熟,容器已經開始被更多企業用在了更大規模的項目中。因此,我們需要一套連貫的工作流程和流水線來簡化大規模項目的部署。在本指南中,我們將從代碼開發、持續集成、持續部署以及零停機更新幾個方面進行介紹。在大型組織中,這已是相當標準的工作流;但在本系列文章中,我們會更著重於探討在容器時代,如何在基於Docker的環境中復制這些工作流。另外,我們還將詳細介紹如何利用Docker和Rancher自動化處理這些工作流。在本指南中,我們提供了每個步驟的詳細示例,幫助你實現自己的CI系統。


我們希望你通過該指南,能夠提取到其中的一些想法,利用諸如Docker和Rancher這類工具來創建屬於你們企業的持續集成和持續部署流水線,並根據自己的實際情況和需求在這CI/CD流水線中也加入自定義的流程。


在我們開始之前,還有一些需要註意的事項:因為Docker和Rancher的版本更叠都非常快,可能會出現一些在不同版本的平臺上API和實現不一致的情況。作為參考,我們在指南中的工作環境是:Golang 1.8,Docker 1.13.1+,Jenkins Version 2.32.2,docker-compose 1.11.1+ 以及Rancher 1.4.1+。


第一部分:持續集成


那麽踏出第一步,我們先從流水線的入口開始,即構建源代碼。在任何項目開始時,構建/編譯並不會是什麽麻煩的問題,因為大多數語言和工具都有定義良好且記錄詳細的編譯源代碼過程。但是隨著項目和團隊在規模上的擴大以及依賴關系的增加,在確保代碼質量的同時,如何為所有開發人員提供一致且穩定的構建,會逐漸成為一個更大的挑戰。在本節中,我們將會介紹一些常見的挑戰、最佳實踐以及如何通過Docker來實現持續集成。

技術分享圖片

擴展構建系統的挑戰


在分享最佳實踐之前,讓我們看看在維護構建系統中常出現的一些挑戰。

技術分享圖片

首先,在擴展項目時你會面臨的第一個問題就是Dependency Management(依賴管理)。開發人員會從庫中拉取代碼並和源代碼進行集成,如此一來,跟蹤代碼所使用的每個庫的版本,確保項目所有部分都使用相同的版本,測試庫版本的升級並把通過測試的更新push到你全部的項目中,這些過程都變得非常重要。


其次,管理環境依賴是一個和依賴管理相關但又有一些不同的問題。它包括IDE和IDE配置、工具版本(如Maven版本、Python版本)和工具配置(如靜態分析規則文件、代碼格式化模版)。因為項目的不同部分可能相互間有需求沖突,環境依賴管理會變得非常棘手。和代碼層面的依賴沖突不同,想要解決這些沖突往往是非常困難甚至是不可能的。比如,在最近的一個項目中,我們使用fabric進行自動化部署,使用s3cmd將工件上傳到Amazon S3。不幸的是,fabric的最新版本需要Python 2.7,而s3cmd需要使用Python 2.6。修復程序需要我們切換到s3cmd的測試版本或者使用舊版的fabric。


最後,對每個大型項目來說,它們主要面臨的是構建時間問題。隨著項目範圍和復雜度增加,越來越多的語言添加了進來。同時,項目團隊還需要為各種相互依賴的組件進行測試。例如,如果你有一個共享數據庫,那麽改變相同數據的測試是不能夠同時執行的。此外,我們需要在測試執行之前設置預期狀態,並能在完成後自行清理。如此一來,所有這一切可能需要幾分鐘到幾個小時的時間進行構建,充分測試意味著會大大減慢開發速度,但如果跳過測試又有可能出現嚴重的問題。


解決方案和最佳實踐


為了解決所有這些問題,就需要我們有一個支持下列需求的構建系統:


可重復性

我們必須能夠在不同的開發機器和自動構建服務器上生成/創建出有同樣依賴關系的、相似(或相同)的構建環境。


集中管理

我們必須能夠控制所有開發人員的構建環境,並從中央代碼倉庫或服務器構建服務器。這包括了設置構建環境以及更新延時。


隔離

項目的各個子組件必須獨立構建,而不是使用明確定義的共享依賴項。


並行化

我們必須能夠為子組件提供並行化構建。


為了滿足可重復性要求,我們必須使用集中式依賴管理。大多數現代語言和開發框架都支持自動依賴管理。Maven廣泛用於Java和其他幾種語言,Python使用pip,Ruby使用Bundler。所有這些工具都有一個非常相似的樣式,你可以commit索引文件(pom,xml, requirements.txt或者gemfile)到你的源碼控制中。然後運行該工具,把依賴下載到構建機器上。我們可以在測試過它們後,集中管理索引文件,接著通過更新源碼控制中的索引來進行更改。但是,管理環境依賴的問題依然存在,比如我們必須安裝正確版本的Maven、Python和Ruby。我們還需要確保這些工具由開發人員運行。Maven能夠自動檢查依賴項更新,但對於pip和Bundler,我們必須將構建命令包裝在觸發依賴項更新運行的腳本中。


對於設置依賴關系管理工具和腳本,大多數小型團隊只使用文檔,並把任務交給了開發人員。然而這種方法在大型團隊中並不完全適用,特別是當依賴關系會隨時間發生變化時。更復雜的是,根據構建機器的平臺和操作系統不同,這些工具的安裝命令都會發生變化。你可以使用編排工具(比如Puppet或Chef)來管理依賴項的安裝以及設置配置文件。Puppet和Cher都允許在源代碼控制中使用中央服務器或共享配置,來支持集中管理。這樣一來,你就可以提前對配置更改進行測試,然後交給開發人員。但是,這些工具有一些缺點:首先,安裝和配置Puppet或Chef會變得過於重要,而且它們的完整版本都不是免費的。另外,每一種工具都有自己的語言來定義任務,這就為IT團隊和開發人員增加了另一項管理成本。還有一點是,編排工具不提供隔離,因此工具版本的沖突依舊是一個問題,而且執行並行化測試的問題也依然沒有解決。


為了確保組件隔離並且縮短構建時間,我們可以使用自動虛擬化系統,比如Vagrant。Vagrant可以創建並運行虛擬機,這些虛擬機能夠隔離各種組件的構建,而且能支持並行構建。當準備好集中管理時,Vagrant配置文件可以提交到源碼控制中,並且交給開發人員。另外。可以對虛擬機進行測試,將其部署到Atlas,供所有開發人員下載。這樣還是會有缺點,你需要進一步的配置來設置Vagrant,而且在這個問題中,虛擬機是非常重要的解決方案。每個虛擬機運行一個完整的操作系統和網絡堆棧,包含測試運行或者編譯器。內存和磁盤資源需要提前分配給每一臺虛擬機。


盡管存在一些警告和缺陷,但是使用依賴管理(Maven、pip、Bundler)、編排(Puppet、Chef)和虛擬化(Vagrant),我們可以構建一個穩定的、可測試的、集中管理的構建系統。並非所有的項目都需要有完整的工具堆棧;不過,任何長期運行的大型項目都需要這種層面的自動化。


利用Docker創建容器化的構建系統


Docker出現之後,我們可以無需再花費過多時間和資源來支持上文我們提到的這些工具,Docker及其工具生態系統就可以幫助我們滿足上述的需求。在本節中,我們將通過下面的步驟為應用程序創建容器化構建環境。

技術分享圖片

1. 將你的構建環境容器化

2. 用Docker將你的應用程序打包起來

3. 使用Docker Compose創建構建環境


我們使用一個叫做go-messenger的實例應用程序來說明如何在構建流水線中使用Docker,後面章節也會用到它。你可以從Github中獲取這個應用程序:

https://github.com/usmanismail/go-messenger/tree/golang-1.8

系統的主要數據流如下所示。該應用程序有兩個組件:一個是用Golang便攜的RESTful認證服務器,另一個是會話管理器,它接受來自客戶端的長時運行TCP連接並在客戶端之間路由消息。回到本文的目標,我們將重點介紹RESTful認證服務(go-auth)。這個子系統包含了一組無狀態網絡服務器以及一個數據庫集群,用於存儲用戶信息。

技術分享圖片


將你的構建環境容器化


建立構建系統的第一步,是創建一個容器鏡像,其中包含了構建項目所需的全部工具。我們鏡像的Docker文件如下圖所示。因為我們的應用程序是用Go語言編寫的,所以使用的是官方的golang鏡像,並且安裝了govendor依賴管理工具。需要註意的是,如果你在自己的項目中使用的是Java語言,那麽可以用Java基礎鏡像創建一個類似的“構建容器”,並安裝Maven替代govendor。

技術分享圖片

然後我們添加了一個編譯腳本,將構建和測試我們代碼的所有步驟集中到了一塊。下面所示的腳本使用了govendor restore下載依賴項,通過go fmt命令標準化格式,用go test命令執行測試,接著使用了go build來編譯項目。

技術分享圖片

為確保可重復性,我們可以使用Docker容器以及一切需要的工具,將組件構建成一個單一的、版本化的容器鏡像。該鏡像可從Dockerhub上下載,也可以使用Dockerfile構建(docker build -t go-builder:1.8)。到這為止,所有的開發人員(以及構建環境的機器)都可以通過下面的命令,來使用容器構建任何的go項目:

技術分享圖片

上面的命令中我們運行了usman/go-builder鏡像的1.8版本,並使用-v將我們的源代碼安裝到了容器中,使用-e指定了SOURCE_PATH環境變量。如果想要在我們的示例項目中測試go-builder,你可以使用下面的命令運行全部步驟,並在go-auth項目的根目錄中創建一個名為go-auth的可執行文件。

技術分享圖片

將所有源從構建工具中隔離開來後,產生的一個有趣的副產物是,我們可以輕松地更換構建工具和配置。例如,在上面的命令中,我們使用了golang 1.8。把go builder:1.8改成go builder:1.5,你就可以測試使用golang 1.5時對項目的影響。為了集中管理所有開發人員使用的鏡像,我們可以將構建容器(builder container)的最新測試版本部署到一個固定版本(即最新版本),並確保所有開發人員都使用了go-builder:latest構建源代碼。同樣地,如果我們項目中不同部分使用了不同版本的構建工具,我們可以使用不同的容器來構建他們,而無需擔心在單個構建環境中管理多個語言版本的問題。例如,我們可以使用支持各種python版本的官方python鏡像來減輕早期的python問題。


用Docker打包你的應用程序


如果你想將可執行文件打包到自己的容器中,那麽需要先添加一個dockerfile文件,包含下面顯示的內容,接著運行“docker build -t go-auth”。在dockerfile中,我們將最後一步的二進制輸出添加到一個新容器中,並將9000端口公開給應用程序以便接受傳入的連接。我們還指定了運行二進制文件的入口點,該入口點使用了給定的參數。由於Go的二進制文件是自包含(self-contained)的,因此我們使用了原版的Ubuntu鏡像。不過如果你的項目需要運行時(run time)依賴項,那麽也可以將它們打包到容器中。例如,如果你準備生成一個war文件,你可以使用tomcat容器。

技術分享圖片


使用Docker Compose創建構建環境


現在我們可以在集中管理的容器中重復構建項目了,該容器隔離了各種組件,我們還可以擴展構建管道來運行集成測試。這也充分展示了Docker在使用並行化時加速構建的能力。而測試不能並行化的一個主要原因是共享數據存儲。對於集成測試來說尤為如此,因為我們通常不會去模擬外部數據庫。我們的示例項目也有類似的問題,因為我們使用了MySQL數據庫來存儲用戶。我們想編寫一個測試,確保我們可以註冊新用戶。而第二次為同一用戶進行註冊時,我們期望會發生沖突錯誤。這讓我們不得不對測試進行序列化,這樣我們在測試完成後就可以清除註冊用戶,然後再開始新的測試。


要想設置隔離的、並行的構建,我們可以按如下的方式定義一個Docker Compose模板(docker-compose.yml)。我們定義了一個數據庫服務,它使用MySQL官方鏡像以及需要的環境變量。然後我們使用自己創建的容器,創建一個GoAuth服務來打包應用程序,並將其與數據庫容器連接起來。需要註意的是,這裏我們使用了GO_AUTH_VERSION變量替換。如果在環境中指定了該變量,那麽compose將使用它作為go-auth鏡像的標記,否則會使用默認值latest作為標記。

技術分享圖片

有了這個docker-compose模板,我們可以通過執行docker-compose up來運行應用程序環境。然後運行下面的curl命令來模擬我們的集成測試。第一次應該會返回200 OK,而第二次應該返回409 Conflict。如果你是在Linux上運行,則service_ip參數應該是localhost,而如果你使用的是OSX,那麽參數應該是Docker虛擬機的IP。想要查找service_ip你可以運行:

技術分享圖片

最後,在運行完測試之後,我們可以運行docker-compose rm來清理整個應用程序環境。


如果想要運行多個獨立版本的應用程序,我們需要更新docker-compose模板來,將服務database1和goauth1以相同的配置添加到其對應項中。唯一的變化是在Goauth1中,我們需要將9000:9000端口條目改變為9001:9000。這樣應用程序公開的端口就不會發生沖突。完整的模板在這裏。現在運行docker-compose時,可以並行運行兩個集成測試。像這樣的東西可以有效地用於為一個具有多個獨立子組件的項目加速構建,例如,多模塊的Maven項目。

技術分享圖片


總 結


在本文中,我們開始了構建持續集成流水線的第一步工作——構建系統(Build System)的創建。我們分析了【Build】這一環節的常見的三大挑戰——依賴管理、管理環境依賴、復雜項目的漫長構建時間,以及如何用傳統工具與方法解決這些問題。接著,我們分享了如何利用Docker創建容器化的構建系統以更輕松地解決那些傳統挑戰,包括如何將構建環境容器化、如何使用Docker打包應用程序、如何使用Docker Compose創建構建環境,最終創造一個可重復的、集中管理的、良好隔離的、並行化的構建系統。


在下一篇文章中,我們將分享如何創建一個持續集成的流水線,內容將包含分支模式以及如何使用Jenkins創建CI流水線,將涉及到構建應用、打包應用、執行集成測試等技術細節內容。


本系列文章計劃分為四篇,共兩萬多字,後續文章將陸續在Rancher微信公眾號發布,記得保持關註~


持續集成與持續部署寶典Part 1:將構建環境容器化