Docker鏡像優化
前言
上篇博文說到使用Visual Studio Tools for Docker幫助我們生成Dockerfile,現在我們討論下生成的Dockerfile的優劣。
一、以往Dockerfile構建模式
(1)發布API項目
新建Web API項目,項目名稱為API
在項目所在目錄輸入指令:dotnet publish --runtime ubuntu.16.04-x64
(2)創建鏡像
在發布目錄新建Dockerfile文件,黏貼以下代碼
# 聲明使用的基礎鏡像FROM microsoft/dotnet:2.1-sdk # 設置工作目錄 WORKDIR /app # 將本地應用拷貝到 容器/app 目錄下 COPY ./ ./ # 設置導出端口 EXPOSE 80 # 指定應用入口點 API.dll代表的是主程序文件 ENTRYPOINT ["dotnet", "API.dll"]
在Dockerfile所在的目錄下輸入指令生成鏡像:docker build -t api .
不要忘記後面有一個點 .
生成api:latest鏡像成功
參考博客操作步驟:https://www.cnblogs.com/bluesummer/p/8087326.html
(3)分析鏡像
我們來查看鏡像信息,輸入:docker images
發現api:latest鏡像的大小為1.83GB,大得有點誇張。
現在我們來分析這個Dockerfile:
FROM microsoft/dotnet:2.1-sdk
FROM:指定所創建的基礎鏡像,如果本地不存在,則默認去Docker Hub下載指定鏡像。任何Dockerfile中的第一條指令必須為FROM指令。並且,如果在同一個Dockerfile中創建多個鏡像,可以使用多個FROM指令。
文中Dockerfile基於microsoft/dotnet:2.1-sdk
那問題來了,我們應該采用哪個鏡像作為基礎鏡像,來創建我們自己的鏡像呢?
查閱了微軟官網文檔說明:
microsoft/dotnet:<version>-sdk包含帶有.NET Core 和命令行工具 (CLI) 的.NET Core SDK。此鏡像將映射到開發方案,可使用此鏡像進行本地開發、調試和單元測試。
microsoft/dotnet:<version>-runtime包含.NET Core(runtime和庫),並且針對在生產環境中運行.NET Core 應用進行了優化。
我們修改Dockerfile的FROM指令為
FROM microsoft/dotnet:2.1-aspnetcore-runtime
其他指令保持不變,新建一個api:1.0.0的鏡像。
可以看到,鏡像大小瞬間小了很多。但是我們還不滿足,因為原本microsoft/dotnet:2.1-aspnetcore-runtime鏡像才253MB,而我們的鏡像是353MB。
WORKDIR /app
WORKDIR:為後續RUN、CMD和ENTRYPOINT指令設置工作目錄。可以使用多個WORKDIR,後續命令如果參數是相對路徑,則會基於之前命令指定路徑。例如:
WORKDIR /app
WORKDIR publish
WORKDIR api
最終路徑為:/app/publish/api
這個指令在這裏看上去應該優化不了。
COPY ./ ./
COPY:格式為COPY <src> <dest>。復制本地主機的<src>(為Dockerfile所在的目錄的相對路徑)下的內容到容器中的<dest>下。目標路徑不存在,則自動創建。
./ ./ 就是將本地Dockerfile所在的目錄的文件和文件夾都復制到鏡像中的/app目錄下。
註意區分以下兩條指令:
COPY test relativeDir/ # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/ # adds "test" to /absoluteDir/
想了下,這裏應該是不合理的,把全部文件都復制過去,這肯定會造成鏡像變大。
又想了下,這個已經是我們發布過的文件,那還能怎麽辦。問題先放這裏,等會再解決。
EXPOSE 80
EXPOSE:聲明鏡像內服務所監聽的端口。
ENTRYPOINT ["dotnet", "API.dll"]
ENTRYPOINT:指定鏡像的默認入口命令,該入口命令會在啟動容器是作為跟命令執行,所有傳入值作為該命令的參數。支持兩種格式:
ENTRYPOINT [“executable”,”param1”,”param2”] (exec調用執行)
ENTRYPOINT command param1 param2 (shell中執行)
每個Dockerfile裏若出現多個ENTRYPOINT,只有放後面的那個ENTRYPOINT有效。
Dockerfile參考:https://docs.docker.com/engine/reference/builder/#usage 裏面有各個指令的詳細介紹。
二、multi-stage builds(多階段構建)
在多階段構建的過程中,我們在Dockerfile使用多個FROM指令,每個FROM指令使用不同的基礎鏡像構成了不同階段。你可以選擇從上一個階段的產物(artifacts)復制到下一個階段,從而確保不會把不需要的東西帶到下一階段。這種方法可以有效減小Docker鏡像的大小。
默認情況下,這些階段沒有被命名,可以通過它們的整數引用它們,第一個FROM指令從0開始。然而,我們也可以以as <NAME>的方式命名每個階段。
參考官網:https://docs.docker.com/develop/develop-images/multistage-build/
以下我們用Visual Studio Tools for Docker生成的Dockerfile進行介紹。
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 FROM microsoft/dotnet:2.1-sdk AS build WORKDIR /src COPY ["API/API.csproj", "API/"] RUN dotnet restore "API/API.csproj" COPY . . WORKDIR "/src/API" RUN dotnet build "API.csproj" -c Release -o /app FROM build AS publish RUN dotnet publish "API.csproj" -c Release -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . ENTRYPOINT ["dotnet", "API.dll"]
它分為四個階段,分別是base、build、publish和final。
base階段:上面已經分析了這裏不再詳述。
build階段:
FROM microsoft/dotnet:2.1-sdk AS build以microsoft/dotnet:2.1-sdk為基礎鏡像
WORKDIR /src工作目錄為/src
COPY ["API/API.csproj", "API/"]把Dockerfile所在目錄的API/API.csproj文件復制到容器的/src/API/中
RUN dotnet restore "API/API.csproj"在當前鏡像的基礎上執行dotnet restore "API/API.csproj",把API項目的依賴項和工具還原,並輸出結果。
dotnet restore這條命令是使用 NuGet 還原依賴項以及在 project 文件中指定項目特殊的工具執行對依賴項和工具的還原。
COPY . .
WORKDIR "/src/API"切換工作目錄到/src/API,可以用WORKDIR API替代,但明顯第一種方法更直觀。
RUN dotnet build "API.csproj" -c Release -o /app執行dotnet build "API.csproj" -c Release -o /app,以Release模式生成API項目及其所有依賴項並把生成的二進制文件輸出到/app目錄。
publish階段:
FROM build AS publish以上一階段build為基礎鏡像
RUN dotnet publish "API.csproj" -c Release -o /app執行dotnet publish "API.csproj" -c Release -o /app,以Release模式把API應用程序及其依賴項打包到/app目錄以部署到托管系統。
final階段:
FROM base AS final以上階段base為基礎鏡像
WORKDIR /app以/app為工作目錄
COPY --from=publish /app .把publish階段生成的/app目錄下的文件和文件夾復制到/app目錄。
這樣做的原因是,上階段的產物是不會帶到下一階段。
現在可以解釋為什麽使用Visual Studio Tools for Docker不用發布也能生成可運行的鏡像了,它實時 (JIT) 編譯,提高啟動性能。而且它只獲取了程序運行所需要的文件放到鏡像中。
我們生成一個最新的鏡像
發現它和microsoft/dotnet:2.1-aspnetcore-runtime鏡像一樣大,這下滿足了,畢竟我們沒寫什麽代碼到項目中。
三、優化Docker鏡像的方向
1.精簡鏡像用途,盡量讓每個鏡像的用途都比較集中、單一,避免構造大而復雜,功能多的鏡像。
2.選用合適的基礎鏡像。
3.在Dockerfile中寫上註釋,方便維護和他人使用。
4.正確使用版本號,如1.0.1。
5.使用多階段構建鏡像。
Docker鏡像優化