兩萬餘字的系列乾貨長文,攻下持續集成與持續部署!python
本文爲第一篇,開始持續集成流水線的第一步工做——構建系統(Build System)的建立!看如何利用Docker建立容器化的構建系統,解決常見的三大挑戰——依賴管理、管理環境依賴、複雜項目的漫長構建時間,最終創造一個可重複的、集中管理的、良好隔離的、並行化的構建系統。git
介 紹github
隨着Docker項目及其相關生態系統逐漸成熟,容器已經開始被更多企業用在了更大規模的項目中。所以,咱們須要一套連貫的工做流程和流水線來簡化大規模項目的部署。在本指南中,咱們將從代碼開發、持續集成、持續部署以及零停機更新幾個方面進行介紹。在大型組織中,這已經是至關標準的工做流;但在本系列文章中,咱們會更着重於探討在容器時代,如何在基於Docker的環境中複製這些工做流。另外,咱們還將詳細介紹如何利用Docker和Rancher自動化處理這些工做流。在本指南中,咱們提供了每一個步驟的詳細示例,幫助你實現本身的CI系統。golang
咱們但願你經過該指南,可以提取到其中的一些想法,利用諸如Docker和Rancher這類工具來建立屬於大家企業的持續集成和持續部署流水線,並根據本身的實際狀況和需求在這CI/CD流水線中也加入自定義的流程。docker
在咱們開始以前,還有一些須要注意的事項:由於Docker和Rancher的版本更迭都很是快,可能會出現一些在不一樣版本的平臺上API和實現不一致的狀況。做爲參考,咱們在指南中的工做環境是:Golang 1.8,Docker 1.13.1+,Jenkins Version 2.32.2,docker-compose 1.11.1+ 以及Rancher 1.4.1+。數據庫
第一部分:持續集成tomcat
那麼踏出第一步,咱們先從流水線的入口開始,即構建源代碼。在任何項目開始時,構建/編譯並不會是什麼麻煩的問題,由於大多數語言和工具都有定義良好且記錄詳細的編譯源代碼過程。可是隨着項目和團隊在規模上的擴大以及依賴關係的增長,在確保代碼質量的同時,如何爲全部開發人員提供一致且穩定的構建,會逐漸成爲一個更大的挑戰。在本節中,咱們將會介紹一些常見的挑戰、最佳實踐以及如何經過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及其工具生態系統就能夠幫助咱們知足上述的需求。在本節中,咱們將經過下面的步驟爲應用程序建立容器化構建環境。
咱們使用一個叫作go-messenger的實例應用程序來講明如何在構建流水線中使用Docker,後面章節也會用到它。你能夠從Github中獲取這個應用程序:
https://github.com/usmanismai...。
系統的主要數據流以下所示。該應用程序有兩個組件:一個是用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微信公衆號發佈,記得保持關注~