Weather Forecast
March 18, 2023

WeatherForecast. Docker-изация.

Предисловие

И так, у нас есть проект, подопытный кролик, который мы опубликовали нашими умелыми ручками на виртуальную машину с "настоящими" базами данных Postgres и Redis. В очевидной простоте (скачать исходники на виртуальную машину, собрать, настроить демона) есть и такие же очевидные недостатки:

  • ручная работа (= ошибки), которую можно автоматизировать скриптами. Но главное достоинство хорошего программиста - это лень. Если можно что-то не делать, лучше этого не делать.
  • сложности с обновлением приложения. Вот вы запустили первую версию. Прошло время и у вас готова новая версия приложения, а денег вы ещё не заработали на новые сервера. Вам придется производить работы на "горячем" сервере. Огромная доза адреналина вам обеспечена. Сервер будет обслуживать клиентов и в это же время собирать новую версию. А ещё нужно внести изменения в настройки системы и NGINX. Страшно! Логично остановить работу приложения на время работ. И скорее всего вам придется это сделать, так как новая версия потребует обновление схемы баз данных. А делать миграции баз данных так, чтобы не требовалось останавливать приложение нужно уметь ещё. И это пол беды. А что если в новой версии обнаружились баги?! o_0 Нужно откатываться на предыдущую версию (вы же не удалили ветку в git с предыдущей версией o_0). Опять останов. Куча адреналина!
  • и самое волшебное, что может с вами случиться - проект просто не соберется на сервере. Да, да. Такое может случиться. Например, вы забили закомитить какой-то файл. Или GitHub или какой-то другой репозиторий прилег, или забанен. Или сервер NuGet решил взять выходной. Или...

Тут-то и приходит нам на помощь Docker. Но давайте по порядку. Сначала нам нужно поместить наше приложение в контейнер и научиться работать с ним локально.

Поехали

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

Начнем мы с того, что удалим контейнеры Postgres и Redis, который ранее создавали. Наше приложение будет запускаться в контейнере и подключаться оно будет к базам данных, который тоже находятся в контейнерах. Настройка сети Docker-а, которая используется по-умолчанию, сеть bridge, позволяет адресовать контейнеры только через IP, что не удобно (нужно будет после запуска контейнера узнавать его IP). Подробности тут. По этому мы сначала создадим свою сеть в Docker командой:

docker network create weather-forecast-net

Команды для создания контейнеров баз данных теперь будут выглядеть так (мы добавили параметр --net):

docker run --name weather-forecast-postgres --net weather-forecast-net -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres
docker run --name weather-forecast-redis --net weather-forecast-net -p 6379:6379 -d redis

Для создания образа контейнера нашего приложения нужно создать в проекте файл 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 .

# Set Environment Variables for the App
ENV ASPNETCORE_ENVIRONMENT=Production
ENV DOTNET_PRINT_TELEMETRY_MESSAGE=false
ENV ConnectionStrings__Postgres="Host=weather-forecast-postgres;Username=postgres;Password=postgres;Database=postgres"
ENV ConnectionStrings__Redis="weather-forecast-redis:6379"

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

В чем тут суть.

Сначала мы используем образ "mcr.microsoft.com/dotnet/sdk:7.0" для сборки и публикации проекта: копируем исходники инструкцией COPY, запускаем сборку dotnet инструкцией RUN.

Берем второй образ "mcr.microsoft.com/dotnet/aspnet:7.0", в котором есть только run-time dotnet-а и apt.net, копируем в него результат сборки-публикации, то есть наше приложение, задаем переменные окружения инструкцией ENV, и указываем какую команду должен выполнить контейнер при старте инструкцией ENTRYPOINT.

Обратите внимание, как мы задаем строки подключения, имена серверов баз данных - это имена контейнеров (для этого мы и создавали свою сеть в Docker).

Дополнительно, чтобы в образы не копировались "ненужные" файлы, радом с Dockerfile лежит файл .dockerignore - это список файлов и каталогов, который нужно проигнорировать при сборке образа.

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

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

Новый образ вы увидите в приложении Docker Desktop:

Осталось запустить контейнер из этого образа (образы баз данных должны быть запущены):

docker run --name weather-forecast-app --net weather-forecast-net -p 5022:80 -d weather-forecast-app-img

Проверяем в браузере:

Работает!

Подытожим. Теперь мы можем оперировать Docker-образами как единицами деплоймента. Один раз собрали образ контейнера - теперь он запускается и работает везде одинаково. "Развернуть" приложение теперь - это одна команда запуска Docker-контейнера. Образы хранятся и версионируются в специальных репозиториях.

З.Ы.

А теперь самое вкусненькое =) Помните, ранее для UI теста мы сначала запускали само приложение, а потом запускали тест. Теперь мы можем убрать это неудобство предоставив работу по запуску приложения библиотеке Testcontainers (у нас же теперь есть образ нашего приложения).