Встроенная поддержка контейнеров для .NET 7 — контейнеризация приложений .NET без Dockerfile

Встроенная поддержка контейнеров для .NET 7 — контейнеризация приложений .NET без Dockerfile

https://t.me/ai_machinelearning_big_data

Появилась новая встроенная поддержка контейнеров для версий .NET 7 и новее. С совершенно новым SDK-пакетом .NET 7 образы Docker для приложения собираются теперь мгновенно, так же быстро поднимаются для него и контейнеры, а Dockerfile не нужен — одним файлом для сопровождения меньше.

В Microsoft анонсировали, начиная с SDK-пакета .NET 7, поддержку фреймворком создания контейнеризированных приложений в рамках инструментария публикации. Таким образом устраняется необходимость в самом дополнительном Dockerfile. Теперь сократится сопровождаемый разработчиками код Docker, значительно упростится весь рабочий процесс.

Содержание

  • Основная идея
  • Контейнеризация приложений .NET 6 — вкратце
  • Недостатки «Dockerfile»
  • Встроенная поддержка контейнеров для .NET 7
  • Дополнительные настройки для встроенной поддержки Docker
  • Directory.Build.props
  • Для локальной разработки
  • Рабочий процесс «GitHub Actions»
  • Небольшие ограничения

Основная идея

Каков общий рабочий процесс большинства программных приложений, особенно микросервисов?

Код → отправка в репозиторий → запуск конвейера непрерывной интеграции и непрерывного развертывания. Часть этого конвейера — этап сборки Docker, на котором из длинного Dockerfile обычно считываются данные и генерируются образы Docker для приложения. → И, наконец, развертывание образа в службе облачных вычислений.

Теперь не нужно сопровождать Dockerfile: образ генерируется самим фреймворком .NET и отправляется в выбранный репозиторий.

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

Сначала быстро рассмотрим контейнеризацию приложения .NET 6 с Dockerfile.

Создадим два простых приложения: dotnet6 и dotnet7. Поместим их код в папки одного репозитория. А в конце покажем, как это интегрировать с конвейером сборки и отправляемым, например, на DockerHub образом, и прямо в GitHub напишем простой рабочий процесс GitHub Actions.

Устанавливаем оба SDK-пакета и Docker Desktop:

Создаем на GitHub репозиторий, клонируем его на компьютер для локальной разработки, при помощи Visual Code открываем папку репозитория и добавляем здесь папку dotnet6.

Вот исходный код этой реализации.

Контейнеризация приложений .NET 6 — вкратце

В папке dotnet6 создаем простой веб-API проект HelloDocker на .NET 6, запуская команду dotnet интерфейса командной строки:

dotnet new webapi --name HelloDocker --framework net6.0

Здесь указывается на TargetFramework, то есть целевой фреймворк net6.0.

ПРОПУСТИТЕ ЭТУ ЧАСТЬ, ЕСЛИ УЖЕ ХОРОШО ЗНАЕТЕ КОНТЕЙНЕРИЗАЦИЮ ПРИЛОЖЕНИЙ ДО ВЕРСИИ .NET 6 ВКЛЮЧИТЕЛЬНО.

Это очень простой веб-API, которым возвращаются стандартные данные о погоде, подобный любому другому новому веб-API проекту по умолчанию на ASP.NET Core. Чтобы его контейнеризировать, до версии .NET 7 в корневой каталог проекта добавляли Dockerfile.

Совет: создав любое приложение dotnet, сразу очистить файл «launchSettings.json» и удалить все конфигурации, связанные со службами информационного сервера интернета IIS.

{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "profiles": {
    "HelloDocker": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": false,
      "launchUrl": "swagger",
      "applicationUrl": "https://localhost:7290;http://localhost:5033",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Так API всегда запустится на безопасном порте 7290 и HTTP-порте 5033. Внимание: только для запуска приложения на компьютере разработчика, а не в контейнере Docker.

По умолчанию, когда контейнер Docker развертывается с образом .NET, приложение запускается в http://+:80.

Чтобы переопределить его на другой номер порта в контейнере Docker, устанавливаем эту переменную окружения:

  • ENV ASPNETCORE_URLS=http://+:5000.

Добавим новый файл Dockerfile с таким содержимым:

FROM mcr.microsoft.com/dotnet/sdk:6.0 as build-env
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /publish

FROM mcr.microsoft.com/dotnet/aspnet:6.0 as runtime
WORKDIR /publish
COPY --from=build-env /publish .
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000
ENTRYPOINT ["dotnet", "HelloDocker.dll"]

Вот что происходит дальше. Из репозитория образов Microsoft извлекается SDK-пакет dotnet6, файл HelloDocker.csproj копируется в каталог сборки, для восстановления всех зависимостей приложения запускается команда dotnet-restore, остальное содержимое копируется, и в режиме Release запускается команда dotnet publish.

После для выполнения приложения извлекается образ файла этапа выполнения aspnet:6.0, предоставляются порты вроде тех 5033 и 7290, которые мы видели в launchsettings.json. И, наконец, задается точка входа HelloDocker.dll приложения.

ВНИМАНИЕ: ЭТОТ DOCKERFILE АВТОГЕНЕРИРУЕТСЯ И ПРИ СОЗДАНИИ ПРОЕКТА. ПРОСТО ВКЛЮЧАЕМ DOCKER SUPPORT, ВЫБИРАЕМ LINUX, И С ПОМОЩЬЮ VISUAL STUDIO ОН ДОБАВЛЯЕТСЯ В КОРНЕВОЙ КАТАЛОГ НОВОГО ПРОЕКТА. ИНТЕРЕСНО ВАМ ЭТО ИЛИ НЕТ, А ВСЕ-ТАКИ ПРИДЕТСЯ ВНЕСТИ ЗДЕСЬ КОРРЕКТИВЫ ВМЕСТЕ С ДРУГИМИ ИЗМЕНЕНИЯМИ СТРУКТУРЫ ПРОЕКТА.

Теперь создадим образ Docker, выполнив команду из папки, в которой находится файл Dockerfile:

docker build -t hellodockerfrom6 .

Так инициализируется процесс сборки Docker. По завершении образ hellodockerfrom6 отправляется в локальный экземпляр Docker.

В Docker Desktop переходим во вкладку Images («Образы»)

Отсюда из образа hellodockerfrom6 развертывается контейнер Docker, и при необходимости передаются дополнительные настройки, номера портов, переменные окружения.

Сопоставим локальный порт 5000 с портом 5000 контейнера Docker. Так любой трафик/запрос, отправленный на порт localhost 5000, перенаправляется на внутренний порт 5000 контейнера Docker, то есть в приложение hellofromdocker6

Вот и все. Запустив контейнер, переходим в http://localhost:5000/WeatherForecast/, и в ответе видим данные о погоде

Недостатки «Dockerfile»

Сам по себе это простой способ создания образов Docker из приложений .NET. Но по мере увеличения размера и сложности проекта на .NET файл Dockerfile становится запутанным.

Вот, например, Dockerfile одного из моих решений со ссылками на более чем 10 проектов:

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /

# Копируем «csproj» и восстанавливаем как отдельные слои
COPY ["Directory.Build.props", "/"]
COPY ["Directory.Build.targets", "/"]
COPY ["dotnet.ruleset", "/"]
COPY ["stylecop.json", "/"]
COPY ["src/Host/Host.csproj", "src/Host/"]
COPY ["src/Core/Application/Application.csproj", "src/Core/Application/"]
COPY ["src/Core/Domain/Domain.csproj", "src/Core/Domain/"]
COPY ["src/Core/Shared/Shared.csproj", "src/Core/Shared/"]
COPY ["src/Infrastructure/Infrastructure.csproj", "src/Infrastructure/"]
COPY ["src/Migrators/Migrators.MSSQL/Migrators.MSSQL.csproj", "src/Migrators/Migrators.MSSQL/"]
 
RUN dotnet restore "src/Host/Host.csproj" --disable-parallel

# Копируем все остальное и выполняем сборку
COPY . .
WORKDIR "/src/Host"
RUN dotnet publish "Host.csproj" -c Release -o /app/publish

# Собираем образ файла этапа выполнения
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app

COPY --from=build /app/publish .

# Создается непривилегированный пользователь с явно заданным «UID», добавляется разрешение на доступ к папке «/app»
# Подробнее — по адресу https://aka.ms/vscode-docker-dotnet-configure-containers
RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser

ENV ASPNETCORE_URLS=https://+:5050;http://+:5060
EXPOSE 5050
EXPOSE 5060

ENTRYPOINT ["dotnet", "Host.dll"]

Это будет очень неудобно для сопровождения. Особенно в микросервисах, где придется сопровождать один Dockerfile для каждой из служб со ссылками внутри на несколько CSPROJ.

Чтобы упростить эту ситуацию и управление ею, в Microsoft доработали поддержку контейнеров, начиная с .NET 7, где Dockerfile для фактической контейнеризации приложения уже не нужен. Однако подход Dockerfile остается рабочим.

Другая потенциальная проблема связана с контекстом сборки Docker. Предполагается, что все нужное Dockerfile находится в одной с ним папке. Однако при создании приложения с несколькими слоями или срезами, очевидно, что это не так.

Допустим, в корневой папке одного из проектов случайного решения для микросервисов имеется Dockerfile. При сборке образа Docker легко пропустить включение других файлов, которые находятся в корневом каталоге. Это весьма распространенный сценарий для Docker — пропускать очень важные для процесса сборки файлы вроде Directory.Packages.props.

Встроенная поддержка контейнеров для .NET 7

В Microsoft эти конкретные проблемы устранили. С новым обновлением перестали быть проблемой и пробелы в контекстах, большинство из вас с ними наверняка уже сталкивались.

Обратимся к новому подходу.

Создаем в корневом каталоге репозитория папку dotnet7, а также новый веб-API 7.0:

dotnet new webapi --name HelloDocker7 --framework net7.0

Теперь, перейдя в проект, вместо Dockerfile добавляем новый NuGet-пакет:

dotnet add package Microsoft.NET.Build.Containers

По утверждениям Microsoft, это пакет временный: в дальнейших выпусках .NET его включат в SDK-пакет. Это практически ссылка на пакет для создания образа Docker и единственное, что добавляется для получения базового образа.

Получаем доступ к образу Docker локально, запуская в корневом каталоге проекта команду dotnet publish с парой параметров:

dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -p:ContainerImageName=hellodocker7

В этом подходе сохраняются все настройки Dockerfile. Разберем сначала передаваемые здесь аргументы и базовые параметры:

  • –os указывается целевая ОС;
  • –arch указывается целевая архитектура;
  • -p обозначаются конкретные параметры, передаваемые в эту команду publish.

Новый образ контейнера доступен в Docker Desktop

Упростим команду, добавив параметры прямо в файл HelloDocker7.csproj, открываем его в Visual Code и вставляем код:

<PropertyGroup>
  <ContainerImageName>hellodocker7</ContainerImageName>
  <PublishProfile>DefaultContainer</PublishProfile>
  <ContainerImageTags>1.1.0;latest</ContainerImageTags>    
</PropertyGroup>

Задав однажды, уже не нужно каждый раз добавлять это в аргументы терминала.

Теперь команда стала такой:

dotnet publish --os linux --arch x64

Сделаем ее еще проще, добавив это свойство:

<RuntimeIdentifier>linux-x64</RuntimeIdentifier>

Вот что осталось от команды:

dotnet publish

Запустив ее, обнаружим в Docker Desktop новый образ с тегом 1.1.0  — так прямо в файле csproj указываются различные настройки для образа Docker.

Дополнительные настройки для встроенной поддержки Docker

В Dockerfile образ настраивался очень гибко, то же и с новым подходом.

Уже искали во встроенной поддержке Docker на .NET дополнительные настройки? Например, как здесь определяется базовый образ, добавляются переменные окружения, изменяются теги?

Имеются такие:

  • ContainerBaseImage. С этим свойством мы управляем базовым образом для создания приложения dotnet, по умолчанию SDK принимаются значения mcr.microsoft.com/dotnet/aspnet для проектов ASP.NET Core.
  • ContainerImageName. Здесь меняем название образа. Если оно не указано, SDK вернется к названию самой сборки.
  • ContainerPort. Для автоматического сопоставления портов.

А так указываются переменные окружения:

<ItemGroup>
  <ContainerEnvironmentVariable Include="ENABLE_REDIS" Value="Trace" />
</ItemGroup>

Вот подробная документация по другим параметрам/свойствам для настройки контейнера.

Directory.Build.props

Обычно в одном решении имеется несколько проектов на C#. Чтобы каждым из проектов, на которые ссылаются в решении, использовались все эти свойства, в таких сценариях они включаются и в корневой каталог решения Directory.Build.props.

Например, вот фрагмент кода из файла Directory.Build.props шаблонного решения для микросервисов dotnet:

<PropertyGroup>
    <PublishProfile>DefaultContainer</PublishProfile>
    <ContainerImageTags>1.3.0;latest</ContainerImageTags>
</PropertyGroup>

На уровне csproj указывается это:

<PropertyGroup>
  <ContainerImageName>fsh-microservices.catalog</ContainerImageName>    
</PropertyGroup>

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

Для локальной разработки

Это очень хорошо для локальной разработки, где приложения .NET контейнеризируются частенько. Одной dotnet publish достаточно для легкого доступа к образу Docker, и без отражения в Dockerfile многочисленных изменений и добавления новых ссылок на проекты.

Кроме того, жизнь облегчается применением задач Visual Code, похожих на этот фрагмент кода из моего файла .vscode/tasks.json:

{
    "label": "publish:catalog-api",
    "command": "dotnet",
    "type": "process",
    "args": [
        "publish",
        "${workspaceFolder}/src/services/catalog/Host/Host.csproj",
        "--os",
        "linux",
        "--arch",
        "x64",
        "-c",
        "Release",
        "-p:PublishProfile=DefaultContainer",
        "-p:ContainerImageTags=latest",
        "--self-contained"
    ],
    "problemMatcher": "$msCompile"
},

Рабочий процесс «GitHub Actions»

Теперь отправим приложение .NET в репозиторий, для сборки приложения .NET 7 создадим экшен GitHub, опубликуем его со сборкой образа Docker и отправим в общедоступный репозиторий DockerHub.

Во вкладке Actions репозитория GitHub создаем рабочий процесс с новым файлом ci.yml

Чтобы создать приложение .NET 7, сгенерировать для него образ Docker и отправить в DockerHub, напишем очень простой рабочий процесс ci экшена конвейера сборки:

name: ci
on:
  workflow_dispatch: 
jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 7.0.x
    - name: Restore dependencies
      working-directory: ./dotnet7/HelloDocker7/
      run: dotnet restore
    - name: Build
      working-directory: ./dotnet7/HelloDocker7/
      run: dotnet build --no-restore
  docker:
    name:  Docker Build & Push
    runs-on: ubuntu-latest
    needs:
      - build
    steps:
    - name: Checkout
      uses: actions/checkout@v3  
    - name: Docker Login
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    - name: Build Image
      working-directory: ./dotnet7/HelloDocker7/
      run: dotnet publish -c Release -p:ContainerImageName=${{ secrets.DOCKER_USERNAME }}/hellodockerfromdotnet7
    - name: Push Image
      run: docker push ${{ secrets.DOCKER_USERNAME }}/hellodockerfromdotnet7 --all-tags

Рассмотрим этот сценарий подробнее.

  • Строки 2–3: здесь указывается, что этот рабочий процесс запускается только вручную. Имеются варианты автоматического его запуска, например при каждом добавлении в репозиторий. В этом демо пока достаточно ручного запуска.
  • Строки 5–20: на основе .NET 7 в скрипте восстанавливаются зависимости HelloDocker7, выполняется попытка их собрать.
  • Строки 20–37: все, что связано с Docker.
  • Строки 28–32: попытки войти в DockerHub с именем пользователя и паролем, установленными в качестве секретных данных репозитория, скриншот прилагается ниже.
  • Строка 35: запускаем команду dotnet publish с конфигурацией Release, здесь же на основе указанного ContainerImageName генерируется образ Docker.
  • Строка 37: собранный на предыдущем этапе образ отправляется здесь в DockerHub.

Чтобы установить секретные данные, во вкладке настроек Settings репозитория GitHub выбираем Secrets и добавляем имя пользователя и пароль Docker для доступа из конвейера

После сохраняем yml-файл и создаем в DockerHub новый общедоступный репозиторий с таким же названием, как в этом файле —  hellodockerfromdotnet7. Так из скрипта конвейера все гарантированно отправится в этот репозиторий.

Выбираем во вкладке Actions этого нового репозитория Github рабочий процесс ci и нажимаем Run Workflow («Запустить рабочий процесс»)

Теперь конвейер готов к работе, выполняется попытка собрать приложение dotnet

На этапе Docker Build & Push («Сборка и добавление Docker»): новая dotnet publish в действии.

Если обошлось без неожиданностей, увидите везде галочки в зеленых кружочках, то есть образ Docker отправится в DockerHub.

Небольшие ограничения

  • docker-compose для сборки образа нужен Dockerfile. Сейчас это не поддерживается. Решение — образ Docker отправляется на компьютер локальной разработки, затем указывается в файле docker-compose.
  • Пока поддерживаются только образы для Linux x64.
  • Нет встроенной поддержки аутентификации для внешних репозиториев образов.

Проект активно разрабатывается, поэтому в Microsoft все эти ограничения скоро устранят. А кроме них серьезных препятствий для применения этого нового функционала сейчас нет.

Заключение

Мы узнали о совершенно новой встроенной поддержке контейнеров для приложений .NET, начиная с SDK-пакета .NET 7. Рассмотрели стандартный подход Dockerfile для приложений .NET 6, еще применимый для более новых SDK-пакетов .NET, обсудили недостатки Dockerfile, ознакомились с функционалом нового SDK-пакета .NET, где Dockerfile для сборки образов Docker уже не нужен.

Рассмотрели различные доступные настройки и параметры, их применение для целей локальной разработки. Наконец, сделали рабочий процесс GitHub Action для создания приложения, сборки образа Docker и отправки на DockerHub.

Вот код для этого демо на GitHub.


Источник


Report Page