Вступление

В этой статье мы научились быстро и эффективно создавать с Docker Compose гибкие среды для приложений. Но как масштабировать архитектуру контейнерных приложений, обеспечить высокую доступность и отказоустойчивость без головной боли и чрезмерных затрат на управление? С помощью Docker Swarm.

Swarm  —  один из ведущих инструментов оркестрации и управления кластерами, которым упрощается масштабирование приложений и инфраструктуры. Используя децентрализованные роли и встроенную балансировку нагрузки Swarm, мы обеспечиваем высокую доступность и сверхбыстрое развертывание с минимальными накладными расходами.

Продемонстрируем это.

Что будем создавать?

  1. Среду Docker Swarm с одним управляющим узлом и тремя рабочими узлами.
  2. Стек Docker для развертывания 3-уровневой архитектуры приложений с тремя службами Docker: Web/Apache, Node.js и Postgres.

Понадобятся:

  1. Учетная запись AWS с доступом пользователя IAM.
  2. Учетная запись Docker Hub.
  3. Базовые знания о Docker, Docker Compose и командах CLI.
  4. Знание экземпляров EC2 Amazon и консоли управления AWS.
  5. Знание файловых систем и команд Linux.
  6. Доступ к инструменту командной строки.
  7. IDE, например VS Code.

Ключевые понятия

Узел  —  это физическая или виртуальная машина, на которой размещается приложение и выполняются различные роли: управляющая или рабочая. Управляющим узлом интерпретируются службы и по всем рабочим узлам распределяются задачи. Выданные управляющим узлом задачи выполняются рабочим узлом.

Задача  —  это просто контейнер Docker и набор выполняемых в нем инструкций или команд.


Этап 1. Подготовка

Прежде чем переходить к Docker Swarm, настроим серверы/машины для узлов Swarm. Воспользуемся экземплярами EC2 Amazon, виртуальные и локальные машины тоже сгодятся.

Понадобится:

  1. Один экземпляр EC2 с управляющим узлом.
  2. Три экземпляра EC2 с рабочим узлом.

Группы безопасности

Для взаимодействия всех узлов в Docker Swarm нужен доступ к определенным портам:

Подробнее  —  здесь.

Создадим две группы безопасности: swarm_app_mgr_sg для управляющего узла, swarm_app_wkr_sg для рабочих узлов.

Сначала создаем группы, затем добавляем для них правила входящих подключений. Для каждого правила задаем источник групп безопасности mgr/wkr, добавить его к sg потом в AWS не получится.

Правила входящих подключений для групп безопасности управляющего узла:

  • SSH | TCP | Порт: 22 | Источник: мой IP
  • HTTP | TCP | Порт: 80 | Источник: IPv4 везде
  • HTTPS | TCP | Порт: 443 | Источник: IPv4 везде
  • Пользовательский TCP | TCP | Порт: 8080 | Источник: IPv4 везде
  • Пользовательский TCP | TCP | Порт: 2377 | Источник: swarm_app_wkr_sg
  • Пользовательский TCP | TCP | Порт: 7946 | Источник: swarm_app_wkr_sg
  • Пользовательский UDP | UDP | Порт: 7946 | Источник: swarm_app_wkr_sg
  • Пользовательский UDP | UDP | Порт: 4789 | Источник: swarm_app_wkr_sg

Правила входящих подключений для групп безопасности рабочего узла:

  • SSH | TCP | Порт: 22 | Источник: мой IP
  • HTTP | TCP | Порт: 80 | Источник: IPv4 везде
  • HTTPS | TCP | Порт: 443 | Источник: IPv4 везде
  • Пользовательский TCP | TCP | Порт: 7946 | Источник: swarm_app_mgr_sg
  • Пользовательский UDP | UDP | Порт: 7946 | Источник: swarm_app_mgr_sg
  • Пользовательский UDP | UDP | Порт: 4789 | Источник: swarm_app_mgr_sg

Создаем сервер управляющего узла

Если Docker Swarm  —  это оркестр, то управляющие узлы  —  дирижеры и администраторы. Их задача  —  контролировать/распределять службы и задачи между рабочими узлами. Кроме того, управляющими узлами при необходимости узлы добавляются/удаляются, повышаются/понижаются их роли. Это центр администрирования среды приложения.

Обычно для высокой доступности нужно несколько управляющих узлов, но пока хватит одного.

Создадим шаблон запуска EC2 swarm_node_template и к каждому поднимаемому серверу/узлу применим одинаковые настройки.

Настройки шаблона запуска:

  • AMI: Amazon Linux 2.
  • Type: бесплатный уровень t2.micro, но выбирайте в соответствии с требованиями приложения.
  • Key pair: выбирается/создается пара ключей.
  • Network: сети, задаются при запуске экземпляров.
  • Advanced Details User data: здесь добавляется скрипт bash, по которому при запуске экземпляров устанавливаются, включаются и запускаются Docker и Docker Compose. Скопируйте и вставьте в раздел User details этот скрипт:
sudo yum update -y
sudo yum install -y docker

#устанавливаем docker compose
sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

sudo systemctl enable docker
sudo systemctl start docker

На этом шаблоне запустим экземпляр swarm_app_mgr и подключим группу безопасности swarm_app_mgr_sg. Воспользуемся виртуальным частным облаком по умолчанию и подсетью.

Создаем серверы рабочего узла

Тем же шаблоном запустим на рабочих машинах три экземпляра swarm_app_wkr1, swarm_app_wkr2 и swarm_app_wkr3 с группой безопасности swarm_app_wkr_sg:


Этап 2. Создание Swarm

Создадим среду Swarm и присвоим каждому экземпляру EC2 его роль узла.

Присваиваем роль управляющего узла

Сначала подключимся по SSH к экземпляру управляющего узла и, запустив docker --version и docker-compose --version, проверим установленные версии Docker и Docker Compose:

Авторизуемся со своим именем пользователя в Docker Hub и паролем sudo docker login.

Создадим Swarm и присвоим этому серверу роль управляющего узла:

sudo docker swarm init

Мы получили управляющий узел и направления для создания новых управляющих/рабочих узлов.

IP-адрес  —  это частный IP-адрес экземпляра. Это важно, потому что управляющему узлу для подключения рабочих узлов нужен статический IP-адрес. Скопируйте ту первую команду.

В этом случае в Swarm правильный IP-адрес определился автоматически, но иногда вы направляетесь на «рекламу» IP-адреса. И тогда придется снова запустить команду init с дополнительным параметром --advertise-addr <YOUR_IP_ADDRESS>.

Присваиваем роли рабочих узлов

Скопировав команду, откроем еще три окна терминала и подключимся по SSH к каждому экземпляру рабочих узлов. Вставляем команду, данную от управляющего узла, и получаем подтверждение добавления каждого узла в Swarm:

sudo docker swarm join --token <TOKEN> <MANAGER_IP_ADDRESS>

Чтобы просмотреть все узлы с их статусами, в терминале управляющего узла запускаем sudo docker node ls. Звездочкой * обозначается узел, в котором мы сейчас находимся:

Если запустим эту же команду в терминале рабочего узла, получим сообщение об ошибке:
Error response from daemon: This node is not a swarm manager. Worker nodes can’t be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager. (Реакция на ошибку от демона: «Это не управляющий узел Swarm. Рабочие узлы не используются для просмотра или изменения состояния кластера. Запустите эту команду в управляющем узле или повысьте роль текущего узла до управляющего).

Так задумано: все администрирование и оркестрация выполняются только с управляющих узлов.

Дополнительный бонус: чтобы легче распознавать, что есть что, я поменял имена хостов для каждого узла. Вот как это делается.

Другие терминалы рабочих узлов больше не понадобятся, поэтому закрываем их или сворачиваем.

Теперь узлы Swarm готовы к работе.


Этап 3. Создание стека Swarm

Базовая среда настроена, определим и развернем архитектуру приложения. Приложение настраивается на трех уровнях.

  1. Уровень фронтенда/веба: на веб-серверах Apache, 10 реплик.
  2. Уровень бэкенда/приложения: на Node.js, 4 реплики.
  3. Уровень бэкенда / базы данных: на Postgres, 1 реплика.

У нас нет фактического исходного кода приложения для развертывания, но процесс тот же.

И это декларативный процесс, где нужно только определить ресурсы и конечный результат. Все остальное, в том числе что нужно для достижения этого результата,  —  забота Swarm.

Мы определяем все узлы, службы, образы, тома и параметры развертывания, на Swarm  —  балансировка нагрузки и распределение служб/контейнеров между доступными узлами.

При сбое задачи, например экземпляра службы/контейнера, в Swarm она автоматически завершается и ей на замену создается новая для восполнения общего количества объявленных реплик.

Если случается сбой узла или он становится недоступен, необходимые службы и контейнеры перераспределяются между доступными узлами. Этим объясняется такая высокая доступность и отказоустойчивость Swarm.

Docker Compose

Конечно, службы и задачи создаются отдельно с помощью команд Docker CLI, но это муторно и времязатратно. Лучше создадим с Docker Compose Swarm-стек и оркестрируем всю архитектуру одним файлом и одной командой.

Стек создается из файла docker-compose.yml. Все то же самое, что и в Docker Compose, но в дополнение к службам, портам, томам и сетям с помощью стека определяются параметры развертывания. Мы указываем число реплик для каждой службы, ограничения на размещение, политики перезапуска, политики отката и многое другое. Полный список параметров  —  в официальной документации.

Но закончим с предисловиями, пора за работу.

Так же подключаясь по SSH к управляющему узлу, создадим новый файл docker-compose.yml или клонируем репозиторий Github с файлом compose внутри.

Напомним: это базовая структура файла compose. У нас имеется четыре службы* и две мостовые сети для фронтенда и бэкенда:

version: "3.8"

services:
web:

node:

db:

visualizer:

networks:
frontend:
driver: bridge
backend:
driver: bridge

volumes:
version: "3.8"

services:
web:
image: httpd:2.4.55
ports:
- 80:80
networks:
- frontend
deploy:
replicas: 10
placement:
constraints: [node.role == worker]
restart_policy:
condition: on-failure

node:
image: node:19-alpine3.16
networks:
- frontend
- backend
command: ["sleep", "10000"] # это только для принудительного сохранения контейнера запущенным, ведь фактического кода приложения нет
deploy:
replicas: 4
placement:
constraints: [node.role == worker]
restart_policy:
condition: on-failure

db:
image: postgres:15.2
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=mypass
networks:
- backend
deploy:
replicas: 1
placement:
constraints: [node.role == worker]
restart_policy:
condition: on-failure

visualizer:
image: bretfisher/visualizer
ports:
- 8080:8080
stop_grace_period: 1m30s
networks:
- frontend
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
placement:
constraints: [node.role == manager]

networks:
frontend:
driver: bridge
backend:
driver: bridge

volumes:
db-data:

*Я включил четвертую службу visualizer  —  отличный инструмент разработки, которым визуализируется ландшафт и работоспособность служб всех узлов.

Сосредоточившись только на архитектуре, а не на фактическом коде приложения, я сделал файл Compose довольно простым. Дополняйте его и делайте надежнее для задач своего приложения.

Единственный обязательый параметр развертывания  —  replicas. Мы также объявили для каждой службы ограничения на размещение: они распределяются только по рабочим узлам. Если бы этого объявления не было, задачи распределялись бы и по управляющему узлу.

Файл Compose готов.


Этап 4. Развертывание стека

Развернуть Swarm-стек очень просто.

Это делается в командной оболочке управляющего узла командой docker stack delpoy с указанием с помощью -c файла Compose. Затем добавляется файл Compose и имя стека swarm_app:

sudo docker stack deploy -c docker-compose.yml stack_name
Создаются сети и службы

Вот приложение и развернуто… по крайней мере, для разработки.

Службы в стеке просматриваем этой командой:

sudo docker stack services stack_name
Запущены все реплики

Подробнее все задачи/контейнеры просматриваем этой командой:

sudo docker stack ps stack_name

В выделенной области показана работа Swarm в реальном времени. Похоже, некоторые задачи службы node.js завершились, вероятно, из-за команды sleep, но немедленно заменились запущенной задачей.

Если перейти на любой из общедоступных IP-адресов узла Swarm, мы должны увидеть сайт Apache в реальном времени:

Visualizer

Теперь самое интересное. Перейдем в ту неожиданно появившуюся четвертую службу visualizer, развернутую в управляющем узле с портом 8080 manager_node_public_IP:8080:

Я работаю с пользовательскими интерфейсами, и этот дашборд очень полезен для понимания того, что происходит. С visualizer мы в реальном времени видим контейнеры, распределенные по рабочим узлам.

Смоделируем сбой узла. В консоли управления AWS останавливаем один из серверов рабочего узла и возвращаемся в visualizer:

Поскольку один из узлов стал недоступен, в Swarm задачи перераспределились на другие доступные узлы. При этом количество реплик для каждой службы осталось неизменным.

Поздравляю, мы выполнили оркестрацию и развертывание высокодоступной, отказоустойчивой архитектуры веб-приложения с Docker Swarm.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Aalok Trivedi: Automating & scaling application infrastructure with Docker Swarm and AWS

Предыдущая статьяСоздание лаконичных модульных тестов во Flutter
Следующая статьяКак структурировать API-вызовы при автоматизированном тестировании с Playwright и JavaScript