本文選自『.NET大牛之路』知識星球,發布于2022年05月25日。
今天我們一起來寫 Dockerfile 構建一個 ASP.NET Core 應用鏡像,同時還會將鏡像發布到 Docker Hub 倉庫。
1創建示例 Web 應用程序
為了演示,我們先創建一個 ASP.NET Core 應用程序:
PS D:\Samples> dotnet new web -o AspNetDemo
已成功創建模板“ASP.NET Core Empty”。正在處理創建后操作...
在 D:\Samples\AspNetDemo\AspNetDemo.csproj 上運行 “dotnet restore”...正在確定要還原的項目…已還原 D:\Samples\AspNetDemo\AspNetDemo.csproj (用時 77 ms)。
已成功還原。
項目創建好了,檢查一下看看是否能正常運行:
PS D:\Samples> cd .\AspNetDemo\
PS D:\Samples\AspNetDemo> dotnet run
正在生成...
info: Microsoft.Hosting.Lifetime[14]Now listening on: https://localhost:7000
info: Microsoft.Hosting.Lifetime[14]Now listening on: http://localhost:5276
info: Microsoft.Hosting.Lifetime[0]Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]Content root path: D:\Samples\AspNetDemo\
打開輸出提示的地址 https://localhost:7000
,瀏覽器正常顯示 “Hellow World”,說明應用程序正常。
然后我們在 AspNetDemo
目錄中添加一個 Dockerfile
文件,為下文作準備。
2依賴本地環境構建鏡像
我們可以在 DockerHub 找到我們需要的 ASP.NET Core 運行時基礎鏡像:

現在,要把我們的 AspNetDemo
應用通過鏡像的方式發布,利用我們之前學過的 Docker 知識,我們很自然會想到這樣的思路:通過 dotnet publish
命令打包發布文件,然后把發布文件復制到 ASP.NET Core 運行時基礎鏡像中。
于是我們先在本地(bin/Publish
目錄)生成好發布文件:
PS D:\Samples\AspNetDemo> dotnet publish -c release -o bin/Publish正在確定要還原的項目…所有項目均是最新的,無法還原。AspNetDemo -> D:\Samples\AspNetDemo\bin\release\net6.0\AspNetDemo.dllAspNetDemo -> D:\Samples\AspNetDemo\bin\Publish\
然后 Dockerfile 文件可以這樣寫:
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY bin/Publish .
ENTRYPOINT ["dotnet", "AspNetDemo.dll"]
開始構建鏡像:
PS D:\Samples\AspNetDemo> docker build -t aspnetdemo .
[+] Building 0.8s (8/8) FINISHED
...PS D:\Samples\AspNetDemo> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
aspnetdemo latest 62c8c40cbc70 33 seconds ago 208MB
試試在本地使用該鏡像運行容器:
PS D:\Samples\AspNetDemo> docker run -d?-p?80:80 aspnetdemo
a4d67637585c67384a6c7a3a9e8a39acc345253730ce22f39b7afdedec353397
打開瀏覽器訪問 localhost
效果如下:

看起來還不錯。
3多階段構建鏡像
相信很多童鞋已經想到了上面依賴本地的開發環境構建鏡像存在的問題了。我們前面構建的 aspnetdemo
鏡像,是先在本地生成好了發布的文件再復制到鏡像里的。這樣存在的一個明顯問題是,其他人如果環境和我們的不一致,構建的鏡像就可能是一個有問題的鏡像,甚至直接構建失敗。這種例子很常見,比如同一套代碼,在你的機器上可以正常運行,因為環境不同(比如未安裝指定的軟件、未配置環境變量等),在同事機器上可能就運行不起來。
要避免這種情況,生成發布文件甚至是開發測試的過程,就不能依賴本地的開發環境來做了。即,我們要把生成發布文件的過程也放到 Dockerfile 中去做。
但由于 ASP.NET Core 運行時鏡像不具有編譯的能力,所以我們需要把基礎鏡像換成 .NET SDK 鏡像。這樣就可以了嗎?這樣也不是不可以,但是 .NET SDK 鏡像會比 ASP.NET Core 運行時鏡像大很多,我們可以比較一下:
PS D:\Samples\AspNetDemo> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
mcr.microsoft.com/dotnet/sdk 6.0 d3863aa157b5 6 days ago 736MB
mcr.microsoft.com/dotnet/aspnet 6.0 683c56113596 8 weeks ago 208MB
可以看到 .NET SDK 鏡像比 ASP.NET Core 運行時鏡像大了 500 多 MB,這顯然會大大降低鏡像發布的速度。
這時候我們就需要用到多階段構建了,思路是把鏡像的構建分成多個階段,不同的階段使用不同的基礎鏡像,前面的所有階段都只是為最后一個階段做準備,最終發布的也是最后一個階段。
下面使用多階段構建來改寫 Dockerfile,參考如下:
# 階段一:build
# 選擇 SDK 鏡像用于編譯源碼和生成發布文件
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /source
# 復制源代碼
COPY *.csproj *.cs .
# 生成發布文件
RUN dotnet publish -c release -o /app# 階段二:final
# 使用 ASP.NET Core 運行時鏡像
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS final
WORKDIR /app
# 從 build 階段復制生成好的發布文件
COPY --from=build /app .
ENTRYPOINT ["dotnet", "AspNetDemo.dll"]
這個 Dockerfile 還可以繼續優化,我將在下一節課講鏡像的優化時再改寫它。
為了觀察效果,我們稍微修改一下 Program.cs
中 Http 響應的內容:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();app.MapGet("/", () => "v2: Hello Docker!");app.Run();
再次構建并運行容器:
PS D:\Samples\AspNetDemo> docker image build -t aspnetdemo .
[+] Building 5.8s (13/13) FINISHED
...PS D:\Samples\AspNetDemo> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
aspnetdemo latest d6a596b1514d 55 seconds ago 208MBPS D:\Samples\AspNetDemo> docker run -d?-p?80:80 aspnetdemo
e9b9045299f5a3b7614cb3cee91b00ebe67a066b1f65eff46369fa1844b1d824
打開瀏覽器訪問 localhost
驗證一下效果:

可以看到我們雖然把生成發布文件的過程放到了 Dockerfile 中,但通過多階段構建,最后構建出來的鏡像也是 208M,和前面一樣。
所以,我們可以把編譯運行所需要的環境配置都寫到 Dockerfile 中,這樣可以保證任何一臺機器都可以順利構建鏡像,且不管誰來構建,相同的源代碼構建出來的鏡像都是一樣的。
4發布鏡像
最后我們可以把構建好的鏡像發布到自己的 Docker 倉庫,這里以 Docker Hub 為例(實際生產環境請發布到自己的私有倉庫)。
先在 Docker Hub 創建一個 Repositry:

推送鏡像前,需要在本地登錄一下:
PS D:\Samples\AspNetDemo> docker login
Authenticating with existing credentials...
Login Succeeded
然后給我們的鏡像打上一個標簽(默認是latest
):
PS D:\Samples\AspNetDemo> docker tag aspnetdemo liamwang/aspnetdemo
# 也可以指定標簽:docker tag aspnetdemo:latest liamwang/aspnetdemo:latest
然后推送到遠程倉庫:
PS D:\Samples\AspNetDemo> docker push liamwang/aspnetdemo
The push refers to repository [docker.io/liamwang/aspnetdemo]
e68e6a7d93c2: Pushed
ace5cec48f84: Pushed
17aff088b762: Pushed
9a515fdf7f03: Pushed
c4d9ca739af5: Pushed
3f94255da7c2: Pushed
608f3a074261: Pushed
latest: digest: sha256:dc479f2e52d48b3a81c0a83b5c740a085b299d046f268d21bb61c5bcfa5ae608 size: 1787
PS D:\Samples\AspNetDemo>
這步完成后,可以在 Docker Hub 上看到已發布的鏡像:

然后我們可以到任意一臺服務器 pull 該鏡像運行容器了。
5小結
本節課我們以 ASP.NET Core 應用為例,先是用依賴本地環境的方式構建了鏡像,分析了這種方式存在的問題,然后講了如何使用多階段構建來解決這個問題,最后演示了如何把已經構建好的鏡像發布到自己的 Docker 鏡像倉庫。
下節課我們來解析和理解鏡像的分層,理解鏡像的分層可以幫助我們優化鏡像的構建過程,也有助于制作更優質的鏡像。