Docker Swarm: создание кластера из трёх нод
Один сервер — это всегда единая точка отказа. Как только приложение перестаёт помещаться в категорию «тестовый стенд» и становится чем-то, что нужно держать в рабочем состоянии постоянно, встаёт вопрос кластеризации. Docker Swarm — встроенный в Docker оркестратор, который не требует отдельной установки и разворачивается заметно проще Kubernetes, оставаясь при этом достаточным для большинства небольших и средних инфраструктур.
Соберём кластер из трёх нод: srv-swarm01 (192.168.10.41), srv-swarm02 (192.168.10.42) и srv-swarm03 (192.168.10.43), все в сети 192.168.10.0/24, домен itpages.lab.local. На всех трёх серверах уже должен быть установлен Docker (см. первую статью серии).
Шаг 1. Подготовка сети между нодами
Прежде чем инициализировать Swarm, убедись, что между нодами открыты нужные порты. Это самая частая причина «кластер не собирается» в изолированных лабораторных или защищённых сетях.
sudo ufw allow 2377/tcp comment 'Swarm management'
sudo ufw allow 7946/tcp comment 'Swarm discovery TCP'
sudo ufw allow 7946/udp comment 'Swarm discovery UDP'
sudo ufw allow 4789/udp comment 'Swarm overlay VXLAN'
Выполни это на всех трёх нодах. Порт 2377 нужен только на менеджерах, но проще открыть его везде, если ноды в будущем могут менять роль.
Шаг 2. Инициализация Swarm на первой ноде
На srv-swarm01 инициализируем кластер, явно указав адрес, по которому эта нода будет доступна остальным:
docker swarm init --advertise-addr 192.168.10.41
Команда выведет готовую строку для присоединения воркеров вида docker swarm join --token SWMTKN-1-... 192.168.10.41:2377. Сохрани её — понадобится на следующем шаге. Если токен потерялся, его всегда можно получить заново:
docker swarm join-token worker
docker swarm join-token manager
Шаг 3. Присоединение остальных нод
На srv-swarm02 и srv-swarm03 выполняем команду join, полученную на предыдущем шаге:
docker swarm join --token SWMTKN-1-xxxxxxxxxxxx 192.168.10.41:2377
Для отказоустойчивости самого управления кластером имеет смысл сделать srv-swarm02 не воркером, а вторым менеджером — тогда при падении srv-swarm01 кластер не потеряет управление. Получаем токен менеджера на srv-swarm01 и используем его на srv-swarm02:
docker swarm join-token manager
# на srv-swarm02:
docker swarm join --token SWMTKN-1-yyyyyyyyyyyy 192.168.10.41:2377
Проверить состояние кластера можно с любого менеджера:
docker node ls
В выводе должны быть все три ноды, у srv-swarm01 и srv-swarm02 в колонке MANAGER STATUS — отметка Leader или Reachable, у srv-swarm03 — пусто (это воркер).
Шаг 4. Развёртывание сервиса на кластере
Разворачиваем простой сервис с несколькими репликами, которые Swarm сам распределит по нодам:
docker service create \
--name web \
--replicas 3 \
--publish published=8080,target=80 \
nginx:1.27-alpine
Смотрим, как реплики распределились по кластеру:
docker service ps web
docker service ls
Благодаря встроенному routing mesh обратиться к сервису можно через порт 8080 на любой из трёх нод — Swarm сам перенаправит запрос на ноду, где реально работает реплика, даже если она физически на другом сервере:
curl http://192.168.10.41:8080
curl http://192.168.10.43:8080
Шаг 5. Развёртывание стека через docker stack deploy
Для многосервисных приложений в Swarm используется тот же синтаксис docker-compose.yml, что и в обычном Compose, но команда развёртывания другая — docker stack deploy. Возьмём знакомый по предыдущей статье стек WordPress и адаптируем под Swarm:
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
volumes:
- db_data:/var/lib/mysql
networks:
- wp_net
secrets:
- db_root_password
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
wordpress:
image: wordpress:6.6-php8.3-apache
environment:
WORDPRESS_DB_HOST: db:3306
ports:
- "8081:80"
networks:
- wp_net
deploy:
replicas: 3
networks:
wp_net:
driver: overlay
volumes:
db_data:
secrets:
db_root_password:
external: true
Обрати внимание на секцию deploy — это Swarm-специфичные настройки, которые обычный docker compose up просто игнорирует, но docker stack deploy учитывает: количество реплик и placement constraints, которые прибивают базу данных к менеджерской ноде, где предсказуемо лежит её volume. Секреты вынесены в отдельный механизм Swarm вместо .env-файла — они передаются нодам в зашифрованном виде.
Создаём секрет и разворачиваем стек:
echo "ChangeMe_root_2026" | docker secret create db_root_password -
docker stack deploy -c docker-compose.yml wp_stack
docker stack services wp_stack
Шаг 6. Обновление сервисов без простоя
Swarm умеет накатывать обновления постепенно, не роняя сервис целиком:
docker service update \
--image nginx:1.27-alpine \
--update-parallelism 1 \
--update-delay 10s \
web
Реплики будут обновляться по одной с паузой в 10 секунд между ними, так что часть трафика всегда обслуживается работающими репликами.
Метки нод и размещение сервисов
Иногда нужно управлять тем, на какой именно ноде окажется сервис, более гибко, чем просто constraint по роли manager/worker. Для этого Swarm поддерживает произвольные метки на нодах. Пометим srv-swarm03 как ноду с SSD-диском для сервисов, которым важна скорость дисковых операций:
docker node update --label-add disk=ssd srv-swarm03
И используем метку в качестве constraint при развёртывании:
docker service create \
--name cache \
--constraint 'node.labels.disk == ssd' \
redis:7-alpine
Посмотреть текущие метки и доступные ресурсы каждой ноды:
docker node inspect srv-swarm03 --format '{{.Spec.Labels}}'
Вывод ноды из кластера на обслуживание
Прежде чем перезагружать один из серверов кластера для планового обслуживания, правильно сначала вывести его в режим drain — Swarm аккуратно переместит все запущенные на нём реплики на другие ноды, не роняя сервис:
docker node update --availability drain srv-swarm03
Проверяем, что реплики действительно переехали:
docker service ps web
После завершения обслуживания возвращаем ноду в работу:
docker node update --availability active srv-swarm03
Такой цикл — drain, обслуживание, active — стоит превратить в привычку для любых плановых работ на нодах кластера, будь то обновление ядра, перезагрузка или замена диска.
Автоматическое масштабирование сервиса
Если нагрузка на сервис колеблется, количество реплик можно менять на лету без пересоздания сервиса:
docker service scale web=5
Или сразу для нескольких сервисов одной командой:
docker service scale web=5 wp_stack_wordpress=4
Встроенного автоскейлинга по метрикам (как в Kubernetes HPA) у Swarm нет из коробки, но простую логику масштабирования по расписанию или по показаниям мониторинга легко собрать скриптом на cron, который вызывает docker service scale на основе данных из Prometheus.
Просмотр логов сервиса целиком по кластеру
Когда реплики сервиса размещены на разных нодах, смотреть логи каждого контейнера по отдельности неудобно. Docker Swarm агрегирует логи всех реплик сервиса одной командой, независимо от того, на какой физической ноде они выполняются:
docker service logs -f web
Это работает даже если ты выполняешь команду с srv-swarm02, а часть реплик физически крутится на srv-swarm01 и srv-swarm03 — менеджер сам агрегирует вывод со всех нод.
Резервное копирование состояния Swarm
Вся конфигурация кластера — список нод, сервисов, секретов, распределённое состояние Raft — хранится менеджерами в директории /var/lib/docker/swarm. Потеря всех менеджеров одновременно означает потерю кластера как единого целого, даже если сами контейнеры на воркерах продолжают физически работать. Регулярный бэкап этой директории с одного из менеджеров — обязательная часть эксплуатации:
docker node ls --filter role=manager
sudo systemctl stop docker
sudo tar czf /srv/backups/swarm-state-$(date +%F).tar.gz -C /var/lib/docker swarm
sudo systemctl start docker
Восстановление на новой ноде выполняется через флаг --force-new-cluster, который поднимает новый Swarm из состояния одного менеджера, если все остальные менеджеры кластера безвозвратно потеряны — это крайняя мера, применяемая только когда кворум менеджеров действительно нельзя восстановить обычным способом.
Откат неудачного обновления сервиса
Если обновление сервиса из шага про update прошло неудачно — например, новая версия образа падает при старте, — Swarm можно откатить к предыдущей рабочей конфигурации одной командой, без необходимости помнить, какой именно образ был раньше:
docker service update --rollback web
Ещё надёжнее — настроить автоматический откат прямо в определении сервиса, чтобы Swarm сам откатывался при обнаружении, что новые реплики не проходят healthcheck:
docker service update \
--update-failure-action rollback \
--update-monitor 30s \
--rollback-monitor 30s \
web
С такой настройкой неудачное обновление откатится автоматически ещё до того, как ты успеешь заметить проблему по алертам мониторинга.
Конфигурации Swarm (configs) для неконфиденциальных данных
Наряду с secrets, для чувствительных данных, Swarm поддерживает механизм configs — для файлов конфигурации, которые не являются секретами, но должны единообразно раздаваться на все реплики сервиса (конфиг nginx, файл настроек приложения):
docker config create nginx_conf /srv/app01/nginx.conf
docker service create \
--name web \
--config source=nginx_conf,target=/etc/nginx/nginx.conf \
nginx:1.27-alpine
В отличие от secrets, содержимое config не шифруется при хранении и его можно посмотреть через docker config inspect — используй configs для обычных файлов настроек и оставляй secrets для паролей, токенов и ключей. Обновление config работает не через редактирование на месте, а через создание новой версии и пересоздание сервиса со ссылкой на неё, что гарантирует атомарность обновления конфигурации сразу на всех репликах.
Частые ошибки и как их избежать
- Единственный менеджер в кластере. Если srv-swarm01 — единственный менеджер и он падает, кластер теряет управление, хотя воркеры продолжают крутить уже запущенные сервисы. Держи нечётное число менеджеров (обычно три), чтобы сохранялся кворум по алгоритму Raft.
- Закрытые порты 7946 и 4789 между нодами. Без них ноды не видят друг друга и overlay-сети не работают, а диагностика сводится к невнятному «нода недоступна». Проверяй сетевую доступность этих портов в первую очередь.
- Хранение стейтфул-данных без привязки к конкретной ноде. Если у сервиса с базой данных нет placement constraint, Swarm может запустить его на любой ноде — а volume с данными физически лежит только на одной. Для таких сервисов либо фиксируй ноду через constraint, либо используй сетевое хранилище, доступное со всех нод.
- Перезагрузка ноды без предварительного drain. Если просто перезагрузить srv-swarm03 с активными репликами, сервис на короткое время потеряет часть мощности, а иногда столкнётся с обрывом соединений в процессе. Всегда переводи ноду в drain перед плановым обслуживанием.
Что дальше
Docker редко используют в одиночку — вот что обычно настраивают рядом: