Docker 多階段構建實戰 (multi-stage builds)
歡迎訪問我的 GitHub
這裏分類和彙總了欣宸的全部原創(含配套源碼): https://github.com/zq2599/blog_demos
本篇概覽
-
在編寫 Dockerfile 構建 docker 鏡像時,常遇到以下問題:
-
RUN 命令會讓鏡像新增 layer,導致鏡像變大,雖然通過 &&連接多個命令能緩解此問題,但如果命令之間用到 docker 指令例如 COPY、WORKDIR 等,依然會導致多個 layer;
-
有些工具在構建過程中會用到,但是最終的鏡像是不需要的(例如用 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 微服務鏡像;
環境信息
-
本次實戰的環境信息如下:
-
操作系統:Ubuntu 18.04.2 LTS
-
Docker:18.06.1-ce
-
Java:1.8.0_191
-
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 鏡像,那麼每次都要經歷這個過程,為了避免每次都下載,請做如下操作:
-
找一個可以運行 maven 的環境,把 eureka-server 工程複製到這個環境上;
-
在 eureka-server 目錄下執行命令 mvn clean package -U -DskipTests ,開始構建此工程;
-
構建成功後,進入本地的 maven 緩存目錄,通常是用户的 home 目錄下的**.m2** 文件夾,裏面有個名為 repository 的目錄;
-
將整個 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目錄了,先刪除鏡像中已有的repository
RUN 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賬號,新建用户admin
RUN adduser -Dh /home/admin admin
#工作目錄是/app
WORKDIR /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 腳本,請參考每行的註釋來理解,有以下幾點需要重點關注:
-
一共有兩次 FROM 指令出現,而最終的鏡像是基於最後一個 FROM 生成的;
-
PROJECT_NAME 這個環境變量被定義了兩次(ENV PROJECT_NAME),因為前面階段定義的環境變量在後面的階段是用不了的;
-
COPY --from=compile_stage這個命令,可以將指定階段的文件複製到當前階段來,這一步很關鍵,第一階段用 maven 構建出來的 jar 文件,通過該命令複製到後面的階段來使用了;
-
最後一個 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 20421e52c3e6
Removing intermediate container 20421e52c3e6
---> dfb33f654436
Step 14/16 : WORKDIR /app
---> Running in d17f74e9c119
Removing intermediate container d17f74e9c119
---> bbd17f2d0777
Step 15/16 : COPY --from=compile_stage $WORK_PATH/target/${PROJECT_NAME}-${PROJECT_VERSION}.jar .
---> 5d194c2a6b17
Step 16/16 : CMD ["sh", "-c", "java -jar /app/${PROJECT_NAME}-${PROJECT_VERSION}.jar --spring.profiles.active=dev"]
---> Running in 2cb771e5af44
Removing intermediate container 2cb771e5af44
---> b05fc74903ed
Successfully built b05fc74903ed
Successfully tagged bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT
複製代碼
-
查看鏡像的體積如下,148 兆,符合預期:
REPOSITORY TAG IMAGE ID CREATED SIZE
bolingcavalry/multi-stage-build 0.0.1-SNAPSHOT b05fc74903ed 2 minutes ago 148MB
maven 3.6.1 740262c47f21 3 days ago 614MB
java 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:程序員欣宸
- 那些 Go 語言發展歷史上的重大決策
- 從趨勢到挑戰,一站式解讀操作系統運維和可觀測性
- 百萬級 Topic,騰訊雲的 Apache Pulsar 穩定性實踐
- Apache Doris 在思必馳的應用優化實踐:海量語音通話數據下,實時、離線一體的數倉架構設計實踐
- 愛數正式開源認知智能開發框架 KWeaver
- 運維智能化的三大關鍵技術
- “抄我的還‘反捅’我一刀”,Gary Marcus 發文駁斥圖靈獎得主 Yann LeCun
- 當出海成為必選項,企業如何構建全場景全生態技術底座?
- 數智底座必備能力三:快速構建創新應用
- Docker 多階段構建實戰 (multi-stage builds)
- 工作筆記之 SELECT 語句在 SAP ABAP 中的用法總結(上)
- 經久不衰的設計定律是不要讓我思考的設計
- 不要指望下一個像 GPT 這樣的大型語言模型會民主化
- Java 近期新聞:Helidon Níma、Spring Framework、MicroProfile、MicroStream、Kotlin 和 Piranha
- 一文入門 jQuery
- C 學習 ---__libc_open 函數的原理
- 監控系統工作原理
- 甲骨文新微服務框架 Helidon Níma:使用虛擬線程實現高性能
- 【雲原生 | 從零開始學 Kubernetes】二、使用 kubeadm 搭建 K8S 集羣
- Elasticsearch 聚合學習之四:結果排序