教學課程:容器化 .NET 應用程式
在此教學課程中,您將了解如何使用 Docker 來將 .NET 應用程式容器化。 容器有許多功能和優點,例如不可變的基礎結構、提供可攜式結構,以及啟用可擴縮性。 映像可用來為您的本機開發環境、私人雲端,或公用雲端建立容器。
在本教學課程中,您已:
- 建立及發佈簡單的 .NET 應用程式
- 建立及設定適用於 .NET 的 Dockerfile
- 建置 Docker 映像
- 建立及執行 Docker 容器
您將了解 .NET 應用程式的 Docker 容器建置及部署工作。 「Docker 平台」會使用「Docker 引擎」快速建置應用程式,並將其封裝為「Docker 映像」。 這些映像是以 Dockerfile 格式所撰寫,可在分層式容器中部署及執行。
注意
本教學課程不適用於 ASP.NET Core 應用程式。 如果您使用 ASP.NET Core,請參閱了解如何容器化 ASP.NET Core 應用程式教學課程。
必要條件
安裝下列先決條件:
- .NET 8+ SDK
如果您已安裝 .NET,則請使用dotnet --info
命令來判斷您使用的是哪一個 SDK。 - Docker Community Edition
- Dockerfile 和 .NET 範例應用程式的暫存工作資料夾。 在本教學課程中,docker-working 名稱會作為工作資料夾使用。
- .NET 7+ SDK
如果您已安裝 .NET,則請使用dotnet --info
命令來判斷您使用的是哪一個 SDK。 - Docker Community Edition
- Dockerfile 和 .NET 範例應用程式的暫存工作資料夾。 在本教學課程中,docker-working 名稱會作為工作資料夾使用。
建立 .NET 應用程式
您需要 Docker 容器執行的 .NET 應用程式。 開啟您的終端機,建立工作資料夾 (如果沒有),並進入該資料夾。 在工作資料夾中執行下列命令,在名為 App 的子目錄中建立新專案:
dotnet new console -o App -n DotNet.Docker
您的資料夾樹狀目錄會如下所示:
📁 docker-working
└──📂 App
├──DotNet.Docker.csproj
├──Program.cs
└──📂 obj
├── DotNet.Docker.csproj.nuget.dgspec.json
├── DotNet.Docker.csproj.nuget.g.props
├── DotNet.Docker.csproj.nuget.g.targets
├── project.assets.json
└── project.nuget.cache
dotnet new
命令會建立名為 App 的新資料夾,並產生 "Hello World" 主控台應用程式。 從終端機會話變更目錄並瀏覽至 App 資料夾。 使用 dotnet run
命令來啟動應用程式。 應用程式會執行,並在命令下方列印 Hello World!
:
cd App
dotnet run
Hello World!
預設範本所建立的應用程式會列印到終端機,然後立即終止。 針對此教學課程,您會使用無限期執行迴圈的應用程式。 在文字編輯器中開啟 Program.cs 檔案。
提示
如果您使用 Visual Studio Code,請從上一個終端工作階段輸入下列命令:
code .
這會開啟包含專案 Visual Studio Code 的 App 資料夾。
Program.cs 看起來應該像下列 C# 程式碼:
Console.WriteLine("Hello World!");
使用下列每秒計算數字的程式碼來取代檔案:
var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
Console.WriteLine($"Counter: {++counter}");
await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}
var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
Console.WriteLine($"Counter: {++counter}");
await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}
儲存檔案,然後使用 dotnet run
再次測試程式。 請記住此應用程式會無限期執行。 使用取消命令 Ctrl + C 來停止它。 以下是一個範例輸出:
dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C
如果您在命令列上傳遞一個數字給應用程式,它將只會計算到該數量,然後結束。 搭配 dotnet run -- 5
試用它以計算到五。
重要
--
之後的任何參數都不會傳遞至 dotnet run
命令,而會改為傳遞至您的應用程式。
發佈 .NET 應用程式
將 .NET 應用程式新增至 Docker 映像之前,必須先發佈。 最好讓容器執行已發佈的應用程式版本。 若要發佈應用程式,請執行下列命令:
dotnet publish -c Release
此命令會將您的應用程式編譯至 publish 資料夾。 從工作資料夾通往 publish 資料夾的路徑應該是 .\App\bin\Release\net8.0\publish\
。
此命令會將您的應用程式編譯至 publish 資料夾。 從工作資料夾通往 publish 資料夾的路徑應該是 .\App\bin\Release\net7.0\publish\
。
從 App 資料夾取得發佈資料夾的目錄清單,以確認已建立 DotNet.Docker.dll 檔案。
dir .\bin\Release\net8.0\publish\
Directory: C:\Users\default\App\bin\Release\net8.0\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 9/22/2023 9:17 AM 431 DotNet.Docker.deps.json
-a--- 9/22/2023 9:17 AM 6144 DotNet.Docker.dll
-a--- 9/22/2023 9:17 AM 157696 DotNet.Docker.exe
-a--- 9/22/2023 9:17 AM 11688 DotNet.Docker.pdb
-a--- 9/22/2023 9:17 AM 353 DotNet.Docker.runtimeconfig.json
dir .\bin\Release\net7.0\publish\
Directory: C:\Users\default\App\bin\Release\net7.0\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2/13/2023 1:52 PM 431 DotNet.Docker.deps.json
-a--- 2/13/2023 1:52 PM 6144 DotNet.Docker.dll
-a--- 2/13/2023 1:52 PM 153600 DotNet.Docker.exe
-a--- 2/13/2023 1:52 PM 11052 DotNet.Docker.pdb
-a--- 2/13/2023 1:52 PM 253 DotNet.Docker.runtimeconfig.json
建立 Dockerfile
docker build
命令會使用 Dockerfile 檔案來建立容器映像。 此檔案是名為 Dockerfile 且沒有副檔名的文字檔案。
在包含 .csproj 的目錄中建立名為 Dockerfile 的檔案,然後在文字編輯器中開啟它。 本教學課程使用 ASP.NET Core 執行階段映像 (其中包含 .NET 執行階段映像),並與 .NET 主控台應用程式相對應。
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
注意
雖然可能已經使用映像 mcr.microsoft.com/dotnet/runtime:8.0
,但在這裡刻意使用 ASP.NET Core 執行階段映像。
提示
此 Dockerfile 會使用多階段組建,藉由分層建置並只保留必要的成品,最佳化映像的最終大小。 如需詳細資訊,請參閱 Docker Docs:多階段組建。
FROM
關鍵字需要完整的 Docker 容器映像名稱。 Microsoft Container Registry (MCR,mcr.microsoft.com) 是 Docker Hub 的一個分支,可裝載可公開存取的容器。 dotnet
區段是容器存放庫,而 sdk
或 aspnet
區段則是容器映像名稱。 映像會以 8.0
標記,用於版本設定。 因此,mcr.microsoft.com/dotnet/aspnet:8.0
是 .NET 8.0 執行階段。 確定您所提取的執行階段版本符合您 SDK 以其為目標的執行階段。 例如,在上一節中建立的應用程式會使用 8.0 SDK 和 Dockerfile 中參考的基底映像加上 8.0 標記。
重要
使用 Windows 型容器映像時,您必須指定映像標記,而不僅僅是 8.0
,例如,mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809
而不是 mcr.microsoft.com/dotnet/aspnet:8.0
。 根據您使用的 Nano Server 或 Windows Server Core,以及該作業系統的版本來選取映像名稱。 您可以在 NET 的 Docker Hub 頁面上找到所有支援標記的完整清單。
儲存 Dockerfile 檔案。 工作資料夾的目錄結構應如下所示。 部分更下層的檔案和資料夾已省略,以節省文章空間:
📁 docker-working
└──📂 App
├── Dockerfile
├── DotNet.Docker.csproj
├── Program.cs
├──📂 bin
│ └──📂 Release
│ └──📂 net8.0
│ └──📂 publish
│ ├── DotNet.Docker.deps.json
│ ├── DotNet.Docker.exe
│ ├── DotNet.Docker.dll
│ ├── DotNet.Docker.pdb
│ └── DotNet.Docker.runtimeconfig.json
└──📁 obj
└──...
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
注意
雖然可能已經使用 mcr.microsoft.com/dotnet/runtime:7.0
映像,但在這裡刻意使用 ASP.NET Core 執行階段映像。
提示
此 Dockerfile 會使用多階段組建,藉由分層建置並只保留必要的成品,最佳化映像的最終大小。 如需詳細資訊,請參閱 Docker Docs:多階段組建。
FROM
關鍵字需要完整的 Docker 容器映像名稱。 Microsoft Container Registry (MCR,mcr.microsoft.com) 是 Docker Hub 的一個分支,可裝載可公開存取的容器。 dotnet
區段是容器存放庫,而 sdk
或 aspnet
區段則是容器映像名稱。 映像會以 7.0
標記,用於版本設定。 因此,mcr.microsoft.com/dotnet/aspnet:7.0
是 .NET 7.0 執行階段。 確定您所提取的執行階段版本符合您 SDK 以其為目標的執行階段。 例如,在上一節中建立的應用程式會使用 7.0 SDK 和 Dockerfile 中參考的基底映像加上 7.0 標記。
儲存 Dockerfile 檔案。 工作資料夾的目錄結構應如下所示。 部分更下層的檔案和資料夾已省略,以節省文章空間:
📁 docker-working
└──📂 App
├── Dockerfile
├── DotNet.Docker.csproj
├── Program.cs
├──📂 bin
│ └──📂 Release
│ └──📂 net7.0
│ └──📂 publish
│ ├── DotNet.Docker.deps.json
│ ├── DotNet.Docker.exe
│ ├── DotNet.Docker.dll
│ ├── DotNet.Docker.pdb
│ └── DotNet.Docker.runtimeconfig.json
└──📁 obj
└──...
從終端機執行下列命令:
docker build -t counter-image -f Dockerfile .
Docker 將會處理 Dockerfile 中的每一行。 命令中的 .
會 docker build
設定映像的建置內容。 參數 -f
是 Dockerfile 的路徑。 此命令會建置映像,並建立名為 counter-image 的本機存放庫以指向該映像。 當此命令完成之後,執行 docker images
以查看已安裝的映像清單:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 2f15637dc1f6 10 minutes ago 217MB
counter-image
存放庫是映像的名稱。 latest
標記是用來識別映像的標記。 2f15637dc1f6
是映像識別碼。 10 minutes ago
是映像的建立時間。 217MB
是映像的大小。 Dockerfile 的最後一個步驟是從映像建立容器並執行應用程式、將已發佈的應用程式複製到容器,以及定義進入點。
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 2f15637dc1f6 10 minutes ago 208MB
counter-image
存放庫是映像的名稱。 latest
標記是用來識別映像的標記。 2f15637dc1f6
是映像識別碼。 10 minutes ago
是映像的建立時間。 208MB
是映像的大小。 Dockerfile 的最後一個步驟是從映像建立容器並執行應用程式、將已發佈的應用程式複製到容器,以及定義進入點。
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
COPY
命令會指示 Docker,將您電腦上指定的資料夾複製到容器中的資料夾。 在此範例中,會將 publish 資料夾複製到容器中名為 App/out 的資料夾。
WORKDIR
命令會將容器內的目前目錄變更為 App。
下一個命令 ENTRYPOINT
會指示 Docker 將容器設定為以可執行檔的形式執行。 當容器啟動時,ENTRYPOINT
命令就會執行。 當此命令結束時,容器將會自動停止。
提示
在 .NET 8 之前,設定為以唯讀身分執行的容器可能會失敗,並顯示 Failed to create CoreCLR, HRESULT: 0x8007000E
。 若要處理這個問題,請將 DOTNET_EnableDiagnostics
環境變數指定為 0
(在 ENTRYPOINT
步驟之前):
ENV DOTNET_EnableDiagnostics=0
如需多項 .NET 環境變數的詳細資訊,請參閱 .NET 環境變數。
注意
.NET 6 會針對設定 .NET 執行階段行為的環境變數,透過前置詞 DOTNET_
(而非 COMPlus_
) 進行標準化。 不過,COMPlus_
前置詞將繼續運作。 如果使用舊版的 .NET 執行階段,則您仍應對環境變數使用 COMPlus_
前置詞。
建立容器
您現在已有包含應用程式的映像,您可以建立一個容器。 您可以兩種方式建立容器。 首先,建立已停止的新容器。
docker create --name core-counter counter-image
docker create
命令會根據 counter-image 映像建立容器。 該命令的輸出會顯示已建立容器的容器識別碼 (您的映像識別碼將會不同):
d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf
若要查看「所有」容器的清單,請使用 docker ps -a
命令:
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d0be06126f7d counter-image "dotnet DotNet.Docke…" 12 seconds ago Created core-counter
管理容器
容器是以特定名稱 core-counter
建立,此名稱是用來管理容器。 下列範例會使用 docker start
命令來啟動容器,然後使用 docker ps
命令只顯示正在執行的容器:
docker start core-counter
core-counter
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf01364df453 counter-image "dotnet DotNet.Docke…" 53 seconds ago Up 10 seconds core-counter
同樣地,docker stop
命令會停止容器。 下列範例會使用 docker stop
命令來停止容器,然後使用 docker ps
命令顯示沒有任何容器正在執行:
docker stop core-counter
core-counter
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
連線到容器
當容器正在執行之後,您可以連線到它以查看輸出。 使用 docker start
和 docker attach
命令來啟動容器,並查看輸出資料流。 在此範例中,會使用 Ctrl + C 按鍵輸入來將執行中的容器中斷連結。 除非另有指定,否則此按鍵輸入會結束容器中的程序,這將停止容器。 --sig-proxy=false
參數可確保 CTRL+C 不會停止容器中的程序。
當您從容器中斷連結之後,請重新連結以確認它仍在執行且正在進行計算。
docker start core-counter
core-counter
docker attach --sig-proxy=false core-counter
Counter: 7
Counter: 8
Counter: 9
^C
docker attach --sig-proxy=false core-counter
Counter: 17
Counter: 18
Counter: 19
^C
刪除容器
在本文中,您不想讓容器閒置且不執行任何動作。 刪除您先前建立的容器。 如果容器正在執行,請停止它。
docker stop core-counter
下列範例會列出所有容器。 它接著會使用 docker rm
命令來刪除容器,然後第二次檢查任何執行中的容器。
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2f6424a7ddce counter-image "dotnet DotNet.Dock…" 7 minutes ago Exited (143) 20 seconds ago core-counter
docker rm core-counter
core-counter
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
單一執行
Docker 提供 docker run
命令來建立容器,並以單一命令執行。 使用此命令,就不需依序執行 docker create
及 docker start
。 您也可以設定此命令,在容器停止時自動刪除容器。 例如,使用 docker run -it --rm
來執行兩個動作,首先,自動使用目前的終端機連線到容器,然後在容器完成時將其移除:
docker run -it --rm counter-image
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C
容器也會將參數傳遞至 .NET 應用程式的執行。 若要指示 .NET 應用程式只計算到 3,請傳入 3。
docker run -it --rm counter-image 3
Counter: 1
Counter: 2
Counter: 3
搭配 docker run -it
,則 Ctrl+C 命令會停止正在容器中執行的程序,接著停止容器。 由於已提供 --rm
參數,因此會在程序停止時自動刪除容器。 確認它不存在:
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
變更 ENTRYPOINT
docker run
命令也可讓您從 Dockerfile 修改 ENTRYPOINT
命令並執行其他動作,但只適用於該容器。 例如,使用下列命令來執行 bash
或 cmd.exe
。 視需要編輯命令。
在此範例中,ENTRYPOINT
會變更為 cmd.exe
。 按下 Ctrl + C 以結束程序並停止容器。
docker run -it --rm --entrypoint "cmd.exe" counter-image
Microsoft Windows [Version 10.0.17763.379]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\>dir
Volume in drive C has no label.
Volume Serial Number is 3005-1E84
Directory of C:\
04/09/2019 08:46 AM <DIR> app
03/07/2019 10:25 AM 5,510 License.txt
04/02/2019 01:35 PM <DIR> Program Files
04/09/2019 01:06 PM <DIR> Users
04/02/2019 01:35 PM <DIR> Windows
1 File(s) 5,510 bytes
4 Dir(s) 21,246,517,248 bytes free
C:\>^C
基本命令
Docker 有許多不同的命令,可建立、管理及與容器和映像互動。 這些 Docker 命令對於管理容器而言非常重要:
清除資源
在此教學課程中,您建立了容器與映像。 您可以視需要刪除這些資源。 使用下列命令
列出所有容器
docker ps -a
停止依其名稱執行的容器。
docker stop core-counter
刪除容器
docker rm core-counter
接下來,在電腦上刪除您不再需要的任何映像。 刪除 Dockerfile 所建立的映像,然後刪除 Dockerfile 以其為基礎的 .NET 映像。 您可以使用映像識別碼或存放庫:標記格式的字串。
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:8.0
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:7.0
使用 docker images
命令來查看已安裝的映像清單。
提示
映像檔可能很大。 一般而言,會移除您在測試及開發應用程式時所建立的暫存容器。 如果您打算根據已安裝的執行階段建置其他映像,您通常會使用該執行階段來保存基底映像。