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