Docker Swarm tasks. Fix tasks.md layout.

pull/3/head
Dmitry Ignatiev 1 year ago
parent 9f498642b2
commit 5fa278b8cc

@ -1,6 +1,6 @@
# Задания # Задания
### 1. Многоступенчатая сборка образов ## 1. Многоступенчатая сборка образов
В полученной вами директории `todo_app` находятся исходные коды Enterprise Quality™® системы для менеджмента напоминаний. В полученной вами директории `todo_app` находятся исходные коды Enterprise Quality™® системы для менеджмента напоминаний.
@ -11,11 +11,11 @@
Необходимо будет контейнеризировать эту систему. Необходимо будет контейнеризировать эту систему.
#### 1 Контейнеризация приложения ### 1.1 Контейнеризация приложения
##### 1.1 Подготовка образа для `TodoApi` и `todo_ui` #### 1.1.1 Подготовка образа для `TodoApi` и `todo_ui`
Используя подход к многоступенчатой сборке образов, напишите `Dockerfile`, из которого можно было бы создать образ, \ Используя подход к многоступенчатой сборке образов, напишите `Dockerfile`, из которого можно было бы создать образ,
и который отвечал бы следующим требованиям: и который отвечал бы следующим требованиям:
- Фаза сборки API-сервиса должна использовать образ `mcr.microsoft.com/dotnet/sdk:7.0` - Фаза сборки API-сервиса должна использовать образ `mcr.microsoft.com/dotnet/sdk:7.0`
- Фаза сборки `JavaScript` приложения должна использовать образ `node:21` - Фаза сборки `JavaScript` приложения должна использовать образ `node:21`
@ -28,39 +28,33 @@
Фаза сборки сервиса `TodoApi` состоит из следующих шагов: Фаза сборки сервиса `TodoApi` состоит из следующих шагов:
- Во-первых, для его сборки необходим .NET 7 SDK - Во-первых, для его сборки необходим .NET 7 SDK
- Далее, из директории, в которой находятся исходные коды сервиса(см. `todo_app/TodoApi`), \ - Далее, из директории, в которой находятся исходные коды сервиса(см. `todo_app/TodoApi`),
нужно сделать `dotnet restore TodoApi.csproj` - эта команда скачает зависимости сервиса. нужно сделать `dotnet restore TodoApi.csproj` - эта команда скачает зависимости сервиса.
- Следующим шагом идет непосредственно сборка сервиса. \ - Следующим шагом идет непосредственно сборка сервиса.\
Она производится командой `dotnet build TodoApi.csproj -c Release -o <output_directory>`, \ Она производится командой `dotnet build TodoApi.csproj -c Release -o <output_directory>`,\
в которой вместо `<output_directory>` необходимо подставить имя директории, которое будет \ в которой вместо `<output_directory>` необходимо подставить имя директории, которое будет
содержать бинарные файлы сервиса, например `/app/build` содержать бинарные файлы сервиса, например `/app/build`
- Последняя вещь, которую необходимо сделать - это вызывать команду \ - Последняя вещь, которую необходимо сделать - это вызывать команду\
`dotnet publish TodoApi.csproj -c Release -o <publish_directory>`, \ `dotnet publish TodoApi.csproj -c Release -o <publish_directory>`,\
которая опубликует результирующие бинарные файлы, а также зависимости сервиса \ которая опубликует результирующие бинарные файлы, а также зависимости сервиса
в `<publish_directory>`(например в `/app/publish`). в `<publish_directory>`(например в `/app/publish`).
Для запуска процесса сервиса SDK не нужен, достаточно лишь соответствующего фреймворка \ Для запуска процесса сервиса SDK не нужен, достаточно лишь соответствующего фреймворка `ASP.NET` - в данном случае версии `7.0`. Запуск осуществляется вызовом исполняемого файла `./TodoApi` из директории, которая хранит опубликованные бинарные файлы сервиса и его зависимостей.
`ASP.NET` - в данном случае версии `7.0`. \
Запуск осуществляется вызовом исполняемого файла `./TodoApi` из директории, которая хранит опубликованные \
бинарные файлы сервиса и его зависимостей.
Фаза сборки `JavaScript` приложения состоит из следующих шагов: Фаза сборки `JavaScript` приложения состоит из следующих шагов:
- Прежде всего, необходим `NodeJS` - в данном случае подойдет версия `21`. - Прежде всего, необходим `NodeJS` - в данном случае подойдет версия `21`.
- Далее, из директории, в которой находятся исходные коды приложения(см. `todo_app/todo_ui`), \ - Далее, из директории, в которой находятся исходные коды приложения(см. `todo_app/todo_ui`),
нужно вызывать `npm install` - это команда скачает зависимости приложения. нужно вызывать `npm install` - это команда скачает зависимости приложения.
- После этого, в этой же директории необходимо выполнить команду `npm run build`, в результате чего, \ - После этого, в этой же директории необходимо выполнить команду `npm run build`, в результате чего,
в этой директории появится директория `wwwroot`, содержащая файл `index.html`, \ в этой директории появится директория `wwwroot`, содержащая файл `index.html`,
результирующий код на JS, а также его ресурсы, такие как картинки и файлы стилей(css). результирующий код на JS, а также его ресурсы, такие как картинки и файлы стилей(css).
Для запуска `JavaScript` приложения `NodeJS` не нужен, достаточно лишь любого веб-сервера, и в \ Для запуска `JavaScript` приложения `NodeJS` не нужен, достаточно лишь любого веб-сервера, и в
нашем случае, в API-сервисе такой имеется(он называется `Kestrel`). нашем случае, в API-сервисе такой имеется(он называется `Kestrel`). Для хостинга JS-приложения в API-сервисе, необходимо всего лишь скопировать упомянутую выше директорию `wwwroot` внутрь результирующей директории API-сервиса(которая получается после выполнения `dotnet publish ...`).
Для хостинга JS-приложения в API-сервисе, необходимо всего лишь скопировать упомянутую выше директорию \
`wwwroot` внутрь результирующей директории API-сервиса(которая получается после выполнения `dotnet publish ...`).
Создайте образ из полученного `Dockerfile` и назовите его `todo-bundle`. Создайте образ из полученного `Dockerfile` и назовите его `todo-bundle`.
##### 1.2 Подготовка образа PostgreSQL #### 1.1.2 Подготовка образа PostgreSQL
Перед запуском самого приложения, необходимо запустить и подготовить `PostgreSQL` в сети `todo`. Перед запуском самого приложения, необходимо запустить и подготовить `PostgreSQL` в сети `todo`.
@ -68,83 +62,75 @@
Создайте том данных, в котором `PostgreSQL` будет хранить БД. Назовите его `todo_pgdata`. Создайте том данных, в котором `PostgreSQL` будет хранить БД. Назовите его `todo_pgdata`.
Запустите именованный контейнер `todo_postgres` из образа `postgres:16`, при этом: Запустите именованный контейнер `todo_postgres` из образа `postgres:16`.
1. При запуске установите следующие переменные окружения контейнера в значение `todo`:
- `POSTGRES_DB` - имя базы данных, к которой будет обращаться API-сервис При этом:
- `POSTGRES_USER` - пользователь БД, через которого будет работать сервис - При запуске установите следующие переменные окружения контейнера в значение `todo`:
- `POSTGRES_PASSWORD` - пароль для пользователя - `POSTGRES_DB` - имя базы данных, к которой будет обращаться API-сервис
2. Установите значение переменной среды `PGDATA` в значение `/var/lib/postgresql/data` - здесь `PostgreSQL` будет хранить данные. - `POSTGRES_USER` - пользователь БД, через которого будет работать сервис
3. Примонтируйте том данных `todo_pgdata` в качестве директории `/var/lib/postgresql/data`. - `POSTGRES_PASSWORD` - пароль для пользователя
4. Примонтируйте директорию `initdb` из `todo_app` в качестве директории `/docker-entrypoint-initdb.d` внутри контейнера. \ - Установите значение переменной среды `PGDATA` в значение `/var/lib/postgresql/data` - здесь `PostgreSQL` будет хранить данные.
- Примонтируйте том данных `todo_pgdata` в качестве директории `/var/lib/postgresql/data`.
- Примонтируйте директорию `initdb` из `todo_app` в качестве директории `/docker-entrypoint-initdb.d` внутри контейнера.
Там находится скрипт `init_db.sql`, который используется для инициализации базы данных при первом старте контейнера. Там находится скрипт `init_db.sql`, который используется для инициализации базы данных при первом старте контейнера.
##### 1.3 Проверка работоспособности контейнеризованного приложения #### 1.1.3 Проверка работоспособности контейнеризованного приложения
Запустите контейнер `todo_bundle` в сети `todo` из созданного вами образа `todo-bundle`, при этом: Запустите контейнер `todo_bundle` в сети `todo` из созданного вами образа `todo-bundle`, при этом:
- Установите значение переменной среды `ConnectionStrings__PostgreSQL` в контейнере равной \ - Установите значение переменной среды `ConnectionStrings__PostgreSQL` в контейнере равной
`Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo`. `Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo`.
Можете выбрать другой `Host` в этой строке, если вы назвали контейнер с PostgreSQL по-другому. Можете выбрать другой `Host` в этой строке, если вы назвали контейнер с PostgreSQL по-другому.
Аналогично - и другие параметры. Аналогично - и другие параметры.
- Пробросьте порт 80 на хост-систему(например на порт 8080). \ - Пробросьте порт 80 на хост-систему(например на порт 8080).
API-сервис работает на 80м порту по умолчанию, но это можно изменить, установив значение \ API-сервис работает на 80м порту по умолчанию, но это можно изменить, установив значение переменной \
переменной `ASPNETCORE_URLS` например в `http://*:5000` - тогда внутри контейнера сервис \ `ASPNETCORE_URLS` например в `http://*:5000` - тогда внутри контейнера сервис будет слушать порт 5000.
будет слушать порт 5000.
Убедитесь, что приложение работает и доступно на выбранном вами порту на локальной машине. Убедитесь, что приложение работает и доступно на выбранном вами порту на локальной машине.
При возникновении сложностей и необходимости отладки, вы можете также установить значение переменной \ При возникновении сложностей и необходимости отладки, вы можете также установить значение переменной `ASPNETCORE_ENVIRONMENT` в `Development` - таким образом сервис будет выдавать больше логов.
`ASPNETCORE_ENVIRONMENT` в `Development` - таким образом сервис будет выдавать больше логов.
#### 2 Рефакторинг контейнеризованного приложения ### 1.2 Рефакторинг контейнеризованного приложения
##### 1.1 Декомпозиция образа `todo-bundle` #### 1.2.1 Декомпозиция образа `todo-bundle`
Несмотря на то что API-сервис поддерживает хостинг `JavaScript` приложения, в реальном(или скорее, идеальном) мире \ Несмотря на то что API-сервис поддерживает хостинг `JavaScript` приложения, в реальном(или скорее, идеальном) мире никто так не делает. Над интерфейсом и API часто работают разные команды, у них может быть разный график работы, разное версионирование приложений, и тем более разные репозитарии, и разный подход к разработке.
никто так не делает. Над интерфейсом и API часто работают разные команды, у них может быть разный график работы, \
разное версионирование приложений, и тем более разные репозитарии, и разный подход к разработке.
Кроме того, хотя `Kestrel` - хороший веб-сервер, все же раздача статических файлов - не основная его специализация. Кроме того, хотя `Kestrel` - хороший веб-сервер, все же раздача статических файлов - не основная его специализация.
Поэтому, руководствуясь принципом разделения ответственности, вам необходимо разделить контейнеризацию \ Поэтому, руководствуясь принципом разделения ответственности, вам необходимо разделить контейнеризацию API-сервиса и `JavaScript` приложения.
API-сервиса и `JavaScript` приложения.
Остановите контейнер `todo_bundle`, удалите его и образ `todo-bundle` Остановите контейнер `todo_bundle`, удалите его и образ `todo-bundle`
Модифицируйте `Dockerfile` для API-сервиса, таким образом, чтобы убрать из него все упоминание `JavaScript` приложения \ Модифицируйте `Dockerfile` для API-сервиса, таким образом, чтобы убрать из него все упоминание `JavaScript` приложения и его этапов сборки.
и его этапов сборки.
Создайте образ `todo-api` на основе нового докер-файла. Создайте образ `todo-api` на основе нового докер-файла.
Напишите конфигурацию `Nginx` для использования его как в качестве обратного прокси для API-сервиса, так и для \ Напишите конфигурацию `Nginx` для использования его как в качестве обратного прокси для API-сервиса, так и для раздачи файлов из `wwwroot`, получаемой после сборки `JavaScript` приложения. При этом:
раздачи файлов из `wwwroot`, получаемой после сборки `JavaScript` приложения. \
При этом:
- Учтите, что все методы API-сервера имеют префикс `/api` - Учтите, что все методы API-сервера имеют префикс `/api`
- Не забудьте о том, что приложение на `JS` является `SPA`, и веб-сервер должен перенаправлять все нераспознанные пути \ - Не забудьте о том, что приложение на `JS` является `SPA`, и веб-сервер должен перенаправлять все нераспознанные пути
на `index.html` (используйте директиву `try_files`) на `index.html` (используйте директиву `try_files`)
- Содержимое `wwwroot` должно лежать в `/var/www/todo` - Содержимое `wwwroot` должно лежать в `/var/www/todo`
Напишите `Dockerfile` для `JavaScript` приложения, взяв за основу образ `nginx`. \ Напишите `Dockerfile` для `JavaScript` приложения, взяв за основу образ `nginx`.
При сборке образа копируйте написанную вами конфигурацию Nginx в `/etc/nginx/nginx.conf`, \ При сборке образа копируйте написанную вами конфигурацию Nginx в `/etc/nginx/nginx.conf`,
а результат сборки `JavaScript` приложения в `/var/www/todo`. а результат сборки `JavaScript` приложения в `/var/www/todo`.
Создайте образ `todo-ui` из этого докер-файла. Создайте образ `todo-ui` из этого докер-файла.
##### 1.2 Проверка работоспособности контейнеризованного приложения #### 1.2.2 Проверка работоспособности контейнеризованного приложения
Запустите в сети `todo` контейнер `todo_api` из образа `todo-api`, при этом: Запустите в сети `todo` контейнер `todo_api` из образа `todo-api`, при этом:
- Не забудьте про переменную среды `ConnectionStrings__PostgreSQL`. - Не забудьте про переменную среды `ConnectionStrings__PostgreSQL`.
- Убедитесь что контейнер недоступен из внешней сети. - Убедитесь что контейнер недоступен из внешней сети.
Запустите в сети `todo` контейнер `todo_ui` из образа `todo-ui`, при этом: Запустите в сети `todo` контейнер `todo_ui` из образа `todo-ui`, при этом:
- Пробросьте порт контейнера 80(или любой другой, который вы использовали при \ - Пробросьте порт контейнера 80(или любой другой, который вы использовали при написании конфигурации Nginx) на локальную машину(например, опять же на порт 8080)
написании конфигурации Nginx) на локальную машину(например, опять же на порт 8080)
Убедитесь что приложение доступно извне и работает. Убедитесь что приложение доступно извне и работает.
##### 1.3 Рефакторинг образа `todo-api` #### 1.2.3 Рефакторинг образа `todo-api`
Платформа `.NET`, на самом деле, позволяет собирать приложения, отвязанные от "внешней" предустановленной \ Платформа `.NET`, на самом деле, позволяет собирать приложения, отвязанные от "внешней" предустановленной среды выполнения.
среды выполнения.
Хотя рекомендованный способ докеризации приложений на `.NET` - отталкиваться от образа с `mcr.microsoft.com`, Хотя рекомендованный способ докеризации приложений на `.NET` - отталкиваться от образа с `mcr.microsoft.com`,
иногда возникает необходимость именно в `self-contained` приложениях. иногда возникает необходимость именно в `self-contained` приложениях.
@ -153,64 +139,96 @@ API-сервиса и `JavaScript` приложения.
- Поменяйте образ SDK для сборки на `mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim` - Поменяйте образ SDK для сборки на `mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim`
- Замените команду публикации приложения на \ - Замените команду публикации приложения на \
`dotnet publish TodoApi.csproj --self-contained -r linux-x64 -c Release -o /app/publish` \ `dotnet publish TodoApi.csproj --self-contained -r linux-x64 -c Release -o /app/publish` \
(замените `/app/publish` на директорию которую использовали вы, если она у вас отличается) (замените `/app/publish` на директорию которую использовали вы, если она у вас отличается)
- Поменяйте результирующий исходный образ на `debian:bullseye-slim` - Поменяйте результирующий исходный образ на `debian:bullseye-slim`
- В образе `debian:bullseye-slim` не хватает `ICU`, необходимого для запуска полноценного `.NET` приложения. \ - В образе `debian:bullseye-slim` не хватает `ICU`, необходимого для запуска полноценного `.NET` приложения. \
Установите `libicu` в этом образе при сборке: `apt-get update && apt-get install libicu67` Установите `libicu` в этом образе при сборке: `apt-get update && apt-get install libicu67`
##### 1.4 Проверка работоспособности контейнеризованного приложения #### 1.2.4 Проверка работоспособности контейнеризованного приложения
Пересоберите образ `todo-api`, и перезапустите контейнер `todo_api` на его основе, проверьте что всё работает. Пересоберите образ `todo-api`, и перезапустите контейнер `todo_api` на его основе, проверьте что всё работает.
### 2. Политики перезапуска и базовый мониторинг ## 2. Политики перезапуска и базовый мониторинг
1. Запустите `todo_ui`, `todo_api` и `todo_postgres` так, чтобы они перезапускались при ошибке или же \ 1. Запустите `todo_ui`, `todo_api` и `todo_postgres` так, чтобы они перезапускались при ошибке или же при перезапуске демона Docker.
при перезапуске демона Docker.
Перезапустите демон Docker, или же вообще, сделайте перезагрузку машины, \ Перезапустите демон Docker, или же вообще, сделайте перезагрузку машины, и проверьте что контейнеры запустились при старте Docker.
и проверьте что контейнеры запустились при старте Docker.
2. Перезапустите контейнер `todo_postges`, так чтобы healthcheck: 2. Перезапустите контейнер `todo_postges`, так чтобы healthcheck:
- Запускался командой `pg_isready -U todo -d todo` - Запускался командой `pg_isready -U todo -d todo`
- Имел интервал 10 секунд - Имел интервал 10 секунд
- Имел timeout 5 секунд - Имел timeout 5 секунд
- Проверка имела бы до 5 попыток старта в случае неудачи - Проверка имела бы до 5 попыток старта в случае неудачи
- Начиналась бы после 10 секунд после старта контейнера - Начиналась бы после 10 секунд после старта контейнера
### 3. Ограничение ресурсов ## 3. Ограничение ресурсов
1. Перезапустите контейнеры `todo_ui`, `todo_api` и `todo_postgres`, так они имели ограничение по памяти в \ 1. Перезапустите контейнеры `todo_ui`, `todo_api` и `todo_postgres`, так они имели ограничение по памяти в 300, 500 и 800 мегабайт соответственно. \
300, 500 и 800 мегабайт соответственно. Поэкспериментируйте с ограничениями и посмотрите что выходит, \ Поэкспериментируйте с ограничениями и посмотрите что выходит, в случае их превышения.
в случае их превышения.
2. Перезапустите контейнеры `todo_ui`, `todo_api` и `todo_postgres`, так они имели ограничение по ресурсам процессора в \ 2. Перезапустите контейнеры `todo_ui`, `todo_api` и `todo_postgres`, так они имели ограничение по ресурсам процессора в 1, 1 и 2 соответственно
1, 1 и 2 соответственно
### 4. Docker Compose ## 4. Docker Compose
1. Убедитесь что docker compose установлен, проверьте версию. 1. Убедитесь что docker compose установлен, проверьте версию.
2. Напишите `docker-compose.yml` для контейнера на основе `busybox`, который бы печатал `Hello, World` при запуске. Запустите. 2. Напишите `docker-compose.yml` для контейнера на основе `busybox`, который бы печатал `Hello, World` при запуске. Запустите.
3. Напишите конфигурацию compose для приложения `cats_app`. \ 3. Напишите конфигурацию compose для приложения `cats_app`. Сделайте так, чтобы образ `cats_app` собирался, если его еще не существует. Запустите приложение через docker compose, используя эту конфигурацию. Убедитесь в том, что приложение работает.
Сделайте так, чтобы образ `cats_app` собирался, если его еще не существует. \
Запустите приложение через docker compose, используя эту конфигурацию. \
Убедитесь в том, что приложение работает.
4. Напишите конфигурацию compose для вышеописанной системы todo. При этом: 4. Напишите конфигурацию compose для вышеописанной системы todo. При этом:
- Добавьте туда как healthcheck, так и ограничения ресурсов описанные выше - Добавьте туда как healthcheck, так и ограничения ресурсов описанные выше
- Сделайте так чтобы контейнер `todo_api` зависел от `todo_postgres`, а `todo_ui` - от `todo_api`. \ - Сделайте так чтобы контейнер `todo_api` зависел от `todo_postgres`, а `todo_ui` - от `todo_api`.
Сделайте так, чтобы образы `todo-ui` и `todo-api` собирались, если они еще не существуют. \ - Сделайте так, чтобы образы `todo-ui` и `todo-api` собирались, если они еще не существуют.
Запустите систему через docker compose, и убедитесь что все работает. Запустите систему через docker compose, и убедитесь что все работает.
5. Добавьте контейнер из образа `dpage/pgadmin4:7` в compose файл к вышеописанной системе, так чтобы он подключался к `PostgreSQL`\ 5. Добавьте контейнер из образа `dpage/pgadmin4:7` в compose файл к вышеописанной системе, так чтобы он подключался к `PostgreSQL` и позволял смотреть данные приложения `todo`. При этом установите следующие переменные среды в контейнере в соответствующие значения:
и позволял смотреть данные приложения `todo`. \
При этом установите следующие переменные среды в контейнере в соответствующие значения:
- `PGADMIN_DEFAULT_EMAIL` = `todo@example.com` - `PGADMIN_DEFAULT_EMAIL` = `todo@example.com`
- `PGADMIN_DEFAULT_PASSWORD` = `todo` - `PGADMIN_DEFAULT_PASSWORD` = `todo`
- `PGADMIN_CONFIG_SERVER_MODE` = `False` - `PGADMIN_CONFIG_SERVER_MODE` = `False`
### 5. Docker Swarm ### 5. Docker Swarm
1. Инициализируйте Docker в режим Swarm
2. Выведите на экран список нод Docker Swarm
3. Выведите токен для присоединения к Swarm в качестве worker
4. Создайте сеть Docker типа `overlay` с именем `todo_swarm`
5. Создайте том данных `todo_swarm_pgdata` так, чтобы в него мог писать только 1 сервис одновременно
6. Запустите образ `postgres:16` как сервис Swarm с именем `todo_postgres`, подключенный к сети `todo_swarm`, с количеством реплик равному 1, при этом:
- При запуске установите следующие переменные окружения контейнера в значение `todo`:
- `POSTGRES_DB` - имя базы данных, к которой будет обращаться API-сервис
- `POSTGRES_USER` - пользователь БД, через которого будет работать сервис
- `POSTGRES_PASSWORD` - пароль для пользователя
- Установите значение переменной среды `PGDATA` в значение `/var/lib/postgresql/data` - здесь `PostgreSQL` будет хранить данные.
- Примонтируйте том данных `todo_swarm_pgdata` в качестве директории `/var/lib/postgresql/data`.
- Примонтируйте директорию `initdb` из `todo_app` в качестве директории `/docker-entrypoint-initdb.d` внутри контейнера.\
Там находится скрипт `init_db.sql`, который используется для инициализации базы данных при первом старте контейнера.
7. Запустите образ `todo-api` как сервис Swarm с именем `todo_api`, подключенный к сети `todo_swarm`, с количеством реплик равному 1, при этом:
- Установите переменную окружения `ConnectionStrings__PostgreSQL` \
в значение `Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo`
- Установите переменную окружения `ASPNETCORE_URLS` в `http://*:80`
8. Запустите образ `todo-ui` как сервис Swarm с именем `todo_ui`, подключенный к сети `todo_swarm`, с количеством реплик равному 1, при этом:
- Пробросьте порт 80 на внешний порт 8080
9. Убедитесь, что приложение доступно на порту 8080
10. Увеличьте количество реплик каждого из сервисов `todo_ui` и `todo_api` до 3. Убедитесь что приложение работает.
11. Выведите листинг реплик сервисов `todo_api` и `todo_ui`
12. Просмотрите логи сервиса `todo_api`. Убедитесь что запросы подхватываются разными репликами.
13. Удалите все три сервиса
14. Напишите compose-файл для этих сервисов, который бы также запускал по 3 реплики `todo_ui` и `todo_api` и 1 реплику `todo_postgres`
- Отталкивайтесь от ранее вами написанного файла для docker compose
15. Запустите docker stack, исходя из написанной вами конфигурации, и убедитесь в работоспособности системы

Loading…
Cancel
Save