Docker Certified Associate(DCA)認證考試學習-Docker Image Multi-Stage Builds Docker Certified Associate(DCA)認證相關資源、Docker Image Multi-Stage Builds

Published on Saturday, February 25, 2023

Docker Image Multi-Stage

在這篇文章Docker Certified Associate(DCA)認證考試學習-Docker Image Layer當中有提到一個Dockerfile的規則
當使用RUN, COPY, ADD任意一個指令,Docker就會幫我們創建一個layers

也就說當我們使用越多的指令Docker就會幫我們建立更多的層,但是在最佳實踐也有提到要盡量減少層數的的產生,因為越多層數會影響運作的速度 那有沒有辦法減少層數呢?
當然是有的,那就是Multi-Stage Builds

首先我們下載Dotnet提供的範例來學習

git clone https://github.com/dotnet/dotnet-docker.git
cd dotnet-docker/samples/dotnetapp

我們將專案目錄列出來看看,可以看到有許多的Dockerfile在Docker的規則中,預設會去讀取Dockerfile的文件,不過也可以叫做別的名稱只要在build時要額外添加 -f 參數指定文件名稱即可

[node1] (local) root@192.168.0.18 ~/dotnet-docker/samples/dotnetapp
$ ls -l
total 100
-rw-r--r--    1 root     root           493 Feb 25 14:57 Dockerfile
-rw-r--r--    1 root     root           780 Feb 25 14:57 Dockerfile.alpine-arm32
-rw-r--r--    1 root     root           885 Feb 25 14:57 Dockerfile.alpine-arm32-slim
-rw-r--r--    1 root     root           784 Feb 25 14:57 Dockerfile.alpine-arm64
-rw-r--r--    1 root     root           889 Feb 25 14:57 Dockerfile.alpine-arm64-slim
-rw-r--r--    1 root     root           778 Feb 25 14:57 Dockerfile.alpine-x64
-rw-r--r--    1 root     root           883 Feb 25 14:57 Dockerfile.alpine-x64-slim
-rw-r--r--    1 root     root           522 Feb 25 14:57 Dockerfile.chiseled
-rw-r--r--    1 root     root           485 Feb 25 14:57 Dockerfile.debian-arm32
-rw-r--r--    1 root     root           489 Feb 25 14:57 Dockerfile.debian-arm64
-rw-r--r--    1 root     root           483 Feb 25 14:57 Dockerfile.debian-x64
-rw-r--r--    1 root     root           588 Feb 25 14:57 Dockerfile.debian-x64-slim
-rw-r--r--    1 root     root           620 Feb 25 14:57 Dockerfile.nanoserver-x64
-rw-r--r--    1 root     root           650 Feb 25 14:57 Dockerfile.nanoserver-x64-slim
-rw-r--r--    1 root     root           483 Feb 25 14:57 Dockerfile.ubuntu-arm32
-rw-r--r--    1 root     root           487 Feb 25 14:57 Dockerfile.ubuntu-arm64
-rw-r--r--    1 root     root           481 Feb 25 14:57 Dockerfile.ubuntu-x64
-rw-r--r--    1 root     root           586 Feb 25 14:57 Dockerfile.ubuntu-x64-slim
-rw-r--r--    1 root     root           511 Feb 25 14:57 Dockerfile.windowsservercore-x64
-rw-r--r--    1 root     root           677 Feb 25 14:57 Dockerfile.windowsservercore-x64-slim
-rw-r--r--    1 root     root          3617 Feb 25 14:57 Program.cs
-rw-r--r--    1 root     root         11310 Feb 25 14:57 README.md
-rw-r--r--    1 root     root           215 Feb 25 14:57 dotnetapp.csproj

我們看看專案內的 Dockerfile ,可以看到竟然有兩個FROM

  • FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
  • FROM mcr.microsoft.com/dotnet/runtime:7.0
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /source

# copy csproj and restore as distinct layers
COPY *.csproj .
RUN dotnet restore --use-current-runtime

# copy and publish app and libraries
COPY . .
RUN dotnet publish -c Release -o /app --use-current-runtime --self-contained false --no-restore

# final stage/image
FROM mcr.microsoft.com/dotnet/runtime:7.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "dotnetapp.dll"]

首先我們知道在 dotnet 中要編譯程式那需要安裝 dotnet sdk 編譯完的程式如果沒有額外做設定,要在其他電腦運作需要安裝 dotnet runtime 我們比較一下這兩個image,看到SIZE欄位

  • mcr.microsoft.com/dotnet/sdk 767MB
  • mcr.microsoft.com/dotnet/runtime 190MB
[node1] (local) root@192.168.0.18 ~/dotnet-docker/samples/dotnetapp
$ docker image ls
REPOSITORY                         TAG       IMAGE ID       CREATED         SIZE
hello                              latest    314c0cca4b2d   4 minutes ago   190MB
<none>                             <none>    2f274cff5d2e   4 minutes ago   971MB
mcr.microsoft.com/dotnet/sdk       7.0       83489ef20059   11 days ago     767MB
mcr.microsoft.com/dotnet/runtime   7.0       eb10f76afdc9   11 days ago     190MB

這兩個image的大小差了好幾倍,所以如果只能選擇一個底層,那只能選擇功能比較多的sdk image
不過這樣大小就要767MB起跳了,還要加上編譯完程式的大小
所以docker提供了一個辦法讓你在功能比較完整的sdk環境做編譯,編譯完後在將程式傳送到runtime為底層的image,這樣大小就變成190MB起跳了
越小的image大小在我們部署跟啟動之時都是非常有幫助,這就是Multi-Stage Builds

接下來繼續解讀Dockerfile的內容

# 首先第一行最後面多了一段 ` AS build` 這個名字沒有限定,是用來指定這個編譯環境的名稱,看到下方 `--from=build` 兩個名字要一樣
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
# 指定新的工作目錄
WORKDIR /source

# 將所有專案檔複製到image內部,並且還原所以專案
COPY *.csproj .
RUN dotnet restore --use-current-runtime

# 將所有檔案複製到image內部,並且publish專案並且輸出到 app 資料夾 
COPY . .
RUN dotnet publish -c Release -o /app --use-current-runtime --self-contained false --no-restore

# 指定第二個image
# 指定工作目錄到 app
# 將所有 build 建構環境的 app 資料夾,複製到新的image底下
# 運行dll
FROM mcr.microsoft.com/dotnet/runtime:7.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "dotnetapp.dll"]

大致了解之後我們在看一次所有的images
第一個就是我們成品的image,底層是使用runtime image
第二個image是,這個比較特別是一個中繼的檔案也就是我們剛剛的build 建構環境的臨時image
這個image如果硬碟容量足夠的話可以不用主動清除掉

[node1] (local) root@192.168.0.18 ~/dotnet-docker/samples/dotnetapp
$ docker image ls
REPOSITORY                         TAG       IMAGE ID       CREATED         SIZE
hello                              latest    314c0cca4b2d   4 minutes ago   190MB
<none>                             <none>    2f274cff5d2e   4 minutes ago   971MB
mcr.microsoft.com/dotnet/sdk       7.0       83489ef20059   11 days ago     767MB
mcr.microsoft.com/dotnet/runtime   7.0       eb10f76afdc9   11 days ago     190MB

我們可以使用 docker history 來看清楚image所有的層數

[node1] (local) root@192.168.0.18 ~/dotnet-docker/samples/dotnetapp
$ docker history 2f274c
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
2f274cff5d2e   31 minutes ago   /bin/sh -c dotnet publish -c Release -o /app…   9.42MB    
48df258627a0   31 minutes ago   /bin/sh -c #(nop) COPY dir:d177e334bdec992e0…   3.93kB    
58f7aa6a3d6f   31 minutes ago   /bin/sh -c dotnet restore --use-current-runt…   194MB     
a66cd1e7ac71   31 minutes ago   /bin/sh -c #(nop) COPY file:323095bfbe621dcb…   215B      
3fdf37a8423e   31 minutes ago   /bin/sh -c #(nop) WORKDIR /source               0B        
83489ef20059   11 days ago      /bin/sh -c powershell_version=7.3.2     && c…   42.4MB    
<missing>      11 days ago      /bin/sh -c curl -fSL --output dotnet.tar.gz …   438MB     
<missing>      11 days ago      /bin/sh -c apt-get update     && apt-get ins…   74.8MB    
<missing>      11 days ago      /bin/sh -c #(nop)  ENV ASPNETCORE_URLS= DOTN…   0B        
<missing>      11 days ago      /bin/sh -c #(nop) COPY dir:0757f1be98a8096d5…   21.8MB    
<missing>      11 days ago      /bin/sh -c #(nop)  ENV ASPNET_VERSION=7.0.3     0B        
<missing>      11 days ago      /bin/sh -c ln -s /usr/share/dotnet/dotnet /u…   24B       
<missing>      11 days ago      /bin/sh -c #(nop) COPY dir:85163c9fd54a3f2f3…   73.2MB    
<missing>      11 days ago      /bin/sh -c #(nop)  ENV DOTNET_VERSION=7.0.3     0B        
<missing>      11 days ago      /bin/sh -c #(nop)  ENV ASPNETCORE_URLS=http:…   0B        
<missing>      11 days ago      /bin/sh -c apt-get update     && apt-get ins…   36.2MB    
<missing>      2 weeks ago      /bin/sh -c #(nop)  CMD ["bash"]                 0B        
<missing>      2 weeks ago      /bin/sh -c #(nop) ADD file:3ea7c69e4bfac2ebb…   80.5MB   

[node1] (local) root@192.168.0.18 ~/dotnet-docker/samples/dotnetapp
$ docker history 314c0
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
314c0cca4b2d   32 minutes ago   /bin/sh -c #(nop)  ENTRYPOINT ["dotnet" "dot…   0B        
0285c20c2552   32 minutes ago   /bin/sh -c #(nop) WORKDIR /app                  0B        
eb10f76afdc9   11 days ago      /bin/sh -c ln -s /usr/share/dotnet/dotnet /u…   24B       
<missing>      11 days ago      /bin/sh -c #(nop) COPY dir:85163c9fd54a3f2f3…   73.2MB    
<missing>      11 days ago      /bin/sh -c #(nop)  ENV DOTNET_VERSION=7.0.3     0B        
<missing>      11 days ago      /bin/sh -c #(nop)  ENV ASPNETCORE_URLS=http:…   0B        
<missing>      11 days ago      /bin/sh -c apt-get update     && apt-get ins…   36.2MB    
<missing>      2 weeks ago      /bin/sh -c #(nop)  CMD ["bash"]                 0B        
<missing>      2 weeks ago      /bin/sh -c #(nop) ADD file:3ea7c69e4bfac2ebb…   80.5MB     

這兩個層數相差非常多,這也是Multi-Stage Builds的好處之一
我們可以在建構環境的 image 完成所有基礎的操作,最後只要將dotnet編譯完的結果複製出來就好了,因為sdk跟程式的原始碼編譯完之後就沒有意義了 所以在實際使用環境Multi-Stage Builds是經常使用的


Summary

今天學習了Multi-Stage Builds的使用方法,使用之後image的層數跟大小,可以縮小數倍可謂是好處多多,所以在最佳實踐中Docker也是建議多使用Multi-Stage Builds 可以讓image能夠更快速的下載下來,層數減少也可以減少container啟動的速度,可以說是很重要的技術。