本文講的是<b>持續內建案例學習:Docker、Java與Maven</b>,【編者的話】對于使用Java技術棧的企業,Maven往往是其持續內建的核心工具,在目前的Docker化的運動中,要如何把Docker鏡像的建構也加入到傳統的Maven建構為基礎的持續內建流程中呢?Alooma公司在本文中分享了他們使用Maven對Docker鏡像建構進行持續內建的經驗。
在Alooma,我們非常非常非常喜愛Docker。真的, 我們想完全容器化我們的應用。 雖然容器化應用有非常多的好處,但在這裡,我并不是要說服你用Docker。我們隻是認為你和我們一樣喜歡這東西。
接下來,讓我們談談Alooma是如何在生産環境使用Docker來精簡開發流程并快速push代碼的。
Docker允許你把你的基礎架構當作代碼一樣來對待。這個代碼就是你的Dockerfile。
像其它代碼一樣,我們想要使用一個緊密的<code>改變->送出->建構->測試</code>的周期(一個完整的持續內建解決方案)。為了實作這個目标,我們需要建構一個流暢的DevOps流水線。
讓我們把目标分解為更加詳細的需求:
在版本控制系統中管理<code>Dockerfile</code>
在CI伺服器上為每個commit建構Docker鏡像
上傳構件并打标簽(這個構件要能夠簡單的部署)
我們的DevOps流水線圍繞GitHub、Jenkins和Maven建構。下面是它的工作流程:
GitHub将repo的每一個push通知給Jenkins
Jenkins觸發一個Maven build
Maven 建構所有的東西,包括Docker鏡像
最後,Maven會把鏡像推送到私有的Docker Registry。
這個工作流的好處是它允許我們能夠很容易的為每個釋出版本打标簽(所有的commit都被建構并且在我們的Docker Registry中準備好了)。然後我們可以非常容易地通過pull和run這些Docker鏡像進行部署。
事實上這個部署過程是非常簡單的,我們通過發送一個指令給我們信任的Slack機器人:"Aloominion"(關于我們的機器人朋友的更多情況将在未來的文章中發表)開始這個過程。
你可能對這個工作流中的其他元素非常熟悉,因為它們都很常見。是以,讓我們來深入了解如何使用Maven建構Docker鏡像。
Alooma是一個Java公司。我們已經使用Maven作為我們建構流水線的中心工具,是以很自然的想到把建構Docker的過程也加入到我們的Maven建構過程中去。
每一個docker鏡像有它自己的Maven子產品(所有上面提到的docker-maven 插件在一個子產品一個Dockerfile時都能順利地工作)
讓我們從Spotify插件的一個簡單配置開始:
我們看到這裡我們把插件的build目标和Maven的package階段綁定,我們也指導它去在我們子產品的根目錄下來尋找Dockerfile(使用dockerDirectory 元素來指定),我們還把鏡像名稱用它的構件Id來命名(用"alloma/"做字首)。
我們注意到的第一件事情是這個鏡像沒有被push到任何地方,我們可以通過加入<pushImage>true</pushImage>到配置中來解決這個問題。
但是現在這個鏡像會被push到預設的Docker Hub Registry上。糟糕。
為了解決這個問題,我們定義了一個新的Maven屬性<docker.registry>docker-registry.alooma.io:5000/</docker.registry>并且把鏡像名稱<code>imageName</code>改為${docker.registry}alooma/${project.artifactId}。 你可能會想,“為什麼需要為Docker Registry設定一個屬性?”, 你是對的!但是有這個屬性可以使我們在Regsitry URL改變的時候能夠更友善的修改。
有一個更重要的事情我們還沒有處理——我們想讓每一個鏡像用它的git commit ID來打标簽。這可以通過改變imageName為${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}來實作。
${git.commit.id.abbrev}屬性是通過我上面提到的<code>maven-git-commit-id-plugin</code>插件來實作的。
是以,現在我們的插件配置看起來像下面這樣:
我們的下一個挑戰是在我們的<code>pom.xml</code>中表達我們的<code>Dockerfile</code>的依賴。一些我們的Docker鏡像在建構時使用了 <code>FROM</code> 其它的Docker 鏡像作為基礎鏡像(也在同一個建構周期中建構)。例如,我們的<code>webgate</code>鏡像(是我們的機遇Tomcat的WebApp)基于我們的<code>base</code>鏡像(包含Java 8、更新到最新的 apt-get、等等)。
這些鏡像在同一個建構過程中建構意味着我們不能簡單的使用FROM docker-registry.alooma.io/alooma/base:some-tag因為我們需要這個标簽程式設計目前建構的标簽(即 git commit ID)。
為了在<code>Dockerfile</code>中獲得這些屬性,我們使用了Maven的resource filtering功能。這在一個資源檔案中替換Maven 的屬性。
在Dockerfile的内部我們有一個這樣的<code>FROM</code>:
一些更多的事情.......我們需要的是我們的配置來找到正确的Dockerfile(過濾過之後的),這可以在target/classes檔案夾内找到,是以我們把dockerDirectory改為${project.build.directory}/classes。
這意味着現在我們的配置檔案長這樣:
此外,我們還要添加<code>base</code>構件作為<code>webgate</code>子產品的一個Maven依賴來保證正确的Maven建構順序。
為了讓這些檔案可以被擷取,我們需要使用插件配置的<code>resources</code>标簽。
注意到我們排除了一些檔案。
記住這個<code>resources</code>标簽不應該和通常的Maven <code>resources</code>标簽弄混,看看下面的例子,它來自于我們的pom.xml的一部分:
前一個添加在我們想添加一些靜态資源到鏡像時工作,但是如果我們想要添加一個在同一個建構中建構的構件時需要更多的調整。
例如,我們的<code>webgate</code> Docker鏡像包含了我們的<code>webgate.war</code>,這是由另一個子產品建構的。
為了添加這個war作為資源,我們首先必須把它作為我們的Maven依賴加進來,然後使用<code>maven-dependency-plugin</code>插件的<code>copy</code>目标來把它加到我們目前的建構目錄中。
現在這允許我們簡單的把這個檔案加到Docker插件的resources中去。
我們需要做的最後一件事情是讓我們的CI伺服器(Jenkins)真的将鏡像push到Docker Registry上。請記住本地構件預設是不會push鏡像的。
為了push這些鏡像,我們改變我們的<pushImage>标簽的值從<code>true</code>變為<code>${push.image}</code>屬性,這預設是被設定為<code>false</code>,并且隻會在CI伺服器上設定為<code>true</code>。(譯注:這裡的意思是,由于開發人員也要在本地建構然後測試之後才會送出,而測試的鏡像不應該被送出到Registry,是以<pushImage>應該使用一個屬性,預設為false,在CI伺服器上覆寫為true在建構後去push鏡像。)
這就完成了!讓我們看一下最終的代碼:
這個過程有兩個能夠提高你的建構和部署的性能的改進地方:
讓你的基礎的機器鏡像(在EC2的例子下是AMI)包含一些你的Docker鏡像的基礎版本。這樣會使得<code>docker pull</code>隻去pull那些改變了的層,即增量(相對于整個鏡像來說要小得多)。
在Docker Registry的前端放一個Redis緩存。這可以緩存标簽和中繼資料,減少和真實存儲(在我們的例子下是S3)的回環。
我們現在已經使用這個建構過程一段時間了,并且對它非常滿意。然而仍然有提高的空間,如果你有任何關于讓這個過程更加流暢的建議,我很樂意在評論中聽到你的想法。
原文釋出時間為:2015-08-12
本文作者:Casgy
本文來自雲栖社群合作夥伴DockerOne,了解相關資訊可以關注DockerOne。
原文标題:持續內建案例學習:Docker、Java與Maven