Docker Swarm: чем отличается от compose и как не запустить всё дважды

Разбор перехода с docker-compose на Swarm: почему контейнеры оказываются запущены дважды, как распределять сервисы между manager и worker и какие команды для чего.

v1 0 stars 0 forks 0 watchers 1 branch 0 runs Public
mikicreated via APIv1
С чего начинается путаница

Типичный вопрос после перехода на Swarm: «место на диске не освободилось, контейнеры появились на втором сервере, docker-compose теперь не нужен?»

Короткий ответ: всё запущено дважды. Старые контейнеры от docker-compose никуда не делись, а рядом появились сервисы Swarm. Они не видят друг друга и едят ресурсы одновременно.

Список непоследовательный — читай то, что нужно.

Два разных мира
docker compose updocker stack deploy
Что создаётконтейнерысервисы
Где запускаеттолько на этой машинена любом узле кластера
Чем смотретьdocker psdocker service ls
При падениилежит, пока не поднимешьперезапускается сам
Масштабированиевручнуюreplicas: N

Главное: docker ps показывает и контейнеры Swarm, и обычные. Поэтому по одному docker ps нельзя понять, что чем управляется — и именно отсюда ощущение, что «всё как-то странно».

Разобраться

Понять, что управляется Swarm, а что нет

Сначала смотрим сервисы кластера:

bash
1docker stack ls # список стеков
2docker service ls # сервисы и их реплики
3docker node ls # узлы кластера

Потом — все контейнеры на машине:

bash
1docker ps

У контейнеров Swarm имена вида стек_сервис.1.длинныйидентификатор. У обычных — простые, из compose-файла. Это самый быстрый способ их различить.

Why: Пока не разделишь эти два мира в голове, любая отладка превращается в гадание: останавливаешь контейнер, а он поднимается снова — потому что это сервис Swarm, и кластер честно восстанавливает заданное число реплик.
$docker service ls
Check
  • Ты можешь назвать, какие контейнеры на машине от Swarm, а какие нет
  • Понимаешь, почему остановленный контейнер Swarm поднимается сам
Остановить дублирующие контейнеры

Когда сервис уже живёт в Swarm, старый контейнер надо гасить тем же инструментом, которым поднимали:

bash
1# если поднимали через compose
2docker compose -f путь/к/docker-compose.yml down
3
4# если вручную
5docker stop имя && docker rm имя

Не пытайся гасить через docker stop контейнер Swarm-сервиса — кластер тут же создаст новый. Нужно docker service scale имя=0 или docker stack rm.

Why: Два комплекта одних и тех же сервисов — это не только двойной расход памяти. Это ещё и два приложения, пишущих в одну базу, и два планировщика, выполняющих одни и те же фоновые задачи.
$docker ps --format "{{.Names}}"
Check
  • Каждый сервис запущен ровно один раз
  • Старые compose-контейнеры остановлены тем же инструментом, которым поднимались
Распределение между manager и worker

В кластере есть узлы двух ролей. Manager держит состояние кластера и принимает решения, worker — просто запускает задачи.

Рабочее правило распределения:

ЧтоКудаПочему
Инфраструктура кластера, панель управления, проксиManagerбез них кластером не поуправляешь
Базы данныхManagerстабильность важнее гибкости, данные привязаны к тому узла
Кэш (Redis и подобные)Workerлёгкие, перезапуск безболезненен
БэкендыWorkerтяжёлые по CPU, их много
Фронтенды, статикаWorkerлёгкие, легко масштабируются

Закрепляется в compose через ограничения размещения:

yaml
1services:
2 api:
3 deploy:
4 replicas: 2
5 placement:
6 constraints:
7 - node.role == worker
8
9 postgres:
10 deploy:
11 replicas: 1
12 placement:
13 constraints:
14 - node.role == manager

Критично для баз данных: если сервис с томом переедет на другой узел, том за ним не поедет — он привязан к машине. Получишь пустую базу и очень неприятный вечер.

Работа со Swarm

Развернуть стек
bash
1docker stack deploy -c docker-compose.yml имя-стека

Тот же файл, что и для compose, но часть полей работает иначе:

  • раздел deploy читается только в Swarm, compose его игнорирует;
  • build в Swarm не работает — образ должен быть уже собран и доступен всем узлам;
  • depends_on игнорируется — порядок запуска не гарантируется.
Why: Последние два пункта ловят почти всех. Локально собранный образ есть только на одном узле — на остальных сервис просто не запустится, и ошибка будет про отсутствующий образ, а не про сборку.
$docker stack deploy -c docker-compose.yml myapp
Check
  • Стек виден в docker stack ls
  • Все сервисы в состоянии N/N
Понять, почему сервис не поднимается

Если docker service ls показывает 0/1, смотри историю задач:

bash
1docker service ps имя-сервиса --no-trunc

Колонка CURRENT STATE и ERROR обычно говорят всё. Типичные состояния:

  • Rejected — узел отказался: нет образа, не сошлись ограничения размещения, нет ресурсов
  • Failed — контейнер запустился и упал с ненулевым кодом
  • Complete — контейнер вышел с кодом 0. При restart_policy: on-failure кластер его НЕ поднимет, и сервис так и останется 0/1

Логи сервиса целиком: docker service logs имя --tail 50

Why: Состояние `Complete` сбивает с толку сильнее всего: выглядит как успех, а сервис лежит. Зная эти три слова, ты сэкономишь часы.
$docker service ps имя-сервиса --no-trunc
Check
  • Ты знаешь, чем отличаются Rejected, Failed и Complete
  • Умеешь смотреть логи сервиса, а не контейнера
Шпаргалка по командам
bash
1# обзор
2docker node ls # узлы кластера и их роли
3docker stack ls # стеки
4docker service ls # сервисы и реплики
5
6# конкретный сервис
7docker service ps имя --no-trunc # где запущен и что с ним было
8docker service logs имя --tail 50 # логи
9docker service inspect имя # полная конфигурация
10
11# изменения
12docker service scale имя=3 # число реплик
13docker service update --force имя # перезапустить задачи
14docker stack deploy -c file.yml стек # развернуть или обновить
15docker stack rm стек # снести стек целиком

Заметь: docker stack deploy с тем же именем стека не создаёт второй стек, а обновляет существующий. Это и есть штатный способ выкатки.

О безопасности кластера

Чтобы узел вошёл в кластер, нужен join-токен. Их два вида, и разница между ними огромная:

bash
1docker swarm join-token worker # может только выполнять задачи
2docker swarm join-token manager # может УПРАВЛЯТЬ кластером

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

Отсюда три правила:

  1. Токены не коммитятся. Ни в скрипты автоматизации, ни в документацию, ни «временно, чтобы не забыть».
  2. Если токен где-то засветился — ротируй: docker swarm join-token --rotate manager. Старый сразу перестаёт работать, уже вошедшие узлы не вылетают.
  3. Новые узлы — всегда worker, если нет явной причины делать их manager.

Проверь прямо сейчас: grep -r "SWMTKN" . по своим репозиториям и скриптам.

Остановил контейнер через docker stop, а он снова запустился. Почему?
Почему базу данных закрепляют за конкретным узлом?
Сервис показывает 0/1, а в docker service ps состояние Complete. Что это значит?