使用Docker多階段構建來減小映象大小的方法

2022-10-03 13:27:13 字數 4226 閱讀 4489

本文講述了如何通過 docker 的多階段構建功能來大幅度減小映象大小,適用於需要在 dockerfile 中構建程式(如 j**ac),且需要另外安裝編譯工具鏈的映象。(如 j**a)

再來看一下效果: 原 110m+,現 92m。

對比一下 dockerfile

優化前 dockerfile:

from openjdk:8u171-jdk-alpine3.8

add . /app

workdir /app

run apk add m**en \

&& mvn clean package \

&& apk del m**en \

&& mv target/final.jar / \

&& cd / \

&& rm -rf /app \

&& rm -rf /root/.m2

entrypoint j**a -jar /final.jar

優化後 dockerfile:

from openjdk:8u171-jdk-alpine3.8 as builder

add . /app

workdir /app

run apk add m**en \

&& mvn clean package \

&& apk del m**en \

&& mv target/final.jar /

from openjdk:8u181-jre-alpine3.8 as environment

workdir /

copy --from=builder /final.jar .

entrypoint j**a -jar /final.jar

很明顯,優化後的 dockerfile 新增了 from as 這個命令,並出現了兩個 from。這就是多階段構建。

了解一下多階段構建

多階段構建是 docker 17.05 的新增功能,它可以在乙個 dockerfile 中使用多個 from 語句,以建立多個 stages(階段)。每個階段間獨立(**請求),可以通過 copy --from 來獲取其它階段的檔案。我們來打個比方,把最終映象比作一盤菜(炒青椒)。把原料青椒炒完後上桌。

# 對比清單

映象 -> 一盤菜

第乙個階段 -> 炒

第二個階段 -> 上桌

兩個階段的目標是做好(生成)最終的菜(映象)。我們要做的是將第乙個階段「炒」出來的食物進行「上桌」。我們的目標是 做出菜,且 菜盤子(盛菜和中間產物)最輕。

視覺化流程如下:

# 做菜流程

... 省略原料

原料 -> [第乙個階段——炒] # 此時盤子裡有炒的工具、炒的結果和中間產物

# 這時候開啟第二個階段,只保留炒的結果,而不再需要其它。

-> 炒的結果 -> [開始上桌,只保留結果] # 把炒出來的青椒拿來(copy --from),其它不要

-> 最終是一盤菜。

現在應該大致理解多階段構建的流程了吧。我們把話筒交給 j**a,看看在 dockerfile 中使用編譯工具構建乙個 jar,並只保留構建完的 jar 和執行時交給 image,其它則扔掉應該怎麼做:

# 第一階段——編譯(炒)

from openjdk:8u171-jdk-alpine3.8 as builder # 自帶編譯工具

add . /app

workdir /app

run ... 省略編譯和清理工作...

# 現在,jar 已經出爐。jdk 不再需要,所以不能留在映象中。

# 所以我們開啟第二階段——執行(上桌),並扔掉第一階段的所有檔案(包括編譯工具)

from openjdk:8u181-jre-alpine3.8 as environment # 只帶執行時

# 目前,編譯工具等上一階段的東西已經被我們拋下。目前的映象中只有執行時,我們需要把上一階段(炒)的結果拿來,其它不要。

copy --from=0 /final.jar .

# 好了,現在映象只有必要的執行時和 jar 了。

entrypoint j**a -jar /final.jar

如上就是多階段構建的介紹。

使用多階段構建

多階段構建的核心命令是 from。form 對於身經百戰的你來說已經不用多講了。在多階段構建中,每次 from 都會開啟乙個新的 stage(階段),可以看作乙個新的 image(不夠準確、**請求),與其它階段隔離(甚至包括環境變數)。只有最後的 from 才會被納入 image 中。

我們來做乙個最 ****** 的多階段構建例子:

# stage 1

from alpine:3.8

workdir /demo

run echo "hello, stage 1" > /demo/hi-1.txt

# stage 2

from alpine:3.8

workdir /demo

run echo "hello, stage 2" > /demo/hi-2.txt

可以自己構建一下這個 dockerfile,然後 docker s**e > docker.tar 看看其中的內容。不出意外應該只有 /demo/hi-2.txt 和 alpine。

在這個 dockerfile 中,我們建立了兩個階段。第乙個階段建立 hi-1.txt,第二個階段建立 hi-2.tx程式設計客棧t,且第二個階段會被加入最終 image,其它不會。

複製檔案——階段間的橋梁

如果階段間完全隔離,那麼多階段就沒有意義——上乙個階段的結果會被完全拋棄,並進入全新的下一階段。

我們可以通過 copy 命令來獲取其它階段的檔案。在多階段中使用 copy 和普通應用完全一致,僅需要新增 –form ` 即可。那麼,我們修正上乙個例子,使最終映象包含兩個階段的產物:

# stage 1

from alpine:3.8

workdirwww.cppcns.com /demo

run echo "hello, stage 1" > /demo/hi-1.txt

# stage 2

from alpine:3.8

workdir /demo

copy --from=0 /demo/hi-1.txt /demo

run echo "hello, stage 2" > /demo/hi-2.txt

重新構建並儲存(s**e),你會發現多了一層 layer,其中包含 hi-1.txt。

階段命名——快速識別

對於只有七秒記憶的我們來說,每次使用 stage index 並不是一件很妙的事情。這時候,可以通過階段命名的方式給它們賦予名字,以方便識別。

為階段新增名字很簡單,只需要在 from 後加上 as 即可。

現在,我們更新 dockerfile,給予階段名稱並使用名稱來 copy。

# stage 1, it's name is "build1"

from alpine:3.8 as build1

workdir /demo

run echo "hello, stage 1" > /demo/hi-1.txt

# stage 2, it's name is "build2"

from alpine:3.8 as build2

workdir /demo

# no longer use indexes

copy --from=build1 /demo/hi-1.txt /de程式設計客棧mo

run echo "hello, stage 2" > /demo/hi-2.txt

重新構建並儲存,結果應該同上次相同。

僅構建部分階段——輕鬆除錯

docker 還為我們提供了乙個很方便的除錯方式——僅構建部分階段。它可以使構建停在某個階段,並不構建後面的階段。這可以方便我們除錯;區分生產、開發和測試。

仍然沿用上次的 dockerfile,但使用 --tamvnbecrget 引數進行構建:

$ docker build --target build1 .

再次 s**e,你會發現只有 build1 的內容。

總結這就是多階段構建的全部用法了。我們再回到開篇的兩個 d程式設計客棧ockerfile 對比,你能發現優化前的映象胖在**了嗎?

很顯然,它包含了無用的 jdk,jdk 只在編譯時起作用,編譯完便無用了,只需要 jre 即可。所以,利用多階段構建可以隔離編譯階段和執行階段,以達到映象最優化。

參考文獻

本文標題: 使用docker多階段構建來減小映象大小的方法

本文位址:

Docker多階段構建

在 docker 17.05 版本之前,我們構建 docker 映象時,通常會採用兩種方式 一種方式是將所有的構建過程編包含在乙個 dockerfile 中,包括專案及其依賴庫的編譯 測試 打包等流程,這裡可能會帶來的一些問題 package main import fmt func main 編寫...

docker 多階段構建

構建映象最具挑戰性的一點是使映象大小盡可能的小。dockerfile中的每條指令都為影象新增了乙個圖層,您需要記住在移動到下一層之前清理任何不需要的工件。對於多階段構建,您可以在dockerfile中使用多個from語句。每個from指令可以使用不同的基礎,並且每個指令都開始乙個新的構建。您可以選擇...

Docker 的多階段構建

比如我們現在有乙個最簡單的 golang 服務,需要構建乙個最小的docker映象,原始碼如下 複製 我們最終的目的都是將最終的可執行檔案放到乙個最小的映象 比如alpine 中去執行,怎樣得到最終的編譯好的檔案呢?基於docker的指導思想,我們需要在乙個標準的容器中編譯,比如在乙個 ubuntu...