Docker 多階段構建實戰 (multi-stage builds)

語言: CN / TW / HK

歡迎訪問我的 GitHub

這裡分類和彙總了欣宸的全部原創(含配套原始碼): https://github.com/zq2599/blog_demos

本篇概覽

  • 在編寫 Dockerfile 構建 docker 映象時,常遇到以下問題:

  1. RUN 命令會讓映象新增 layer,導致映象變大,雖然通過 &&連線多個命令能緩解此問題,但如果命令之間用到 docker 指令例如 COPY、WORKDIR 等,依然會導致多個 layer;

  2. 有些工具在構建過程中會用到,但是最終的映象是不需要的(例如用 maven 編譯構建 java 工程),這要求 Dockerfile 的編寫者花更多精力來清理這些工具,清理的過程又可能導致新的 layer;

  • 為了解決上述問題,從 17.05 版本開始 Docker 在構建映象時增加了新特性:多階段構建(multi-stage builds),將構建過程分為多個階段,每個階段都可以指定一個基礎映象,這樣在一個 Dockerfile 就能將多個映象的特性同時用到,例如:先用 maven 映象構建 java 工程,再把構建結果和 jre 合成,就做成了一個可以直接執行 java 工程映象了;

  • 官方描述如下圖所示,地址是: https://docs.docker.com/develop/develop-images/multistage-build/

  • 官方的例項是 golang 的,今天我們以 maven 構建 springboot 工程為例,實戰如何使用 multi-stage 特性構建 java 微服務映象;

環境資訊

  • 本次實戰的環境資訊如下:

  1. 作業系統:Ubuntu 18.04.2 LTS

  2. Docker:18.06.1-ce

  3. Java:1.8.0_191

  4. Maven:3.6.1

實戰原始碼

  • 本次實戰用到的原始碼是個普通 springboot 工程,功能是 SpringCloud 中的註冊中心 eureka,您可以在 Github 下載,地址和連結如下所示:

  • 這個 git 專案中有多個資料夾,本章原始碼在 springcloudscaledemo 這個資料夾下,如下圖紅框所示:

  • springcloudscaledemo 資料夾內有三個工程,本次實戰用到的是 eureka-server,如下圖:

準備材料

  • 在能正常執行 docker 的電腦上新建一個目錄,例如我這裡是 ubuntu 系統上**/home/willzhao/temp/201906/02** ,將 maven 工程 eureka-server 複製到這個目錄下;

避免每次構建映象都下載工程所需的 jar 包

  • 構建映象過程中會用 maven 構建 springboot 工程,會下載 springboot 工程依賴的 jar 包,此過程很漫長,如果您多次構建 Dockerfile 映象,那麼每次都要經歷這個過程,為了避免每次都下載,請做如下操作:

  1. 找一個可以執行 maven 的環境,把 eureka-server 工程複製到這個環境上;

  2. 在 eureka-server 目錄下執行命令 mvn clean package -U -DskipTests ,開始構建此工程;

  3. 構建成功後,進入本地的 maven 快取目錄,通常是使用者的 home 目錄下的**.m2** 資料夾,裡面有個名為 repository 的目錄;

  4. 將整個 repository 目錄複製到前面提到的**/home/willzhao/temp/201906/02** 目錄下,和 eureka-server 放在同一目錄,如下圖:

  • 這樣,在編寫 Dockerfile 的時候只要用這個 repository 覆蓋映象中的 maven 快取,在編譯時就不會去 maven 的中央倉庫下載 jar 了,會節省很多時間。

編寫 Dockerfile

  • 在**/home/willzhao/temp/201906/02** 目錄下建立檔案 Dockerfile,內容如下所示:

# Docker image for multi stage build# VERSION 0.0.1# Author: bolingcavalry
### 第一階段,用maven映象進行編譯FROM maven:3.6.1 AS compile_stage
####################定義環境變數 start#####################定義工程名稱,也是原始檔的資料夾名稱ENV PROJECT_NAME eureka-server#定義工作目錄ENV WORK_PATH /usr/src/$PROJECT_NAME####################定義環境變數 start####################
#作者MAINTAINER BolingCavalry <[email protected]>
#將原始碼複製到當前目錄COPY ./$PROJECT_NAME $WORK_PATH
#如果前面您已經準備好了repository目錄,就可以用來替換映象中的repository目錄了,先刪除映象中已有的repositoryRUN rm -rf /root/.m2/repository
#將準備好的repository資料夾複製進來,這樣相當於映象環境中已經有了java工程所需的jar,可以避免去maven中央倉庫下載COPY ./repository /root/.m2/repository
#編譯構建RUN cd $WORK_PATH && mvn clean package -U -DskipTests
### 第二階段,用第一階段的jar和jre映象合成一個小體積的映象FROM java:8-jre-alpine
####################定義環境變數 start#####################定義工程名稱,也是原始檔的資料夾名稱ENV PROJECT_NAME eureka-server#定義工程版本ENV PROJECT_VERSION 0.0.1-SNAPSHOT#定義工作目錄ENV WORK_PATH /usr/src/$PROJECT_NAME####################定義環境變數 start####################
#安全起見不用root賬號,新建使用者adminRUN adduser -Dh /home/admin admin
#工作目錄是/appWORKDIR /app
#從名為compile_stage的stage複製構建結果到工作目錄COPY --from=compile_stage $WORK_PATH/target/${PROJECT_NAME}-${PROJECT_VERSION}.jar .
#啟動應用CMD ["sh", "-c", "java -jar /app/${PROJECT_NAME}-${PROJECT_VERSION}.jar --spring.profiles.active=dev"]

複製程式碼

  • 上面就是分成了兩個階段構建的 Dockerfile 指令碼,請參考每行的註釋來理解,有以下幾點需要重點關注:

  1. 一共有兩次 FROM 指令出現,而最終的映象是基於最後一個 FROM 生成的;

  2. PROJECT_NAME 這個環境變數被定義了兩次(ENV PROJECT_NAME),因為前面階段定義的環境變數在後面的階段是用不了的;

  3. COPY --from=compile_stage這個命令,可以將指定階段的檔案複製到當前階段來,這一步很關鍵,第一階段用 maven 構建出來的 jar 檔案,通過該命令複製到後面的階段來使用了;

  4. 最後一個 FROM 是 java:8-jre-alpine,這是精簡版的 java 執行環境映象,最終映象的內容就是 jre 和 maven 的構建結果,而前面的 maven 映象和最終構建結果無關;

構建映象

  • 在 Dockerfile 所在目錄執行以下命令即可構建映象:

docker build -t bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT .

複製程式碼

  • 輸出的部分結果資訊如下:

...Step 13/16 : RUN adduser -Dh /home/admin admin ---> Running in 20421e52c3e6Removing intermediate container 20421e52c3e6 ---> dfb33f654436Step 14/16 : WORKDIR /app ---> Running in d17f74e9c119Removing intermediate container d17f74e9c119 ---> bbd17f2d0777Step 15/16 : COPY --from=compile_stage $WORK_PATH/target/${PROJECT_NAME}-${PROJECT_VERSION}.jar . ---> 5d194c2a6b17Step 16/16 : CMD ["sh", "-c", "java -jar /app/${PROJECT_NAME}-${PROJECT_VERSION}.jar --spring.profiles.active=dev"] ---> Running in 2cb771e5af44Removing intermediate container 2cb771e5af44 ---> b05fc74903edSuccessfully built b05fc74903edSuccessfully tagged bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT

複製程式碼

  • 檢視映象的體積如下,148 兆,符合預期:

REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZEbolingcavalry/multi-stage-build   0.0.1-SNAPSHOT      b05fc74903ed        2 minutes ago       148MBmaven                             3.6.1               740262c47f21        3 days ago          614MBjava                              8-jre-alpine        fdc893b19a14        2 years ago         108MB

複製程式碼

驗證映象是否可用

  • 執行以下命令可以用最新的映象啟動一個容器:

docker run -p 8080:8082 bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT

複製程式碼

  • 在前面 Dockerfile 檔案的 CMD 命令中指定了 profile 引數為 dev,所以 application-dev.properties 檔案會生效,這裡面定義的埠號是 8082,所以 docker run 命令中通過-p 引數將容器的 8082 埠對映到宿主機 8080 埠

  • 用瀏覽器訪問宿主機的 8080 埠,看服務是否正常,如下圖:

  • 至此,docker 的多階段構建實戰就完成了,這是個很實用功能,在您構建映象的過程中如果想用到多個映象的能力,又不想自己去做相關的整合和清理工作,並且對映象體積有要求的時候,希望本文能給您一些參考。

歡迎關注 InfoQ:程式設計師欣宸

學習路上,你不孤單,欣宸原創一路相伴...