[Docker-集成] Docker、Maven、Java

問題導讀

1.如何使用Maven構建Docker鏡像?
2.每一個docker鏡像是否有有它自己的Maven模塊?
3.你認爲什麼是Dockerfile?



概述

Docker允許你把基礎架構當作代碼一樣來對待。這個代碼就是你的Dockerfile。
像其它代碼一樣,我們想要使用一個緊密的改變->提交->構建->測試的週期(一個完整的持續集成解決方案)。爲了實現這個目標,我們需要構建一個流暢的DevOps流水線。
讓我們把目標分解爲更加詳細的需求:
  • 在版本控制系統中管理Dockerfile
  • 在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鏡像。

深入Docker 構建


Alooma是一個Java公司。我們已經使用Maven作爲我們構建流水線的中心工具,所以很自然的想到把構建Docker的過程也加入到我們的Maven構建過程中去。

當搜索和Docker交互的Maven插件時,出現了3個選項。我們選擇使用Spotify的maven-docker-plugin —— 雖然rhus的和alexec的同名插件看起來也是一個不錯的選擇。

另一個我們的構建計劃依賴的Maven插件是maven-git-commit-id-plugin。我們使用這個插件,所以我們的Docker鏡像能使用git的commit ID來打標籤 —— 這在部署過程中非常有幫助,我們可以瞭解運行的是哪個版本。


給我看代碼!

每一個docker鏡像有它自己的Maven模塊(所有上面提到的docker-maven 插件在一個模塊一個Dockerfile時都能順利地工作)

讓我們從Spotify插件的一個簡單配置開始:

[Bash shell] 純文本查看 複製代碼
?
1
2
3
4
5
6
7
8
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
    ........
    <dockerDirectory>${project.basedir}</dockerDirectory>
    <imageName>alooma/${project.artifactId}</imageName>
</configuration>
</plugin>


我們看到這裏我們把插件的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>並且把鏡像名稱imageName改爲${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}屬性是通過我上面提到的maven-git-commit-id-plugin插件來實現的。

所以,現在我們的插件配置看起來像下面這樣:

[Bash shell] 純文本查看 複製代碼
?
1
2
3
4
5
6
7
8
9
<artifactId>docker-maven-plugin</artifactId>
.......
    <dockerDirectory>${project.basedir}</dockerDirectory>
    <imageName>
        ${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}
    </imageName>
    <pushImage>true</pushImage>
</configuration>
</plugin>


我們的下一個挑戰是在我們的pom.xml中表達我們的Dockerfile的依賴。一些我們的Docker鏡像在構建時使用了 FROM 其它的Docker 鏡像作爲基礎鏡像(也在同一個構建週期中構建)。例如,我們的webgate鏡像(是我們的機遇Tomcat的WebApp)基於我們的base鏡像(包含Java 8、更新到最新的 apt-get、等等)。

這些鏡像在同一個構建過程中構建意味着我們不能簡單的使用FROM docker-registry.alooma.io/alooma/base:some-tag因爲我們需要這個標籤編程當前構建的標籤(即 git commit ID)。

爲了在Dockerfile中獲得這些屬性,我們使用了Maven的resource filtering功能。這在一個資源文件中替換Maven 的屬性。
[Bash shell] 純文本查看 複製代碼
?
1
2
3
4
5
6
7
<resource>
<directory>${project.basedir}</directory>
<filtering>true</filtering>
<includes>
    <include>**/Dockerfile</include>
</includes>
</resource>


在Dockerfile的內部我們有一個這樣的FROM:
[Bash shell] 純文本查看 複製代碼
?
1
FROM ${docker.registry}alooma/base:${git.commit.id.abbrevs}


一些更多的事情…….我們需要的是我們的配置來找到正確的Dockerfile(過濾過之後的),這可以在target/classes文件夾內找到,所以我們把dockerDirectory改爲${project.build.directory}/classes。

這意味着現在我們的配置文件長這樣:

[Bash shell] 純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<resources>
<resource>
    <directory>${project.basedir}</directory>
    <filtering>true</filtering>
    <includes>
        <include>**/Dockerfile</include>
    </includes>
</resource>
</resources>
<pluginManagement>
<plugins>
    <plugin>
        ........
            <imageName>
                ${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}
            </imageName>
        </configuration>
    </plugin>
</plugins>
</pluginManagement>



此外,我們還要添加base構件作爲webgate模塊的一個Maven依賴來保證正確的Maven構建順序。

但是我們還有另一個挑戰:我們如何把我們編譯和打包了的源文件添加到我們的Docker鏡像中呢?我們的Dockerfile依賴於很多其它文件,它們通過ADD或COPY命令插入。[你可以在這裏(https://docs.docker.com/reference/builder/)讀到更多的關於Dockerfile的指導。]

爲了讓這些文件可以被獲取,我們需要使用插件配置的resources標籤。

[Bash shell] 純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
<resources>
<resource>
    <targetPath>/</targetPath>
    <directory>${project.basedir}</directory>
    <excludes>
        <exclude>target/**/*</exclude>
        <exclude>pom.xml</exclude>
        <exclude>*.iml</exclude>
    </excludes>
</resource>
</resources>


注意到我們排除了一些文件。

記住這個resources標籤不應該和通常的Maven resources標籤弄混,看看下面的例子,它來自於我們的pom.xml的一部分:

[Bash shell] 純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<resources>           
<resource>
......
    <includes>
        <include>**/Dockerfile</include>
    </includes>
........
<plugins>
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>0.2.3</version>
.....
    </plugin>
</plugins>
</pluginManagement>



前一個添加在我們想添加一些靜態資源到鏡像時工作,但是如果我們想要添加一個在同一個構建中構建的構件時需要更多的調整。

例如,我們的webgate Docker鏡像包含了我們的webgate.war,這是由另一個模塊構建的。

爲了添加這個war作爲資源,我們首先必須把它作爲我們的Maven依賴加進來,然後使用maven-dependency-plugin插件的copy目標來把它加到我們當前的構建目錄中。
[Bash shell] 純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
<plugin>
<groupId>org.apache.maven.plugins</groupId>
.......
                    <groupId>com.alooma</groupId>
                    <artifactId>webgate</artifactId>
                    <version>${project.parent.version}</version>
                    <type>war</type>
                    <outputDirectory>${project.build.directory}</outputDirectory>
                    <destFileName>webgate.war</destFileName>
.......
</plugin>


現在這允許我們簡單的把這個文件加到Docker插件的resources中去。

[Bash shell] 純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<resources>
........
        <include>**/Dockerfile</include>
    </includes>
........
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>0.2.3</version>
        <executions>
........
        </executions>
.........
</plugins>
</pluginManagement>



我們需要做的最後一件事情是讓我們的CI服務器(Jenkins)真的將鏡像push到Docker Registry上。請記住本地構件默認是不會push鏡像的。

爲了push這些鏡像,我們改變我們的<pushImage>標籤的值從true變爲${push.image}屬性,這默認是被設置爲false,並且只會在CI服務器上設置爲true。

譯註:這裏的意思是,由於開發人員也要在本地構建然後測試之後纔會提交,而測試的鏡像不應該被提交到Registry,所以<pushImage>應該使用一個屬性,默認爲false,在CI服務器上覆蓋爲true在構建後去push鏡像。

這就完成了!讓我們看一下最終的代碼:
[Bash shell] 純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<resources>
<resource>
    <directory>${project.basedir}</directory>
    <filtering>true</filtering>
    <includes>
        <include>**/Dockerfile</include>
    </includes>
</resource>
</resources>
<pluginManagement>
<plugins>
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>0.2.3</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>build</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <dockerDirectory>${project.build.directory}/classes</dockerDirectory>
 
            <pushImage>${push.image}</pushImage>      <!-- true when Jenkins builds, false otherwise -->
 
            <imageName>
                ${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}
            </imageName>
            <resources>
                <resource>
                    <targetPath>/</targetPath>
                    <directory>${project.basedir}</directory>
                    <excludes>
                        <exclude>target/**/*</exclude>
                        <exclude>pom.xml</exclude>
                        <exclude>*.iml</exclude>
                    </excludes>
                </resource>
                <rescource>
                    <targetPath>/</targetPath>
                    <directory>${project.build.directory}</directory>
                    <include>webgate.war</include>
                </rescource>
            </resources>
        </configuration>
    </plugin>
</plugins>
</pluginManagement>


性能

這個過程有兩個能夠提高你的構建和部署的性能的改進地方:
  • 讓你的基礎的機器鏡像(在EC2的例子下是AMI)包含一些你的Docker鏡像的基礎版本。這樣會使得docker pull只去pull那些改變了的層,即增量(相對於整個鏡像來說要小得多)。
  • 在Docker Registry的前端放一個Redis緩存。這可以緩存標籤和元數據,減少和真實存儲(在我們的例子下是S3)的迴環。

我們現在已經使用這個構建過程一段時間了,並且對它非常滿意。然而仍然有提高的空間,如果你有任何關於讓這個過程更加流暢的建議,我很樂意在評論中聽到你的想法。

發佈了23 篇原創文章 · 獲贊 40 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章