Docker и C#: контейнеризация .NET приложений продвинутого уровня

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Docker и C#: контейнеризация приложений на продвинутом уровне

Контейнеризация .NET-приложений с помощью Docker стала стандартом индустрии. Если базовые навыки работы с контейнерами уже освоены, настало время перейти на продвинутый уровень. В этой статье мы рассмотрим сложные сценарии: многоэтапную сборку (multi-stage build), создание легковесных production-образов, организацию микросервисов с Docker Compose, внедрение health checks и эффективное управление конфигурацией. Все примеры актуальны для .NET 8 и последних версий Docker.

1. Многоэтапная сборка (Multi-stage build) для .NET

Классическая проблема — огромные образы, включающие SDK и инструменты сборки. Решение — многоэтапная сборка, где финальный образ содержит только рантайм и собранные бинарные файлы.

Рассмотрим продвинутый Dockerfile для ASP.NET Core Web API:

# Этап 1: Восстановление зависимостей
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS restore
WORKDIR /src
COPY ["MyApp.Api/MyApp.Api.csproj", "MyApp.Api/"]
COPY ["MyApp.Core/MyApp.Core.csproj", "MyApp.Core/"]
RUN dotnet restore "MyApp.Api/MyApp.Api.csproj"

# Этап 2: Сборка и публикация
FROM restore AS build
COPY . .
WORKDIR "/src/MyApp.Api"
RUN dotnet publish -c Release -o /app/publish

# Этап 3: Финальный образ (runtime-only)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.Api.dll"]

Ключевые оптимизации:

  • Разделение restore и build: кэширование слоя с зависимостями ускоряет повторные сборки.
  • Target-платформа: используйте --no-restore на этапе publish, если restore уже выполнен.
  • Минимизация слоёв: объединяйте RUN команды, где это возможно, через &&.
  • Использование --chmod: для корректных прав доступа в Linux-контейнерах.

2. Docker Compose для микросервисов на C#

Docker Compose незаменим при работе с несколькими .NET-сервисами. Продвинутый сценарий включает настройку сети, зависимостей и переменных окружения.

Пример docker-compose.yml для микросервисной архитектуры с API Gateway, сервисом заказов и RabbitMQ:

version: '3.8'

services:
  rabbitmq:
    image: rabbitmq:3-management-alpine
    ports:
      - "5672:5672"
      - "15672:15672"
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]
      interval: 30s
      timeout: 10s
      retries: 5

  orders-api:
    build:
      context: .
      dockerfile: src/Orders.Api/Dockerfile
    ports:
      - "5001:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ConnectionStrings__DefaultConnection=Server=db;Database=OrdersDb;User=sa;Password=Your_password123;
      - RabbitMQ__Host=rabbitmq
    depends_on:
      rabbitmq:
        condition: service_healthy
      db:
        condition: service_started
    restart: unless-stopped

  api-gateway:
    build:
      context: .
      dockerfile: src/ApiGateway/Dockerfile
    ports:
      - "80:8080"
    depends_on:
      - orders-api
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - Services__Orders=http://orders-api:8080

Продвинутые техники:

  • Healthcheck в Compose: используйте condition: service_healthy для строгих зависимостей.
  • Профили (profiles): включайте/выключайте сервисы для разных сред разработки.
  • Встроенные volume для логов: volumes: для сохранения логов вне контейнера.
  • Переменные окружения через .env файл: храните чувствительные данные отдельно.

3. Health Checks и Graceful Shutdown

Docker должен знать, что приложение живо и готово принимать трафик. В .NET 8 встроенная поддержка health checks интегрируется с Docker.

Добавьте в Program.cs:

builder.Services.AddHealthChecks()
    .AddDbContextCheck<AppDbContext>()
    .AddRabbitMQ(rabbitConnectionString);

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false // исключаем долгие проверки
});

В Dockerfile настройте liveness и readiness probes:

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl --fail http://localhost:8080/health/live || exit 1

Graceful shutdown — корректная остановка приложения при получении сигнала SIGTERM:

builder.Services.Configure<HostOptions>(opts =>
{
    opts.ShutdownTimeout = TimeSpan.FromSeconds(30);
});

В docker-compose.yml добавьте stop_grace_period: 30s для ожидания завершения обработки запросов.

4. Оптимизация образов и безопасность

Production-образы должны быть не только маленькими, но и безопасными.

Минимизация размера:

  • Используйте dotnet/aspnet:8.0-alpine — образ на 100 МБ вместо 200+ МБ.
  • Удаляйте кэш NuGet: RUN dotnet restore ... && rm -rf ~/.nuget/packages.
  • Применяйте --no-restore и --no-build на этапе publish.

Безопасность:

  • Запускайте приложение от непривилегированного пользователя:
RUN adduser --disabled-password --gecos ""                

Блоги

Рекомендаций по книгам