You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
24 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Задания
## 1. Многоступенчатая сборка образов
В полученной вами директории `todo_app` находятся исходные коды Enterprise Quality™® системы для менеджмента напоминаний.
Она состоит из следующих частей:
- `TodoApi` - API-сервис, на платформе `.NET 7`,
- `todo_ui` - веб-интерфейс к этому сервису, Single-Page `JavaScript` приложение(`SPA`), написанное с использованием `Vue.js 3`,
- `PostgreSQL` - API-сервис использует `PostgreSQL` для хранения данных.
Необходимо будет контейнеризировать эту систему.
В этот раз, к сожалению, программисты не оставили никаких скриптов, но вы можете написать их сами, по ходу работы. Некоторые команды и их аргументы будут довольно длинные.
### 1.1 Контейнеризация приложения
#### 1.1.1 Подготовка образа для `TodoApi` и `todo_ui`
Используя подход к многоступенчатой сборке образов, напишите `Dockerfile`, из которого можно было бы создать образ,
и который отвечал бы следующим требованиям:
- Фаза сборки API-сервиса должна использовать образ `mcr.microsoft.com/dotnet/sdk:7.0`
- Фаза сборки `JavaScript` приложения должна использовать образ `node:21`
- Результирующий образ должен основываться на `mcr.microsoft.com/dotnet/aspnet:7.0`
- Готовое приложение API-сервиса вместе с зависимостями должно находиться в директории `/app`
- `JavaScript` приложение должно находиться в директории `/app/wwwroot`
- При старте контейнера из такого образа, должно запускаться приложение API-сервиса.
Документация по многоступенчатой сборке образов находится тут: https://docs.docker.com/build/building/multi-stage/.
Фаза сборки сервиса `TodoApi` состоит из следующих шагов:
- Во-первых, для его сборки необходим .NET 7 SDK
- Далее, из директории, в которой находятся исходные коды сервиса(см. `todo_app/TodoApi`),
нужно сделать `dotnet restore TodoApi.csproj` - эта команда скачает зависимости сервиса.
- Следующим шагом идет непосредственно сборка сервиса.\
Она производится командой `dotnet build TodoApi.csproj -c Release -o <output_directory>`,\
в которой вместо `<output_directory>` необходимо подставить имя директории, которое будет
содержать бинарные файлы сервиса, например `/app/build`
- Последняя вещь, которую необходимо сделать - это вызывать команду\
`dotnet publish TodoApi.csproj -c Release -o <publish_directory>`,\
которая опубликует результирующие бинарные файлы, а также зависимости сервиса
в `<publish_directory>`(например в `/app/publish`).
Для запуска процесса сервиса SDK не нужен, достаточно лишь соответствующего фреймворка `ASP.NET` - в данном случае версии `7.0`. Запуск осуществляется вызовом исполняемого файла `./TodoApi` из директории, которая хранит опубликованные бинарные файлы сервиса и его зависимостей.
Фаза сборки `JavaScript` приложения состоит из следующих шагов:
- Прежде всего, необходим `NodeJS` - в данном случае подойдет версия `21`.
- Далее, из директории, в которой находятся исходные коды приложения(см. `todo_app/todo_ui`),
нужно вызывать `npm install` - это команда скачает зависимости приложения.
- После этого, в этой же директории необходимо выполнить команду `npm run build`, в результате чего,
в этой директории появится директория `wwwroot`, содержащая файл `index.html`,
результирующий код на JS, а также его ресурсы, такие как картинки и файлы стилей(css). \
Перед выполнением этой команды, также можно установить переменную среды `BASE_PATH`, в значение, \
являющееся префиксом в пути URL, по которому приложение будет находится. По умолчанию, `BASE_PATH` \
трактуется как имеющее значение `/`, таким образом приложение должно раздаваться веб-сервером из корня сайта. \
Если же вы хотите, чтобы приложение находилось например по адресу `http://<домен>/todo/`, тогда необходимо \
установить `BASE_PATH` в значение `/todo/` соответственно. Для установки переменной среды, доступной во время \
сборки образа, используйте директиву `ARG` внутри докер-файла(и соответственно опцию `--build-arg` при вызове `docker build`).
Для запуска `JavaScript` приложения `NodeJS` не нужен, достаточно лишь любого веб-сервера, и в
нашем случае, в API-сервисе такой имеется(он называется `Kestrel`). Для хостинга JS-приложения в API-сервисе, необходимо всего лишь скопировать упомянутую выше директорию `wwwroot` внутрь результирующей директории API-сервиса(которая получается после выполнения `dotnet publish ...`).
Создайте образ из полученного `Dockerfile` и назовите его `todo-bundle`.
#### 1.1.2 Подготовка образа PostgreSQL
Перед запуском самого приложения, необходимо запустить и подготовить `PostgreSQL` в сети `todo`.
Создайте именованную сеть типа `bridge`, в которой будет работать вся система, назовите ее `todo`.
Создайте том данных, в котором `PostgreSQL` будет хранить БД. Назовите его `todo_pgdata`.
Запустите именованный контейнер `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.1.3 Проверка работоспособности контейнеризованного приложения
Запустите контейнер `todo_bundle` в сети `todo` из созданного вами образа `todo-bundle`, при этом:
- Установите значение переменной среды `ConnectionStrings__PostgreSQL` в контейнере равной
`Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo`.
Можете выбрать другой `Host` в этой строке, если вы назвали контейнер с PostgreSQL по-другому.
Аналогично - и другие параметры.
- Пробросьте порт 80 на хост-систему(например на порт 8080).
API-сервис работает на 80м порту по умолчанию, но это можно изменить, установив значение переменной \
`ASPNETCORE_URLS` например в `http://*:5000` - тогда внутри контейнера сервис будет слушать порт 5000.
Убедитесь, что приложение работает и доступно на выбранном вами порту на локальной машине.
При возникновении сложностей и необходимости отладки, вы можете также установить значение переменной `ASPNETCORE_ENVIRONMENT` в `Development` - таким образом сервис будет выдавать больше логов.
### 1.2 Рефакторинг контейнеризованного приложения
#### 1.2.1 Декомпозиция образа `todo-bundle`
Несмотря на то что API-сервис поддерживает хостинг `JavaScript` приложения, в реальном(или скорее, идеальном) мире никто так не делает. Над интерфейсом и API часто работают разные команды, у них может быть разный график работы, разное версионирование приложений, и тем более разные репозитарии, и разный подход к разработке.
Кроме того, хотя `Kestrel` - хороший веб-сервер, все же раздача статических файлов - не основная его специализация.
Поэтому, руководствуясь принципом разделения ответственности, вам необходимо разделить контейнеризацию API-сервиса и `JavaScript` приложения.
Остановите контейнер `todo_bundle`, удалите его и образ `todo-bundle`
Модифицируйте `Dockerfile` для API-сервиса, таким образом, чтобы убрать из него все упоминание `JavaScript` приложения и его этапов сборки.
Создайте образ `todo-api` на основе нового докер-файла.
Напишите конфигурацию `Nginx` для использования его как в качестве обратного прокси для API-сервиса, так и для раздачи файлов из `wwwroot`, получаемой после сборки `JavaScript` приложения. При этом:
- Учтите, что все методы API-сервера имеют префикс `/api`
- Не забудьте о том, что приложение на `JS` является `SPA`, и веб-сервер должен перенаправлять все нераспознанные пути
на `index.html` (используйте директиву `try_files`)
- Содержимое `wwwroot` должно лежать в `/var/www/todo`
Напишите `Dockerfile` для `JavaScript` приложения, взяв за основу образ `nginx`.
При сборке образа копируйте написанную вами конфигурацию Nginx в `/etc/nginx/nginx.conf`,
а результат сборки `JavaScript` приложения в `/var/www/todo`.
Создайте образ `todo-ui` из этого докер-файла.
#### 1.2.2 Проверка работоспособности контейнеризованного приложения
Запустите в сети `todo` контейнер `todo_api` из образа `todo-api`, при этом:
- Не забудьте про переменную среды `ConnectionStrings__PostgreSQL`.
- Убедитесь что контейнер недоступен из внешней сети.
Запустите в сети `todo` контейнер `todo_ui` из образа `todo-ui`, при этом:
- Пробросьте порт контейнера 80(или любой другой, который вы использовали при написании конфигурации Nginx) на локальную машину(например, опять же на порт 8080)
Убедитесь что приложение доступно извне и работает.
#### 1.2.3 Рефакторинг образа `todo-api`
Платформа `.NET`, на самом деле, позволяет собирать приложения, отвязанные от "внешней" предустановленной среды выполнения.
Хотя рекомендованный способ докеризации приложений на `.NET` - отталкиваться от образа с `mcr.microsoft.com`,
иногда возникает необходимость именно в `self-contained` приложениях.
Модифицируйте `Dockerfile` для `todo-api`, следующим образом:
- Поменяйте образ 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` на директорию которую использовали вы, если она у вас отличается)
- Поменяйте результирующий исходный образ на `debian:bullseye-slim`
- В образе `debian:bullseye-slim` не хватает `ICU`, необходимого для запуска полноценного `.NET` приложения. \
Установите `libicu` в этом образе при сборке: `apt-get update && apt-get install libicu67`
#### 1.2.4 Проверка работоспособности контейнеризованного приложения
Пересоберите образ `todo-api`, и перезапустите контейнер `todo_api` на его основе, проверьте что всё работает.
## 2. Политики перезапуска и базовый мониторинг
1. Запустите `todo_ui`, `todo_api` и `todo_postgres` так, чтобы они перезапускались при ошибке или же при перезапуске демона Docker.
Перезапустите демон Docker, или же вообще, сделайте перезагрузку машины, и проверьте что контейнеры запустились при старте Docker.
2. Перезапустите контейнер `todo_postges`, так чтобы healthcheck:
- Запускался командой `pg_isready -U todo -d todo`
- Имел интервал 10 секунд
- Имел timeout 5 секунд
- Проверка имела бы до 5 попыток старта в случае неудачи
- Начиналась бы после 10 секунд после старта контейнера
## 3. Ограничение ресурсов
1. Перезапустите контейнеры `todo_ui`, `todo_api` и `todo_postgres`, так они имели ограничение по памяти в 300, 500 и 800 мегабайт соответственно. \
Поэкспериментируйте с ограничениями и посмотрите что выходит, в случае их превышения.
2. Перезапустите контейнеры `todo_ui`, `todo_api` и `todo_postgres`, так они имели ограничение по ресурсам процессора в 1, 1 и 2 соответственно
## 4. Docker Compose
1. Убедитесь что docker compose установлен, проверьте версию.
2. Напишите `docker-compose.yml` для контейнера на основе `busybox`, который бы печатал `Hello, World` при запуске. Запустите.
3. Напишите конфигурацию compose для приложения `cats_app`. Сделайте так, чтобы образ `cats_app` собирался, если его еще не существует. Запустите приложение через docker compose, используя эту конфигурацию. Убедитесь в том, что приложение работает.
4. Напишите конфигурацию compose для вышеописанной системы todo. При этом:
- Добавьте туда как healthcheck, так и ограничения ресурсов описанные выше
- Сделайте так чтобы контейнер `todo_api` зависел от `todo_postgres`, а `todo_ui` - от `todo_api`.
- Сделайте так, чтобы образы `todo-ui` и `todo-api` собирались, если они еще не существуют.
Запустите систему через docker compose, и убедитесь что все работает.
5. Добавьте контейнер из образа `dpage/pgadmin4:7` в compose файл к вышеописанной системе, так чтобы он подключался к `PostgreSQL` и позволял смотреть данные приложения `todo`. При этом установите следующие переменные среды в контейнере в соответствующие значения:
- `PGADMIN_DEFAULT_EMAIL` = `todo@example.com`
- `PGADMIN_DEFAULT_PASSWORD` = `todo`
- `PGADMIN_CONFIG_SERVER_MODE` = `False`
Убедитесь что консоль администрирования недоступна из интернета, и чтобы проверить ее работоспособность, можно, например, \
прокинуть SSH-туннель от вашей машины, до машины с Docker, например таким образом: \
`ssh -L 5051:localhost:5050 stud@studN.myoffice.ru`. \
где вместо 5050 - подставьте порт, на котором pgadmin доступен изнутри машины `studN.myoffice.ru`. \
После выполнения этой команды, pgadmin будет доступен на вашей машине, на http://localhost:5051
Про SSH туннели подробнее можете почитать здесь: \
https://unix.stackexchange.com/questions/115897/whats-ssh-port-forwarding-and-whats-the-difference-between-ssh-local-and-remot
## 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, исходя из написанной вами конфигурации, и убедитесь в работоспособности системы