Weather Forecast
June 6, 2023

WeatherForecast. Ads. Docker.

Предисловие

О следующем шаге вы уже догадались. Нужно новый сервис "упаковать" в docker-контейнер.

Поехали

Как всегда, код тут.

Первым делом мы создадим docker-файл (Dockerfile.ads) с инструкцией по сборке контейнера, он не будет сольно отличаться от docker-файла для основного приложения:

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build

# Copy everything
COPY . ./


# Build and publish a release
RUN dotnet publish DeployToProduction.Ads.WebApi --configuration Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:7.0
EXPOSE 80

COPY --from=build /out .

# Start App
ENTRYPOINT ["dotnet", "DeployToProduction.Ads.WebApi.dll"]

Далее у нас несколько вариантов.

Первый - это собрать образ отдельной командой:

docker build -t weather-forecast-ads-img -f Dockerfile.ads .

Второй вариант - обновить файлы docker-compose и пересобрать все образы.

Как вы помните, у нас был один файл docker-compose для конфигурации Development - docker-compose.app.dev.yml:

version: "3.9"

services:
  ads:
    container_name: "weather-forecast-ads"
    image: "weather-forecast-ads-img"
    build:
      context: .
      dockerfile: Dockerfile.ads
    networks:
      - weather-forecast-net
    ports:
      - "5298:80"
    environment:
       ASPNETCORE_ENVIRONMENT: Development
       DOTNET_PRINT_TELEMETRY_MESSAGE: false
  app:
    container_name: "weather-forecast-app"
    image: "weather-forecast-app-img"
    build:
      context: .
      dockerfile: Dockerfile.app
    networks:
      - weather-forecast-net
    ports:
      - "5022:80"
    environment:
       ASPNETCORE_ENVIRONMENT: Development
       DOTNET_PRINT_TELEMETRY_MESSAGE: false
       ConnectionStrings__Postgres: Host=weather-forecast-postgres;User Id=postgres;Password=postgres;Database=postgres
       ConnectionStrings__Redis: weather-forecast-redis:6379
       ConnectionStrings__AdsServerUrl: http://weather-forecast-ads
    depends_on:
      postgres:
        condition: service_started
      redis:
        condition: service_started
      ads:
        condition: service_started

Второй - для конфигурации Production - docker-compose.app.prd.yml:

version: "3.9"

services:
  ads:
    container_name: "weather-forecast-ads"
    image: "weather-forecast-ads-img"
    build:
      context: .
      dockerfile: Dockerfile.ads
    networks:
      - weather-forecast-net
    ports:
      - "5298:80"
    environment:
       ASPNETCORE_ENVIRONMENT: Production
       DOTNET_PRINT_TELEMETRY_MESSAGE: false
  setup:
    container_name: "weather-forecast-setup"
    image: "weather-forecast-setup-img"
    build:
      context: .
      dockerfile: Dockerfile.setup
    networks:
      - weather-forecast-net
    environment:
       ConnectionStrings__Postgres: Host=weather-forecast-postgres;User Id=postgres;Password=postgres;Database=postgres
    depends_on:
      postgres:
        condition: service_started
      redis:
        condition: service_started
  app:
    container_name: "weather-forecast-app"
    image: "weather-forecast-app-img"
    build:
      context: .
      dockerfile: Dockerfile.app
    networks:
      - weather-forecast-net
    ports:
      - "5022:80"
    environment:
       ASPNETCORE_ENVIRONMENT: Production
       DOTNET_PRINT_TELEMETRY_MESSAGE: false
       ConnectionStrings__Postgres: Host=weather-forecast-postgres;User Id=webapp;Password=webapppwd;Database=postgres
       ConnectionStrings__Redis: weather-forecast-redis:6379
       ConnectionStrings__AdsServerUrl: http://weather-forecast-ads
    depends_on:
      setup:
        condition: service_completed_successfully

Обращу ваше внимание на один момент. В Dockerfile.ads мы указываем инструкцией EXPOSE 80, что контейнер из этого образа "выставляет" порт 80, то есть слушает и принимает запросы по этому порту. В docker-compose файле при описании сервиса ads в параметре ports мы указываем какой порт "хоста" будет использоваться для обращения к 80-му порту контейнера. НО в переменной окружения ConnectionStrings__AdsServerUrl для сервиса app мы указываем адрес сервиса http://weather-forecast-ads, где weather-forecast-ads - это имя контейнера, порт мы не указываем, так как используется 80-ый порт по умолчанию. Акцентирую, не правильными вариантами будут: http://localhost:5298, http://weather-forecast-ads:5298.

Собрать образы и запустить все сервисы вы можете одной из команд:

docker compose -f docker-compose.db.yml -f docker-compose.app.dev.yml up --build
docker compose -f docker-compose.db.yml -f docker-compose.app.prd.yml up --build

Проверим что все образы на месте в Docker Desktop:

Протестируем работу нашего приложения:

Освежим в памяти, как загрузить образ в Container Registry, на примере нового образа для ads. Для этого нужно сначала добавить тэг:

docker tag weather-forecast-ads-img cr.selcloud.ru/deploy2production/weather-forecast-ads-img:v1.0

А потом отправить образ в Container Registry:

 docker push cr.selcloud.ru/deploy2production/weather-forecast-ads-img:v1.0

Проверяем список образов в интерфейсе Selectel:

Текущий вариант приложения мы не будем разворачивать в Selectel. Вы можете потренироваться самостоятельно, но для этого не забудьте обновить образ приложения app в Container Registry с новой версией кода. Далее мы добавим в сервис Ads базу данных.

З.Ы.

Традиционное заключение - автоматизированные тесты. Добавляем сборку для интеграционного тестирования сервиса Ads:

Сервис запускается в контейнере с помощью библиотеки Testcontainers:

Я выделил интересный момент - это ожидание, когда сервис запустится и будет готов отвечать на наши запросы. Для этого в сам сервис Ads я добавил контроллер, который называют health check:

Так же мы добавляем в UI-тест приложения проверку блока с рекламой: