教學課程:容器化 .NET Core 應用程式Tutorial: Containerize a .NET Core app

本教學課程將教您如何建置包含 .NET Core 應用程式的 Docker 映像。This tutorial teaches you how to build a Docker image that contains your .NET Core application. 映像可用來為您的本機開發環境、私人雲端,或公用雲端建立容器。The image can be used to create containers for your local development environment, private cloud, or public cloud.

您將了解:You'll learn to:

  • 建立及發佈簡單的 .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 應用程式,請參閱瞭解如何容器化 ASP.NET Core 應用程式教學課程。If you're working with an existing ASP.NET Core application, 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 Community EditionDocker Community Edition

  • Dockerfile 和 .NET Core 範例應用程式的暫存工作資料夾。A temporary working folder for the Dockerfile and .NET Core example app. 在本教學課程中,會使用docker-作用中的名稱做為工作資料夾。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 myapp

您的資料夾樹狀目錄會如下所示:Your folder tree will look like the following:

docker-working
│
└───app
    │   myapp.csproj
    │   Program.cs
    │
    └───obj
            myapp.csproj.nuget.cache
            myapp.csproj.nuget.dgspec.json
            myapp.csproj.nuget.g.props
            myapp.csproj.nuget.g.targets
            project.assets.json

dotnet new 命令會建立名為 app 的新資料夾,並產生 "Hello World" 應用程式。The dotnet new command creates a new folder named app and generates a "Hello World" app. 請進入 app 資料夾,然後執行命令 dotnet runEnter the app folder and run the command dotnet run. 您將會看到下列輸出:You'll see the following output:

> dotnet run
Hello World!

預設範本所建立的應用程式會列印到終端機,然後結束。The default template creates an app that prints to the terminal and then exits. 針對此教學課程,您將使用無限期執行迴圈的應用程式。For this tutorial, you'll use an app that loops indefinitely. 在文字編輯器中開啟 Program.cs 檔案。Open the Program.cs file in a text editor. 它目前看起來應該像下列程式碼:It should currently look like the following code:

using System;

namespace myapp
{
    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;

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

儲存檔案,然後使用 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. 您將會看到下列輸出:You'll see the following 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 試用它以計算到五。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 you add your .NET Core app to the Docker image, publish it. 建議您確定容器啟動時,會執行已發佈的應用程式版本。You want to make sure that the container runs the published version of the app when it's started.

從工作資料夾中,進入含有範例原始程式碼的 app 資料夾,然後執行下列命令:From the working folder, enter the app folder with the example source code and run the following command:

dotnet publish -c Release

此命令會將您的應用程式編譯至 publish 資料夾。This command compiles your app to the publish folder. 從工作資料夾通往 publish 資料夾的路徑應該是 .\app\bin\Release\netcoreapp3.1\publish\The path to the publish folder from the working folder should be .\app\bin\Release\netcoreapp3.1\publish\

應用程式資料夾中,取得 [發行] 資料夾的目錄清單,以確認已建立myapp .dll檔案。From the app folder, get a directory listing of the publish folder to verify that the myapp.dll file was created.

> dir bin\Release\netcoreapp3.1\publish

    Directory:  C:\docker-working\app\bin\Release\netcoreapp3.1\publish

01/09/2020  11:41 AM    <DIR>          .
01/09/2020  11:41 AM    <DIR>          ..
01/09/2020  11:41 AM               407 myapp.deps.json
01/09/2020  12:15 PM             4,608 myapp.dll
01/09/2020  12:15 PM           169,984 myapp.exe
01/09/2020  12:15 PM               736 myapp.pdb
01/09/2020  11:41 AM               154 myapp.runtimeconfig.json

如果您使用的是 Linux 或 macOS,請使用 ls 命令來取得目錄清單,並確認已建立myapp .dll檔案。If you're using Linux or macOS, use the ls command to get a directory listing and verify that the myapp.dll file was created.

me@DESKTOP:/docker-working/app$ ls bin/Release/netcoreapp3.1/publish
myapp.deps.json  myapp.dll  myapp.pdb  myapp.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.

在您的終端機中,瀏覽至上一層目錄來移至您一開始建立的工作資料夾。In your terminal, navigate up a directory to the working folder you created at the start. 在工作資料夾中建立名為 Dockerfile 的檔案,然後在文字編輯器中開啟它。Create a file named Dockerfile in your working folder and open it in a text editor. 視您要容器化的應用程式類型而定,您將選擇 ASP.NET Core 執行時間或 .NET Core 執行時間。Depending on the type of application you're going to containerize, you'll choose either the ASP.NET Core runtime or the .NET Core runtime. 若不確定,請選擇包含 .NET Core 執行時間的 ASP.NET Core 執行時間。When in doubt, choose the ASP.NET Core runtime, which includes the .NET Core runtime. 本教學課程將使用 ASP.NET Core 執行時間映射,但在上一節中建立的應用程式是 .NET Core 應用程式。This tutorial will use the ASP.NET Core runtime image, but the application created in the previous sections is an .NET Core application.

  • ASP.NET Core 執行時間ASP.NET Core runtime

    FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
    
  • .NET Core 執行階段.NET Core runtime

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

FROM 命令會指示 Docker 從指定的存放庫中提取標記為3.1的映射。The FROM command tells Docker to pull down the image tagged 3.1 from the specified repository. 請確定您已提取符合 SDK 目標執行時間的執行階段版本。Make sure that you pull the runtime version that matches the runtime targeted by your SDK. 例如,在上一節中建立的應用程式會使用 .NET Core 3.1 SDK,而Dockerfile中所參考的基底映射會加上3.1For 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 cut to save space in the article:

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

從終端機執行下列命令:From your terminal, run the following command:

docker build -t myimage -f Dockerfile .

Docker 將會處理 Dockerfile 中的每一行。Docker will process each line in the Dockerfile. docker build 命令中的 . 會指示 Docker 使用目前的資料夾來尋找 DockerfileThe . in the docker build command tells Docker to use the current folder to find a Dockerfile. 此命令會建置映像,並建立名為 myimage 的本機存放庫以指向該映像。This command builds the image and creates a local repository named myimage 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
myimage                                 latest              38db0eb8f648        4 weeks ago         346MB
mcr.microsoft.com/dotnet/core/aspnet    3.1                 38db0eb8f648        4 weeks ago         346MB

請注意,這兩個映像會共用相同的映像識別碼值。Notice that the two images share the same IMAGE ID value. 此值在這兩個映像之間是一樣的,因為 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 two commands to the Dockerfile. 每個命令都會建立新的映射層,其中最後一個命令代表myimage存放庫專案所指向的影像。Each command creates a new image layer with the final command representing the image the myimage repository entry points to.

COPY app/bin/Release/netcoreapp3.1/publish/ app/

ENTRYPOINT ["dotnet", "app/myapp.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.

下一個命令 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 myimage -f Dockerfile .,並在該命令完成時,執行 docker imagesFrom your terminal, run docker build -t myimage -f Dockerfile . and when that command finishes, run docker images.

> docker build -t myimage -f Dockerfile .
Sending build context to Docker daemon  1.624MB
Step 1/3 : FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
 ---> 38db0eb8f648
Step 2/3 : COPY app/bin/Release/netcoreapp3.1/publish/ app/
 ---> 37873673e468
Step 3/3 : ENTRYPOINT ["dotnet", "app/myapp.dll"]
 ---> Running in d8deb7b3aa9e
Removing intermediate container d8deb7b3aa9e
 ---> 0d602ca35c1d
Successfully built 0d602ca35c1d
Successfully tagged myimage:latest

> docker images
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
myimage                                 latest              0d602ca35c1d        4 seconds ago       346MB
mcr.microsoft.com/dotnet/core/aspnet    3.1                 38db0eb8f648        4 weeks ago         346MB

Dockerfile 中的每個命令都會產生一個圖層,並建立映像識別碼Each command in the Dockerfile generated a layer and created an IMAGE ID. 最終的映像識別碼 (您的映像識別碼將會不同) 為 ddcc6646461b,接下來您將根據此映像建立容器。The final IMAGE ID (yours will be different) is ddcc6646461b 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 myimage
ceda87b219a4e55e9ad5d833ee1a7ea4da21b5ea7ce5a7d08f3051152e784944

上述的 docker create 命令將根據 myimage 映像建立容器。The docker create command from above will create a container based on the myimage image. 該命令的輸出會顯示已建立容器的容器識別碼 (您的映像識別碼將會不同)。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
ceda87b219a4        myimage             "dotnet app/myapp.dll"   4 seconds ago       Created               gallant_lehmann

管理容器Manage the container

每個容器會被指派隨機的名稱,可供您用來參考該容器執行個體。Each container is assigned a random name that you can use to refer to that container instance. 例如,所建立的容器會自動選擇名稱gallant_lehmann (您的不同),而且該名稱可以用來啟動容器。For example, the container that was created automatically chose the name gallant_lehmann (yours will be different) and that name can be used to start the container. 您會使用 docker create --name 參數,利用特定的名稱來覆寫自動名稱。You override the automatic name with a specific one by using the docker create --name parameter.

下列範例會使用 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 gallant_lehmann
gallant_lehmann

> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS         PORTS   NAMES
ceda87b219a4        myimage             "dotnet app/myapp.dll"   7 minutes ago       Up 8 seconds           gallant_lehmann

同樣地,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 gallant_lehmann
gallant_lehmann

> 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 may actually end the process in the container, which will stop the container. --sig-proxy=false 參數可確保 CTRL + C 不會停止容器中的流程。The --sig-proxy=false parameter ensures that CTRL + C won't stop the process in the container.

當您從容器中斷連結之後,請重新連結以確認它仍在執行且正在進行計算。After you detach from the container, reattach to verify that it's still running and counting.

> docker start gallant_lehmann
gallant_lehmann

> docker attach --sig-proxy=false gallant_lehmann
Counter: 7
Counter: 8
Counter: 9
^C

> docker attach --sig-proxy=false gallant_lehmann
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 gallant_lehmann

下列範例會列出所有容器。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
ceda87b219a4        myimage             "dotnet app/myapp.dll"   19 minutes ago      Exited             gallant_lehmann

> docker rm gallant_lehmann
gallant_lehmann

> 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 myimage
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C

搭配 docker run -it,則 CTRL + 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.

WindowsWindows

在此範例中,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" myimage

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

LinuxLinux

在此範例中,ENTRYPOINT 會變更為 bashIn this example, ENTRYPOINT is changed to bash. 執行 quit 命令以結束程序並停止容器。The quit command is run which ends the process and stop the container.

root@user:~# docker run -it --rm --entrypoint "bash" myimage
root@8515e897c893:/# ls app
myapp.deps.json  myapp.dll  myapp.pdb  myapp.runtimeconfig.json
root@8515e897c893:/# exit
exit

基本命令Essential commands

Docker 有許多不同的命令,其中涵蓋您想要使用容器和映像來執行的動作。Docker has many different commands that cover what you want to do with your container 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. CONTAINER_NAME 代表自動指派給容器的名稱。The CONTAINER_NAME represents the name automatically assigned to the container.

    > docker stop CONTAINER_NAME
    
  3. 刪除容器Delete the container

    > docker rm CONTAINER_NAME
    

接下來,在電腦上刪除您不再需要的任何映像。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. 您可以使用映像識別碼存放庫:標記格式的字串。You can use the IMAGE ID or the REPOSITORY:TAG formatted string.

docker rmi myimage: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