которая опубликует результирующие бинарные файлы, а также зависимости сервиса \
которая опубликует результирующие бинарные файлы, а также зависимости сервиса
в `<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`:
При этом:
- При запуске установите следующие переменные окружения контейнера в значение `todo`:
- `POSTGRES_DB` - имя базы данных, к которой будет обращаться API-сервис
- `POSTGRES_DB` - имя базы данных, к которой будет обращаться API-сервис
- `POSTGRES_USER` - пользователь БД, через которого будет работать сервис
- `POSTGRES_USER` - пользователь БД, через которого будет работать сервис
- `POSTGRES_PASSWORD` - пароль для пользователя
- `POSTGRES_PASSWORD` - пароль для пользователя
2. Установите значение переменной среды `PGDATA` в значение `/var/lib/postgresql/data` - здесь `PostgreSQL` будет хранить данные.
- Установите значение переменной среды `PGDATA` в значение `/var/lib/postgresql/data` - здесь `PostgreSQL` будет хранить данные.
3. Примонтируйте том данных `todo_pgdata` в качестве директории `/var/lib/postgresql/data`.
- Примонтируйте том данных `todo_pgdata` в качестве директории `/var/lib/postgresql/data`.
4. Примонтируйте директорию `initdb` из `todo_app` в качестве директории `/docker-entrypoint-initdb.d` внутри контейнера. \
- Примонтируйте директорию `initdb` из `todo_app` в качестве директории `/docker-entrypoint-initdb.d` внутри контейнера.
Там находится скрипт `init_db.sql`, который используется для инициализации базы данных при первом старте контейнера.
Там находится скрипт `init_db.sql`, который используется для инициализации базы данных при первом старте контейнера.
Можете выбрать другой `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` - таким образом сервис будет выдавать больше логов.
Несмотря на то что 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` контейнер `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` приложениях.
@ -158,18 +144,16 @@ API-сервиса и `JavaScript` приложения.
- В образе `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`
Пересоберите образ `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`
@ -178,39 +162,73 @@ API-сервиса и `JavaScript` приложения.
- Проверка имела бы до 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, при этом: