教程:使 .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 社区版
- Dockerfile 和 .NET 示例应用的临时工作文件夹。 在本教程中,docker-working 用作工作文件夹的名称。
- .NET 7+ SDK
如果已安装 .NET,请使用dotnet --info
命令来确定使用的是哪个 SDK。 - Docker 社区版
- 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
命令会新建一个名为“应用”的文件夹,并生成一个“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
计数到 5。
重要
--
之后的参数都不传递到 dotnet run
命令,而是传递到你的应用程序。
发布 .NET 应用
在将 .NET 应用添加到 Docker 映像之前,必须先发布该应用。 最好让容器运行应用的已发布版本。 若要发布应用,请运行以下命令:
dotnet publish -c Release
此命令将应用编译到“发布”文件夹中 。 从工作文件夹到“发布”文件夹的路径应为 .\App\bin\Release\net8.0\publish\
。
此命令将应用编译到“发布”文件夹中 。 从工作文件夹到“发布”文件夹的路径应为 .\App\bin\Release\net7.0\publish\
。
在“应用”文件夹中获取“发布”文件夹的目录列表,以验证 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 容器注册表(MCR,mcr.microsoft.com)是 Docker Hub 的联合,可托管可公开访问的容器。 dotnet
段是容器存储库,其中 sdk
或 aspnet
段是容器映像名称。 该映像使用 8.0
进行标记,它用于版本控制。 因此,mcr.microsoft.com/dotnet/aspnet:8.0
为 .NET 8.0 运行时。 请确保拉取的运行时版本与 SDK 面向的运行时一致。 例如,在上一部分中创建的应用使用了 .NET 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 以及该 OS 的版本,选择映像名称。 可以在 .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 容器注册表(MCR,mcr.microsoft.com)是 Docker Hub 的联合,可托管可公开访问的容器。 dotnet
段是容器存储库,其中 sdk
或 aspnet
段是容器映像名称。 该映像使用 7.0
进行标记,它用于版本控制。 因此,mcr.microsoft.com/dotnet/aspnet:7.0
为 .NET 7.0 运行时。 请确保拉取的运行时版本与 SDK 面向的运行时一致。 例如,在上一部分中创建的应用使用了 .NET 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
是映像 ID。 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
是映像 ID。 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 映像的基础之上创建容器。 该命令的输出显示已创建容器的“CONTAINER ID”(你的 ID 会有所不同):
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 映像。 可以使用 IMAGE ID 或 REPOSITORY:TAG 格式字符串。
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
命令来列出已安装的映像。
提示
映像文件可能很大。 通常情况下,需要删除在测试和开发应用期间创建的临时容器。 如果计划在相应运行时的基础之上生成其他映像,通常会将基础映像与运行时一同安装。