通常我們使用docker構建鏡像的步驟是這樣的:
- 編寫Dockerfile文件
- 執行docker build 命令
執行以上兩部就可以得到構建好的鏡像,這也是docker帶給我們的便利。但是以上步驟背後執行了哪些操作?Dockerfile文件指令是如何被執行的?構建輸出內容分別代表什麼含義?本文現在就針對docker build操作專門說一說隱藏在背後的細節。
閱讀本文的知識前提:
- 對docker有所瞭解,能夠區分鏡像、容器、傳統虛擬機;
- 知道docker build是用於構建鏡像的命令;
- 聽說過鏡像是分層組成的;
本文適用的閱讀對象:
- 剛接觸docker,對docker build背後的操作有好奇心;
- 想要搞清楚docker鏡像是如何構建的;
本文以構建一個asp.net core應用鏡像爲例,Dockerfile文件內容如下:
FROM harbor.xxxx.top:9999/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /appd
FROM harbor.xxxx.top:9999/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["Tccc.BIZ.CMS.Srv/Tccc.BIZ.CMS.Srv.csproj", "Tccc.BIZ.CMS.Srv/"]
COPY ["Tccc.BIZ.ATMS.SPI/Tccc.BIZ.ATMS.SPI.csproj", "Tccc.BIZ.ATMS.SPI/"]
RUN dotnet restore "Tccc.BIZ.CMS.Srv/Tccc.BIZ.CMS.Srv.csproj"
COPY . .
WORKDIR "/src/Tccc.BIZ.CMS.Srv"
RUN dotnet build "Tccc.BIZ.CMS.Srv.csproj" -c Release -o /appd/build
FROM build AS publish
RUN dotnet publish "Tccc.BIZ.CMS.Srv.csproj" -c Release -o /appd/publish
FROM base AS final
WORKDIR /appd
COPY --from=publish /appd/publish .
ENTRYPOINT ["dotnet", "Tccc.BIZ.CMS.Srv.dll"]
執行命令:sudo docker build -t xxxx -f xxxx ./,構建輸出內容如下:
Sending build context to Docker daemon 50.69kB
Step 1/16 : FROM harbor.xxxx.top:9999/dotnet/core/aspnet:3.1-buster-slim AS base
---> 014a41b1f39a
Step 2/16 : WORKDIR /appd
---> Running in 42f5287928dd
Removing intermediate container 42f5287928dd
---> b944a6f67809
Step 3/16 : FROM harbor.xxxx.top:9999/dotnet/core/sdk:3.1-buster AS build
---> 006ded9ddf29
Step 4/16 : WORKDIR /src
---> Using cache
---> 93f67f62562c
Step 5/16 : COPY ["Tccc.BIZ.CMS.Srv/Tccc.BIZ.CMS.Srv.csproj", "Tccc.BIZ.CMS.Srv/"]
---> Using cache
---> bc0a32ea122b
Step 6/16 : COPY ["Tccc.BIZ.ATMS.SPI/Tccc.BIZ.ATMS.SPI.csproj", "Tccc.BIZ.ATMS.SPI/"]
---> Using cache
---> 32c675319114
Step 7/16 : RUN dotnet restore "Tccc.BIZ.CMS.Srv/Tccc.BIZ.CMS.Srv.csproj"
---> Using cache
---> f2c85873c12a
Step 8/16 : COPY . .
---> Using cache
---> 005ea2bc08d0
Step 9/16 : WORKDIR "/src/Tccc.BIZ.CMS.Srv"
---> Using cache
---> 3f2d9b1f4bed
Step 10/16 : RUN dotnet build "Tccc.BIZ.CMS.Srv.csproj" -c Release -o /appd/build
---> Running in 7d8df4377351
Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
All projects are up-to-date for restore.
Tccc.BIZ.ATMS.SPI -> /appd/build/Tccc.BIZ.ATMS.SPI.dll
Tccc.BIZ.CMS.Srv -> /appd/build/Tccc.BIZ.CMS.Srv.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:03.60
Removing intermediate container 7d8df4377351
---> faa92e4ff2f9
Step 11/16 : FROM build AS publish
---> faa92e4ff2f9
Step 12/16 : RUN dotnet publish "Tccc.BIZ.CMS.Srv.csproj" -c Release -o /appd/publish
---> Running in 9034962126ba
Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
All projects are up-to-date for restore.
Tccc.BIZ.ATMS.SPI -> /src/Tccc.BIZ.ATMS.SPI/bin/Release/netstandard2.0/Tccc.BIZ.ATMS.SPI.dll
Tccc.BIZ.CMS.Srv -> /src/Tccc.BIZ.CMS.Srv/bin/Release/netcoreapp3.1/Tccc.BIZ.CMS.Srv.dll
Tccc.BIZ.CMS.Srv -> /appd/publish/
Removing intermediate container 9034962126ba
---> 8581c0bf6a5b
Step 13/16 : FROM base AS final
---> b944a6f67809
Step 14/16 : WORKDIR /appd
---> Running in 20233c270934
Removing intermediate container 20233c270934
---> 33640fdd297a
Step 15/16 : COPY --from=publish /appd/publish .
---> e79a9b48f4f7
Step 16/16 : ENTRYPOINT ["dotnet", "Tccc.BIZ.CMS.Srv.dll"]
---> Running in bb2ba15b786c
Removing intermediate container bb2ba15b786c
---> b182d5480d46
Successfully built b182d5480d46
Successfully tagged xxxx
總結以上構建輸出,可以看到主要有以下幾類內容,這裏先對其含義進行簡單說明:
【Sending build context to Docker daemon 50.69kB】作爲構建輸出內容的第一行,根據內容說明我們執行docker build命令後,先將context發送到了docker daemon,且注意到發送內容的大小是50.69kb。
【Step 1/16 : xxxx】很明顯這代表執行總步驟和當前步驟。通過對比上面Dockerfile文件中的指令可以看到,有多少行指令就有多少步驟。
【---> Using cache】此類輸出的含義是:當前指令找到了可以利用的cache層。
【---> Running in 59bdc0332ec6】此類輸出代表啓動了容器,容器ID爲59bdc0332ec6。
【Removing intermediate container 59bdc0332ec6】可以看到前面創建的容器,用完後清理掉。
【---> a6c2a40e277c】此行代表:指令執行結束後生成的鏡像層的id。官方文檔原文:The Docker daemon runs the instructions in the Dockerfile one-by-one, committing the result of each instruction to a new image if necessary
【Successfully tagged xxxx】構建最後一步就是對鏡像打標籤。
根據完整的構建輸出和其中內容的含義,我們基本能夠梳理清楚docker build執行每一個指令的過程:啓動容器-->執行指令操作-->生成鏡像層-->清理容器。下面針對以上步驟中的幾個重要部分深入分析。
Sending build context部分
其實docker是一個C/S架構組成的系統,我們使用的docker命令稱之爲docker cli,命令對應的實際操作是由docker daemon來執行的。
因爲C/S架構的原因,因此構建鏡像時所依賴的文件就需要在client端發送給server端(即dockers daemon)。那麼是如何發送的呢?很直接,就是docker cli通過http接口發送給docker daemon的,我們看一下接口文檔就清晰了:
接口文檔中明確要求了Content-type的值爲application/x-tar,即壓縮文件。也就是將docker build命令的path部分的文件內容(遞歸的)打包壓縮然後通過http restfule接口發送給了docker daemon。說到這裏如果你在實際應用的時候一定會立即想到:path目錄中有很多文件如obj/*,bin/*等中間文件又多又沒用,是不是可以屏蔽掉來提高效率?是的沒錯,請搜索”dockerignore”,本文不再展開。
Using cache部分
由於docker的鏡像是分層結構的,主要的目的就是爲了提高空間利用率和效率。分層後的鏡像構成示意圖如下:
根據以上示意圖,我們查看一個鏡像真實的分層結構:
$ sudo docker history 8581c0bf6a5b IMAGE CREATED CREATED BY SIZE COMMENT 8581c0bf6a5b About a minute ago /bin/sh -c dotnet publish "Tccc.BIZ.CMS.Srv.… 7.97MB faa92e4ff2f9 About a minute ago /bin/sh -c dotnet build "Tccc.BIZ.CMS.Srv.cs… 4.15MB 3f2d9b1f4bed 7 minutes ago /bin/sh -c #(nop) WORKDIR /src/Tccc.BIZ.CMS.… 0B 005ea2bc08d0 7 minutes ago /bin/sh -c #(nop) COPY dir:96eb41ae59fd6d263… 24kB f2c85873c12a 7 minutes ago /bin/sh -c dotnet restore "Tccc.BIZ.CMS.Srv/… 61.1MB 32c675319114 8 minutes ago /bin/sh -c #(nop) COPY file:5e6b41c9a79d1b7f… 343B bc0a32ea122b 8 minutes ago /bin/sh -c #(nop) COPY file:401d0d012517703b… 796B 93f67f62562c 8 minutes ago /bin/sh -c #(nop) WORKDIR /src 0B 006ded9ddf29 7 weeks ago /bin/sh -c powershell_version=7.0.1 && c… 38.2MB <missing> 7 weeks ago /bin/sh -c dotnet_sdk_version=3.1.301 &&… 340MB <missing> 7 weeks ago /bin/sh -c apt-get update && apt-get ins… 32.9MB <missing> 7 weeks ago /bin/sh -c #(nop) ENV DOTNET_RUNNING_IN_CON… 0B <missing> 2 months ago /bin/sh -c apt-get update && apt-get install… 146MB <missing> 2 months ago /bin/sh -c set -ex; if ! command -v gpg > /… 17.5MB <missing> 2 months ago /bin/sh -c apt-get update && apt-get install… 16.5MB <missing> 2 months ago /bin/sh -c #(nop) CMD ["bash"] 0B <missing> 2 months ago /bin/sh -c #(nop) ADD file:1ab357efe422cfed5… 114MB
出現此類提示就代表在本地緩存路徑(默認爲/var/lib/docker)中中找到了可用的鏡像層,取消重複構建並選用可用的鏡像作爲當前指令的構建結果。關於鏡像分層的更多細節不再展開,有興趣的搜索“鏡像 分層”即可。
Running in 59bdc0332ec6部分
前面說此類輸出的代表啓動了新容器,我們想辦法驗證一下:刻意修改Dockerfile內的指令RUN dotnet1 restore xxxx使其異常中斷,得到輸出內容如下:
Step 7/16 : RUN dotnet1 restore "Tccc.BIZ.CMS.Srv/Tccc.BIZ.CMS.Srv.csproj" ---> Running in 887fed352f5e /bin/sh: 1: dotnet1: not found The command '/bin/sh -c dotnet1 restore "Tccc.BIZ.CMS.Srv/Tccc.BIZ.CMS.Srv.csproj"' returned a non-zero code: 127
此時執行docker ps -a看到確實剛剛啓動了容器3449e0b5f619:
既然是啓動容器,那麼此容器依賴的鏡像是什麼?通過上面的截圖,可以看到當前指令啓動的容器恰好就是上一個指令生成的鏡像2da6630520ad。
---> a6c2a40e277c生成鏡像部分
這一步實際就是將running的容器保存爲鏡像(由於鏡像的分層結構設計,且容器是在鏡像分層的基礎上附加了一層讀寫層,因此保存操作就是將此容器的讀寫層轉換爲只讀層並保存爲靜態的鏡像),我們通過docker inspect 命令(查看容器/鏡像完整詳細信息的命令)查看生成的鏡像的詳細信息來驗證一下這個結論。輸出內容的Container屬性值恰好就是Step 12/16開頭創建的容器ID。同時查看截圖紅色部分,內容也正好是Dockerfile這一步驟的指令,這也說名這層的含義是存儲的相對於上一層的改變,即這條指令產生變更的部分;
總結:
現在回顧以上步驟可以看到,我們執行docker build操作後,docker daemon就按照提供的dockerfile指令一步一步的層層處理,構建出最終需要的鏡像;限於篇幅原因,本文無法涵蓋其中所有的細節,有興趣的讀者還可以繼續研究鏡像分層、鏡像cache、存儲驅動storage driver等詳細細節。
至此,本文以構建一個asp.net core應用鏡像作爲線索,闡述了docker構建鏡像的總體步驟和相關內部細節,希望能讓你對鏡像構建的內部過程有一個全面、深刻的理解。