教程:使 .NET Core 应用程序容器化Tutorial: Containerize a .NET Core app

在本教程中,你将了解如何使用 Docker 容器化 .NET Core 应用。In this tutorial, you'll learn how to containerize a .NET Core application with Docker. 容器具有很多特性和优点,如具有不可变的基础结构、提供可移植的体系结构和实现可伸缩性。Containers have many features and benefits, such as being an immutable infrastructure, providing a portable architecture, and enabling scalability. 此映像可用于为本地开发环境、私有云或公有云创建容器。The image can be used to create containers for your local development environment, private cloud, or public cloud.

在本教程中,你将了解:In this tutorial, you:

  • 创建并发布简单的 .NET Core 应用Create and publish a simple .NET Core app
  • 创建并配置用于 .NET Core 的 DockerfileCreate and configure a Dockerfile for .NET Core
  • 生成 Docker 映像Build a Docker image
  • 创建并运行 Docker 容器Create and run a Docker container

你将了解用于 .NET Core 应用的 Docker 容器生成和部署任务。You'll understand the Docker container build and deploy tasks for a .NET Core application. Docker 平台 使用 Docker 引擎 快速生成应用,并将应用打包为 Docker 映像 。The Docker platform uses the Docker engine to quickly build and package apps as Docker images. 这些映像采用 Dockerfile 格式编写,以供在分层容器中部署和运行。These images are written in the Dockerfile format to be deployed and run in a layered container.

备注

本教程不适用于 ASP.NET Core 应用 。This tutorial is not for ASP.NET Core apps. 如果使用的是 ASP.NET Core,请参阅教程了解如何容器化 ASP.NET Core 应用程序If you're using ASP.NET Core, see the Learn how to containerize an ASP.NET Core application tutorial.

先决条件Prerequisites

安装以下必备组件:Install the following prerequisites:

  • .NET Core 3.1 SDK.NET Core 3.1 SDK
    如果已安装 .NET Core,请使用 dotnet --info 命令来确定使用的是哪个 SDK。If you have .NET Core installed, use the dotnet --info command to determine which SDK you're using.
  • Docker 社区版Docker Community Edition
  • Dockerfile 和 .NET Core 示例应用的临时工作文件夹 。A temporary working folder for the Dockerfile and .NET Core example app. 在本教程中,docker-working 用作工作文件夹的名称。In this tutorial, the name docker-working is used as the working folder.

创建 .Net Core 应用Create .NET Core app

需要有可供 Docker 容器运行的 .NET Core 应用。You need a .NET Core app that the Docker container will run. 打开终端、创建工作文件夹(如果尚没有),然后进入该文件夹。Open your terminal, create a working folder if you haven't already, and enter it. 在工作文件夹中,运行下面的命令,在名为“app”的子目录中新建一个项目 :In the working folder, run the following command to create a new project in a subdirectory named app:

dotnet new console -o App -n NetCore.Docker

文件夹树将如下所示:Your folder tree will look like the following:

docker-working
    └──App
        ├──NetCore.Docker.csproj
        ├──Program.cs
        └──obj
            ├──NetCore.Docker.csproj.nuget.dgspec.json
            ├──NetCore.Docker.csproj.nuget.g.props
            ├──NetCore.Docker.csproj.nuget.g.targets
            ├──project.assets.json
            └──project.nuget.cache

dotnet new 命令会新建一个名为“应用”的文件夹,并生成一个“Hello World”控制台应用程序 。The dotnet new command creates a new folder named App and generates a "Hello World" console application. 从终端会话更改目录并导航到“App”文件夹 。Change directories and navigate into the App folder, from your terminal session. 使用 dotnet run 命令启动应用。Use the dotnet run command to start the app. 应用程序将运行,并在命令下方打印 Hello World!The application will run, and print Hello World! below the command:

dotnet run
Hello World!

默认模板创建应用,此应用先打印输出到终端,然后立即终止。The default template creates an app that prints to the terminal and then immediately terminates. 本教程将使用无限循环的应用。For this tutorial, you'll use an app that loops indefinitely. 在文本编辑器中,打开“Program.cs” 文件。Open the Program.cs file in a text editor.

提示

如果使用 Visual Studio Code,则从上一个终端会话中键入以下命令:If you're using Visual Studio Code, from the previous terminal session type the following command:

code .

这会在 Visual Studio Code 中打开包含该项目的“App”文件夹 。This will open the App folder that contains the project in Visual Studio Code.

Program.cs 应如下面的 C# 代码所示 :The Program.cs should look like the following C# code:

using System;

namespace NetCore.Docker
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

将此文件替换为以下每秒计数一次的代码:Replace the file with the following code that counts numbers every second:

using System;
using System.Threading.Tasks;

namespace NetCore.Docker
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var counter = 0;
            var max = args.Length != 0 ? Convert.ToInt32(args[0]) : -1;
            while (max == -1 || counter < max)
            {
                Console.WriteLine($"Counter: {++counter}");
                await Task.Delay(1000);
            }
        }
    }
}

保存此文件,并使用 dotnet run 再次测试程序。Save the file and test the program again with dotnet run. 请注意,此应用无限期运行。Remember that this app runs indefinitely. 使用取消命令 Ctrl+C 可以停止运行。Use the cancel command Ctrl+C to stop it. 下面是一个示例输出:The following is an example output:

dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C

如果你在命令行中向应用传递一个数字,它就只会计数到这个数字,然后退出。If you pass a number on the command line to the app, it will only count up to that amount and then exit. 试一试用 dotnet run -- 5 计数到 5。Try it with dotnet run -- 5 to count to five.

重要

-- 之后的参数都不传递到 dotnet run 命令,而是传递到你的应用程序。Any parameters after -- are not passed to the dotnet run command and instead are passed to your application.

发布 .Net Core 应用Publish .NET Core app

在将 .NET Core 应用添加到 Docker 映像之前,必须先发布该应用。Before adding the .NET Core app to the Docker image, first it must be published. 最好让容器运行应用的已发布版本。It is best to have the container run the published version of the app. 若要发布应用,请运行以下命令:To publish the app, run the following command:

dotnet publish -c Release

此命令将应用编译到“发布”文件夹中 。This command compiles your app to the publish folder. 从工作文件夹到“发布”文件夹的路径应为 .\App\bin\Release\netcoreapp3.1\publish\The path to the publish folder from the working folder should be .\App\bin\Release\netcoreapp3.1\publish\

在“应用”文件夹中获取“发布”文件夹的目录列表,以验证 NetCore.Docker.dll 文件是否已创建 。From the App folder, get a directory listing of the publish folder to verify that the NetCore.Docker.dll file was created.

dir .\bin\Release\netcoreapp3.1\publish\

    Directory: C:\Users\dapine\App\bin\Release\netcoreapp3.1\publish

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        4/27/2020   8:27 AM            434 NetCore.Docker.deps.json
-a----        4/27/2020   8:27 AM           6144 NetCore.Docker.dll
-a----        4/27/2020   8:27 AM         171520 NetCore.Docker.exe
-a----        4/27/2020   8:27 AM            860 NetCore.Docker.pdb
-a----        4/27/2020   8:27 AM            154 NetCore.Docker.runtimeconfig.json

创建 DockerfileCreate the Dockerfile

docker build 命令使用 Dockerfile 文件来创建容器映像。The Dockerfile file is used by the docker build command to create a container image. 此文件是名为“Dockerfile” 的文本文件,它没有扩展名。This file is a text file named Dockerfile that doesn't have an extension.

在包含 .csproj 的目录中创建名为“Dockerfile”的文件,并在文本编辑器中将其打开 。Create a file named Dockerfile in directory containing the .csproj and open it in a text editor. 本教程将使用 ASP.NET Core 运行时映像(包含 .NET Core 运行时映像),并与 .NET Core 控制台应用程序相对应。This tutorial will use the ASP.NET Core runtime image (which contains the .NET Core runtime image) and corresponds with the .NET Core console application.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1

备注

尽管可能已使用 mcr.microsoft.com/dotnet/core/runtime:3.1 映像,但此处有意使用 ASP.NET Core 运行时映像。The ASP.NET Core runtime image is used intentionally here, although the mcr.microsoft.com/dotnet/core/runtime:3.1 image could have been used.

FROM 关键字需要完全限定的 Docker 容器映像名称。The FROM keyword requires a fully qualified Docker container image name. Microsoft 容器注册表(MCR,mcr.microsoft.com)是 Docker Hub 的联合,可托管可公开访问的容器。The Microsoft Container Registry (MCR, mcr.microsoft.com) is a syndicate of Docker Hub - which hosts publicly accessible containers. dotnet/core 段是容器存储库,其中 aspnet 段是容器映像名称。The dotnet/core segment is the container repository, where as the aspnet segment is the container image name. 该映像使用 3.1 进行标记,它用于版本控制。The image is tagged with 3.1, which is used for versioning. 因此,mcr.microsoft.com/dotnet/core/aspnet:3.1 是 .NET Core 3.1 运行时。Thus, mcr.microsoft.com/dotnet/core/aspnet:3.1 is the .NET Core 3.1 runtime. 请确保拉取的运行时版本与 SDK 面向的运行时一致。Make sure that you pull the runtime version that matches the runtime targeted by your SDK. 例如,在上一节中创建的应用使用的是 .NET Core 3.1 SDK,并且 Dockerfile 中引用的基本映像标记有 3.1 。For example, the app created in the previous section used the .NET Core 3.1 SDK and the base image referred to in the Dockerfile is tagged with 3.1.

保存 Dockerfile 文件 。Save the Dockerfile file. 工作文件夹的目录结果应如下所示。The directory structure of the working folder should look like the following. 为节省本文的空间,省略了一些更深级别的文件和文件夹:Some of the deeper-level files and folders have been omitted to save space in the article:

docker-working
    └──App
        ├──Dockerfile
        ├──NetCore.Docker.csproj
        ├──Program.cs
        ├──bin
        │   └──Release
        │       └──netcoreapp3.1
        │           └──publish
        │               ├──NetCore.Docker.deps.json
        │               ├──NetCore.Docker.exe
        │               ├──NetCore.Docker.dll
        │               ├──NetCore.Docker.pdb
        │               └──NetCore.Docker.runtimeconfig.json
        └──obj
            └──...

在终端中运行以下命令:From your terminal, run the following command:

docker build -t counter-image -f Dockerfile .

Docker 会处理 Dockerfile 中的每一行。Docker will process each line in the Dockerfile. docker build 命令中的 . 指示 Docker 在当前文件夹中查找 Dockerfile 。The . in the docker build command tells Docker to use the current folder to find a Dockerfile. 此命令生成映像,并创建指向相应映像的本地存储库“counter-image” 。This command builds the image and creates a local repository named counter-image that points to that image. 在此命令完成后,运行 docker images 以列出已安装的映像:After this command finishes, run docker images to see a list of images installed:

docker images
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
counter-image                           latest              e6780479db63        4 days ago          190MB
mcr.microsoft.com/dotnet/core/aspnet    3.1                 e6780479db63        4 days ago          190MB

请注意,两个映像共用相同的“IMAGE ID” 值。Notice that the two images share the same IMAGE ID value. 两个映像使用的 ID 值相同是因为,Dockerfile 中的唯一命令是在现有映像的基础之上生成新映像。The value is the same between both images because the only command in the Dockerfile was to base the new image on an existing image. 接下来,在 Dockerfile 中添加三个命令 。Let's add three commands to the Dockerfile. 两个命令都新建映像层,最后一个命令表示 counter-image 存储库条目指向的映像 。Each command creates a new image layer with the final command representing the counter-image repository entry points to.

COPY bin/Release/netcoreapp3.1/publish/ App/
WORKDIR /App
ENTRYPOINT ["dotnet", "NetCore.Docker.dll"]

COPY 命令指示 Docker 将计算机上的指定文件夹复制到容器中的文件夹。The COPY command tells Docker to copy the specified folder on your computer to a folder in the container. 在此示例中,“publish”文件夹被复制到容器中的“App”文件夹 。In this example, the publish folder is copied to a folder named App in the container.

WORKDIR 命令将容器内的当前目录更改为“App” 。The WORKDIR command changes the current directory inside of the container to App.

下一个命令 ENTRYPOINT 指示 Docker 将容器配置为可执行文件运行。The next command, ENTRYPOINT, tells Docker to configure the container to run as an executable. 在容器启动时,ENTRYPOINT 命令运行。When the container starts, the ENTRYPOINT command runs. 当此命令结束时,容器也会自动停止。When this command ends, the container will automatically stop.

在终端中,运行 docker build -t counter-image -f Dockerfile .;在此命令完成后,运行 docker imagesFrom your terminal, run docker build -t counter-image -f Dockerfile . and when that command finishes, run docker images.

docker build -t counter-image -f Dockerfile .
Sending build context to Docker daemon  1.117MB
Step 1/4 : FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
 ---> e6780479db63
Step 2/4 : COPY bin/Release/netcoreapp3.1/publish/ App/
 ---> d1732740eed2
Step 3/4 : WORKDIR /App
 ---> Running in b1701a42f3ff
Removing intermediate container b1701a42f3ff
 ---> 919aab5b95e3
Step 4/4 : ENTRYPOINT ["dotnet", "NetCore.Docker.dll"]
 ---> Running in c12aebd26ced
Removing intermediate container c12aebd26ced
 ---> cd11c3df9b19
Successfully built cd11c3df9b19
Successfully tagged counter-image:latest

docker images
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
counter-image                           latest              cd11c3df9b19        41 seconds ago      190MB
mcr.microsoft.com/dotnet/core/aspnet    3.1                 e6780479db63        4 days ago          190MB

Dockerfile 中的每个命令生成了一个层,并创建了“IMAGE ID” 。Each command in the Dockerfile generated a layer and created an IMAGE ID. 最终“IMAGE ID”是“cd11c3df9b19”(你的 ID 会有所不同),接下来在此映像的基础之上创建容器 。The final IMAGE ID (yours will be different) is cd11c3df9b19 and next you'll create a container based on this image.

创建容器Create a container

至此,已有包含应用的映像,可以创建容器了。Now that you have an image that contains your app, you can create a container. 可以通过两种方式来创建容器。You can create a container in two ways. 首先,新建已停止的容器。First, create a new container that is stopped.

docker create --name core-counter counter-image
0f281cb3af994fba5d962cc7d482828484ea14ead6bfe386a35e5088c0058851

上面的 docker create 命令在 counter-image 映像的基础之上创建容器 。The docker create command from above will create a container based on the counter-image image. 此命令的输出显示已创建容器的“CONTAINER ID” (你的 ID 会有所不同)。The output of that command shows you the CONTAINER ID (yours will be different) of the created container. 若要查看所有 容器的列表,请使用 docker ps -a 命令:To see a list of all containers, use the docker ps -a command:

docker ps -a
CONTAINER ID    IMAGE            COMMAND                   CREATED           STATUS     PORTS    NAMES
0f281cb3af99    counter-image    "dotnet NetCore.Dock…"    40 seconds ago    Created             core-counter

管理容器Manage the container

容器是使用特定名称 core-counter 创建的,此名称用于管理容器。The container was created with a specific name core-counter, this name is used to manage the container. 下面的示例使用 docker start 命令来启动容器,然后使用 docker ps 命令仅显示正在运行的容器:The following example uses the docker start command to start the container, and then uses the docker ps command to only show containers that are running:

docker start core-counter
core-counter

docker ps
CONTAINER ID    IMAGE            COMMAND                   CREATED          STATUS          PORTS    NAMES
2f6424a7ddce    counter-image    "dotnet NetCore.Dock…"    2 minutes ago    Up 11 seconds            core-counter

同样,docker stop 命令会停止容器。Similarly, the docker stop command will stop the container. 下面的示例使用 docker stop 命令来停止容器,然后使用 docker ps 命令来显示未在运行的容器:The following example uses the docker stop command to stop the container, and then uses the docker ps command to show that no containers are running:

docker stop core-counter
core-counter

docker ps
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

连接到容器Connect to a container

在容器运行后,可以连接到它来查看输出。After a container is running, you can connect to it to see the output. 使用 docker startdocker attach 命令,启动容器并查看输出流。Use the docker start and docker attach commands to start the container and peek at the output stream. 在此示例中,Ctrl+C 击键用于从正在运行的容器中分离出来。In this example, the Ctrl+C keystroke is used to detach from the running container. 除非另行指定,否则此击键将结束容器中的进程,这会停止容器。This keystroke will end the process in the container unless otherwise specified, which would stop the container. --sig-proxy=false 参数可确保 Ctrl+C 不会停止容器中的进程。The --sig-proxy=false parameter ensures that Ctrl+C will not stop the process in the container.

从容器中分离出来后重新连接,以验证它是否仍在运行和计数。After you detach from the container, reattach to verify that it's still running and counting.

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

删除容器Delete a container

就本文而言,你不希望存在不执行任何操作的容器。For the purposes of this article you don't want containers just hanging around doing nothing. 删除前面创建的容器。Delete the container you previously created. 如果容器正在运行,停止容器。If the container is running, stop it.

docker stop core-counter

下面的示例列出了所有容器。The following example lists all containers. 然后,它使用 docker rm 命令来删除容器,并再次检查是否有任何正在运行的容器。It then uses the docker rm command to delete the container, and then checks a second time for any running containers.

docker ps -a
CONTAINER ID    IMAGE            COMMAND                   CREATED          STATUS                        PORTS    NAMES
2f6424a7ddce    counter-image    "dotnet NetCore.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

单次运行Single run

Docker 提供了 docker run 命令,用于将容器作为单一命令进行创建和运行。Docker provides the docker run command to create and run the container as a single command. 使用此命令,无需依次运行 docker createdocker startThis command eliminates the need to run docker create and then docker start. 另外,还可以将此命令设置为,在容器停止时自动删除容器。You can also set this command to automatically delete the container when the container stops. 例如,使用 docker run -it --rm 可以执行两项操作,先自动使用当前终端连接到容器,再在容器完成时删除容器:For example, use docker run -it --rm to do two things, first, automatically use the current terminal to connect to the container, and then when the container finishes, remove it:

docker run -it --rm counter-image
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C

容器还会将参数传递给 .NET Core 应用的执行。The container also passes parameters into the execution of the .NET Core app. 指示 .NET Core 应用仅计数为 3 个传入 3 个。To instruct the .NET Core app to count only to 3 pass in 3.

docker run -it --rm counter-image 3
Counter: 1
Counter: 2
Counter: 3

使用 docker run -itCtrl+C 命令会停止在容器中运行的进程,进而停止容器。With docker run -it, the Ctrl+C command will stop process that is running in the container, which in turn, stops the container. 由于提供了 --rm 参数,因此在进程停止时自动删除容器。Since the --rm parameter was provided, the container is automatically deleted when the process is stopped. 验证它是否不存在:Verify that it doesn't exist:

docker ps -a
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

更改 ENTRYPOINTChange the ENTRYPOINT

使用 docker run 命令,还可以修改 Dockerfile 中的 ENTRYPOINT 命令,并运行其他内容,但只能针对相应容器。The docker run command also lets you modify the ENTRYPOINT command from the Dockerfile and run something else, but only for that container. 例如,使用以下命令来运行 bashcmd.exeFor example, use the following command to run bash or cmd.exe. 根据需要,编辑此命令。Edit the command as necessary.

在本例中,ENTRYPOINT 更改为 cmd.exeIn this example, ENTRYPOINT is changed to cmd.exe. 通过按下 Ctrl+C 来结束进程并停止容器。Ctrl+C is pressed to end the process and stop the container.

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

重要命令Essential commands

Docker 包含许多不同的命令,可用于创建、管理以及与容器和映像进行交互。Docker has many different commands that create, manage, and interact with containers and images. 下面这些 Docker 命令对于管理容器来说至关重要:These Docker commands are essential to managing your containers:

清理资源Clean up resources

在本教程中,你创建了容器和映像。During this tutorial, you created containers and images. 如果需要,请删除这些资源。If you want, delete these resources. 以下命令可用于Use the following commands to

  1. 列出所有容器List all containers

    docker ps -a
    
  2. 按名称停止正在运行的容器。Stop containers that are running by their name.

    docker stop counter-image
    
  3. 删除容器Delete the container

    docker rm counter-image
    

接下来,删除计算机上不再需要的任何映像。Next, delete any images that you no longer want on your machine. 依次删除 Dockerfile 创建的映像,以及 Dockerfile 所依据的 .NET Core 映像。Delete the image created by your Dockerfile and then delete the .NET Core image the Dockerfile was based on. 可以使用 IMAGE ID 或 REPOSITORY:TAG 格式字符串。You can use the IMAGE ID or the REPOSITORY:TAG formatted string.

docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/core/aspnet:3.1

使用 docker images 命令来列出已安装的映像。Use the docker images command to see a list of images installed.

提示

映像文件可能很大。Image files can be large. 通常情况下,需要删除在测试和开发应用期间创建的临时容器。Typically, you would remove temporary containers you created while testing and developing your app. 如果计划在相应运行时的基础之上生成其他映像,通常会将基础映像与运行时一同安装。You usually keep the base images with the runtime installed if you plan on building other images based on that runtime.

后续步骤Next steps