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.

24 KiB

Задания

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, исходя из написанной вами конфигурации, и убедитесь в работоспособности системы