March 19, 2023

WeatherForecast. Docker Compose.

Предисловие

На предыдущих шагах (dev, Docker-изация) мы запускали контейнеры баз данных руками. Да, это полезно для понимания и тренировки, но не удобно в повседневной работе. Для сборки необходимого окружения одной командой есть Docker Compose - один файл описывает всё необходимое. Попробуем запустить всё наше приложение одни легким движением руки.

Поехали

Исходники к этому посту находятся тут.

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

Добавим в проект файл docker-compose.yml:

version: "3.9"

name: "weather-forecast"

networks:
  weather-forecast-net:

services:
  postgres:
    image: "postgres"
    container_name: "weather-forecast-postgres"
    networks:
      - weather-forecast-net
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: postgres
  redis:
    image: "redis"
    container_name: "weather-forecast-redis"
    networks:
      - weather-forecast-net
    ports:
      - "6379:6379"
  app:
    container_name: "weather-forecast-app"
    image: "weather-forecast-app-img"
    build:
      context: .
      dockerfile: Dockerfile
    networks:
      - weather-forecast-net
    ports:
      - "5022:80"
    environment:
       ASPNETCORE_ENVIRONMENT: Production
       DOTNET_PRINT_TELEMETRY_MESSAGE: false
       ConnectionStrings__Postgres: Host=weather-forecast-postgres;Username=postgres;Password=postgres;Database=postgres
       ConnectionStrings__Redis: weather-forecast-redis:6379
    depends_on:
      postgres:
        condition: service_started
      redis:
        condition: service_started

Пройдемся по этому файлу.

networks - определяем сеть в которой будут работать наши контейнеры.

services - перечисляем нужные нам контейнеры.

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

Аналогично redis.

app - наше приложение, его контейнер будет собираться из Dockerfile-а, также имя, сеть, порты и переменные окружения мы уже выносим сюда, ещё нужно указать зависимости, чтобы наше приложение запускалось после того, как базы данных будут готовы.

Подредактируем Dockerfile, нам уже не нужно указывать в нем переменные окружения:

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

# Copy everything
COPY . ./

# Build and publish a release
RUN dotnet publish DeployToProduction.WeatherForecast.App --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.WeatherForecast.App.dll"]

Дл запуска открываем терминал в каталоге проекта и выполняем команду:

docker compose up

Команда "docker compose up" запускает все контейнеры перечисленные в файле docker-compose.yml и выводит в консоль их логи. Чтобы остановить контейнеры нажмите Ctrl+C. Для запуска в фоновом режиме используйте параметр -d:

docker compose up -d

Удалить все контейнеры можно командой:

docker compose down

Если вы внесли изменения в проект или Dockerfile, то пересобрать образ можно командой

docker compose build

Есть маленький нюанс. Наше приложение при старте выполняет обновление схемы базы данных Postgres. Хоть мы и указали, что контейнер приложения зависит от контейнера postgres, в момент выполнения запуска приложения база данных postgres может быть ещё не готова принимать запросы. Способов решения этой проблемы несколько, один из них - воспользоваться фичей docker compose - healthcheck. Но, так как я не хочу сильно закапывать вас в эти тонкости (при том что первый результат поиска из гугла и SO не сработает), я сделаю более простую и понятную проверку в коде Db.cs:

И так, проверяем:

Работает!

Пару слов в сторону. Мы ручками создавали простейший проект и настраивали работу с docker, чтобы вся эта кухня не казалась магией. Сегодня Visual Studio из коробки поддерживает интегрированную работу с Docker. Вы просто создаете новый проект и ставити галочку "Enable Docker". VS создаст для вас Dockerfile и настроит запуск и отладку приложения в контейнере. Более того, вы просто сможете добавить docker-compose.yml для инфраструктуры приложения.

З.Ы.

Хотелось бы тут добавить "вишенку на торте", как мы можем автоматизировать UI тест и запускать не просто контейнер из образа нашего приложения, а запускать docker compose, чтобы не приходилось руками поднимать контейнеры базы данных. Но, Testcontainers для Java имеет фичу по работе с docker compose (DockerComposeContainer), а вот в .NET её ещё не завезли. Хотя всё, что делает docker compose, вы можете воспроизвести в коде теста: создать сеть, создать контейнеры базы данных.