Учебник. Контейнеризация приложения .NET

Из этого руководства вы узнаете, как контейнеризировать приложение .NET с помощью Docker. Контейнеры имеют множество функций и преимуществ, таких как неизменяемая инфраструктура, предоставление переносимой архитектуры и обеспечение масштабируемости. Этот образ можно использовать для создания контейнеров в вашей локальной среде разработки, частном или общедоступном облаке.

Изучив это руководство, вы:

  • Создание и публикация простого приложения .NET.
  • Создание и настройка Dockerfile для .NET.
  • Создание образа Docker
  • создать и запустить контейнер Docker.

Вы также узнаете о задачах сборки и развертывания контейнера Docker для приложения .NET. Платформа Docker использует модуль Docker для быстрой сборки и упаковки приложений в качестве образов Docker. Эти образы имеют формат Dockerfile и предназначены для развертывания и запуска в многоуровневом контейнере.

Примечание.

Это руководство не относится к приложениям ASP.NET Core. Если вы используете ASP.NET Core, см. руководство по контейнеризации приложений ASP.NET Core.

Необходимые компоненты

Установите следующие необходимые компоненты:

  • Пакет SDK для .NET 8+
    Если у вас установлена платформа .NET, воспользуйтесь командой dotnet --info, чтобы определить используемую версию пакета SDK.
  • Docker Community Edition.
  • Временная рабочая папка для Dockerfile и примера приложения .NET. В этом руководстве в качестве рабочей папки используется имя docker-working.
  • Пакет SDK для .NET 7+
    Если у вас установлена платформа .NET, воспользуйтесь командой dotnet --info, чтобы определить используемую версию пакета SDK.
  • Docker Community Edition.
  • Временная рабочая папка для Dockerfile и примера приложения .NET. В этом руководстве в качестве рабочей папки используется имя docker-working.

Создание приложения .NET

Вам нужно приложение .NET, которое запускает контейнер Docker. Откройте терминал, создайте рабочую папку, если вы еще этого не сделали, и войдите в нее. В рабочей папке выполните следующую команду, чтобы создать проект в подкаталоге 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 .

Откроется папка App, которая содержит проект в Visual Studio Code.

Файл 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. Путь к папке публикации из рабочей папки должен быть .\App\bin\Release\net8.0\publish\.

Эта команда компилирует приложение и помещает результат в папку publish. Путь к папке публикации из рабочей папки должен быть .\App\bin\Release\net7.0\publish\.

Получите список файлов для папки 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

Файл Dockerfile используется командой docker build для создания образа контейнера. Это текстовый файл с именем Dockerfile, не имеющий расширения.

Создайте файл с именем Dockerfile в каталоге, содержащий CSPROJ , и откройте его в текстовом редакторе. В этом руководстве используется образ среды выполнения 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"]

Примечание.

Образ среды выполнения ASP.NET Core используется намеренно, хотя может использоваться образ mcr.microsoft.com/dotnet/runtime:8.0.

Совет

Этот файл Dockerfile использует многоэтапные сборки, которые оптимизируют окончательный размер образа путем слоев сборки и выхода только необходимых артефактов. Дополнительные сведения см. в документации Docker: многоэтапные сборки.

Ключевое слово FROM требует полного имени образа контейнера Docker. Реестр контейнеров Майкрософт (MCR, mcr.microsoft.com) — это синдикат Docker Hub, на котором размещаются общедоступные контейнеры. Сегмент dotnet — это репозиторий контейнеров, а sdkaspnet сегмент — это имя образа контейнера. Образ помечается 8.0 для управления версиями. Таким образом, mcr.microsoft.com/dotnet/aspnet:8.0 среда выполнения .NET 8.0. Убедитесь, что вызываете версию среды выполнения, которая соответствует версии среды выполнения, с которой работает пакет SDK. Например, приложение, созданное в предыдущем разделе, использовал пакет SDK для .NET 8.0, а базовый образ, упомянутый в 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 и какую версию этой ОС. Полный список всех поддерживаемых тегов можно найти. Страница Docker Hub в NET.

Сохраните файл 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"]

Примечание.

Образ среды выполнения ASP.NET Core используется намеренно, хотя может использоваться образ mcr.microsoft.com/dotnet/runtime:7.0.

Совет

Этот файл Dockerfile использует многоэтапные сборки, которые оптимизируют окончательный размер образа путем слоев сборки и выхода только необходимых артефактов. Дополнительные сведения см. в документации Docker: многоэтапные сборки.

Ключевое слово FROM требует полного имени образа контейнера Docker. Реестр контейнеров Майкрософт (MCR, mcr.microsoft.com) — это синдикат Docker Hub, на котором размещаются общедоступные контейнеры. Сегмент dotnet — это репозиторий контейнеров, а sdkaspnet сегмент — это имя образа контейнера. Образ помечается 7.0 для управления версиями. Таким образом, mcr.microsoft.com/dotnet/aspnet:7.0 это среда выполнения .NET 7.0. Убедитесь, что вызываете версию среды выполнения, которая соответствует версии среды выполнения, с которой работает пакет SDK. Например, приложение, созданное в предыдущем разделе, использовало пакет SDK для .NET 7.0, а базовый образ, упомянутый в 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 скопировать указанную папку на вашем компьютере в папку в контейнере. В этом примере папка публикации копируется в папку с именем 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 6 стандартизует префикс DOTNET_ вместо COMPlus_ для переменных среды, которые настраивают поведение .NET во время выполнения. Но префикс COMPlus_ будет и дальше работать. Если вы используете предыдущую версию среды выполнения .NET, следует и дальше использовать префикс COMPlus_ для переменных среды.

Создание контейнера

Теперь, когда у вас есть образ, содержащий приложение, вы можете создать контейнер. Контейнер можно создать двумя способами. Сначала создайте остановленный контейнер.

docker create --name core-counter counter-image

Эта docker create команда создает контейнер на основе изображения счетчика. В выходных данных этой команды показано, что идентификатор контейнера (ваш будет отличаться) созданного контейнера:

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.

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 также позволяет изменить команду ENTRYPOINT из файла Dockerfile для запуска другой программы, но только для соответствующего контейнера. Например, воспользуйтесь указанной ниже командой, чтобы запустить 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:

Очистка ресурсов

В этом учебнике описано, как создать контейнеры и образы. При желании эти ресурсы можно удалить. Ниже представлены команды, которые позволяют сделать следующее:

  1. Вывод списка всех контейнеров

    docker ps -a
    
  2. Остановить запущенные контейнеры по имени.

    docker stop core-counter
    
  3. Удаление контейнера

    docker rm core-counter
    

Затем удалите все ненужные образы на компьютере. Удалите образ, созданный с помощью файла Dockerfile, а затем удалите образ .NET, на основе которого был создан файл Dockerfile. Вы можете использовать значение IMAGE ID или строку в формате РЕПОЗИТОРИЙ:МЕТКА.

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 просмотрите список установленных образов.

Совет

Файлы образов могут иметь большой размер. Как правило, удаляются временные контейнеры, созданные в ходе тестирования и разработки приложения. При этом рекомендуется оставить базовые образы с установленной средой выполнения, если на ее основе вы планируете создавать другие образы.

Следующие шаги