Docker Swarm vs Kubernetes: как мы выбирали оркестратор для логистической платформы

Ситуация: логистика ищет оркестратор

Логистическая компания «ТрансКарго» обратилась к нам в itfresh.ru с задачей: контейнеризировать 8 сервисов (трекинг грузов, маршрутизация, биллинг, уведомления, личный кабинет, API для партнёров, аналитика, внутренний CRM) и развернуть оркестрацию.

Условия:

  • Команда — 2 backend-разработчика и 1 сисадмин. Нет DevOps-инженера. Нет опыта с Kubernetes.
  • Инфраструктура — 5 bare-metal серверов в собственной серверной. Облако не рассматривалось из-за требований безопасности (персональные данные грузоотправителей).
  • Нагрузка — 500 RPS в среднем, 2000 RPS в пиках (отчётные периоды). 10 000 активных пользователей.
  • Бюджет — ограничен. Managed Kubernetes не доступен.
  • Сроки — 3 недели на запуск в продакшен.

Вопрос: Docker Swarm или Kubernetes? Мы провели полное сравнение и начали с Swarm.

Архитектурное сравнение

Docker Swarm встроен в Docker Engine. Kubernetes — отдельная платформа с десятками компонентов.

КомпонентDocker SwarmKubernetes
Control planeВстроен в Docker Engine (manager nodes)etcd + API server + scheduler + controller manager
Рабочие узлыWorker nodes (Docker Engine)kubelet + kube-proxy + container runtime
Единица деплояService (группа контейнеров)Pod (один или несколько контейнеров)
Конфигурацияdocker-compose.yml (Compose v3)YAML-манифесты (Deployment, Service, etc.)
Service discoveryВстроенный DNSCoreDNS
Load balancingIngress routing meshIngress Controller (nginx, traefik)
Установка1 команда: docker swarm initkubeadm, k3s, RKE2 (десятки шагов)
Кривая обучения1-2 дня2-4 недели

Docker Swarm — это надстройка над знакомым Docker Compose. Если команда умеет писать docker-compose.yml, они уже почти знают Swarm. Kubernetes требует изучения принципиально новых абстракций: Pod, Deployment, StatefulSet, DaemonSet, PV/PVC, Ingress, RBAC, ServiceAccount.

Docker Swarm: от нуля до продакшена за день

Мы начали с Docker Swarm, чтобы быстро получить работающую оркестрацию:

# Инициализация Swarm на первом сервере (manager)
ssh srv1.transcargo.local
docker swarm init --advertise-addr 10.0.1.10
# Swarm initialized: current node is now a manager.
# To add a worker, run:
#   docker swarm join --token SWMTKN-1-xxx 10.0.1.10:2377
# To add a manager, run:
#   docker swarm join-token manager

# Добавляем ещё 2 manager-а для HA (нечётное число для кворума)
ssh srv2.transcargo.local
docker swarm join --token SWMTKN-1-xxx-manager-token 10.0.1.10:2377

ssh srv3.transcargo.local
docker swarm join --token SWMTKN-1-xxx-manager-token 10.0.1.10:2377

# Добавляем 2 worker-а
ssh srv4.transcargo.local
docker swarm join --token SWMTKN-1-xxx-worker-token 10.0.1.10:2377

ssh srv5.transcargo.local
docker swarm join --token SWMTKN-1-xxx-worker-token 10.0.1.10:2377

# Проверяем кластер
docker node ls
# ID              HOSTNAME    STATUS   AVAILABILITY   MANAGER STATUS
# abc123 *        srv1        Ready    Active         Leader
# def456          srv2        Ready    Active         Reachable
# ghi789          srv3        Ready    Active         Reachable
# jkl012          srv4        Ready    Active
# mno345          srv5        Ready    Active

Деплой сервисов через Docker Stack (расширенный Compose):

# docker-stack.yml — все сервисы ТрансКарго
version: "3.8"

services:
  tracking:
    image: registry.transcargo.local/tracking:2.3.1
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 30s
        failure_action: rollback
        monitor: 60s
        order: start-first     # Новый контейнер стартует до остановки старого
      rollback_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M
      placement:
        constraints:
          - node.role == worker
    ports:
      - "8080:8080"
    networks:
      - backend
      - monitoring
    secrets:
      - db_password
      - redis_password
    configs:
      - source: tracking_config
        target: /app/config.yaml
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  routing:
    image: registry.transcargo.local/routing:1.8.0
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '2.0'
          memory: 1G
    networks:
      - backend
    secrets:
      - db_password
      - maps_api_key

  api-gateway:
    image: registry.transcargo.local/api-gateway:3.1.0
    deploy:
      replicas: 3
    ports:
      - "443:8443"
    networks:
      - frontend
      - backend
    secrets:
      - tls_cert
      - tls_key

  postgres:
    image: postgres:16-alpine
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.storage == ssd
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - backend
    secrets:
      - db_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

networks:
  frontend:
    driver: overlay
    attachable: true
  backend:
    driver: overlay
    internal: true      # Нет доступа наружу
  monitoring:
    driver: overlay

volumes:
  pgdata:
    driver: local
    driver_opts:
      type: none
      device: /data/postgres
      o: bind

secrets:
  db_password:
    external: true
  redis_password:
    external: true
  tls_cert:
    external: true
  tls_key:
    external: true
  maps_api_key:
    external: true

configs:
  tracking_config:
    file: ./configs/tracking.yaml

# Деплой
docker stack deploy -c docker-stack.yml transcargo

# Проверка
docker service ls
# ID         NAME                    MODE        REPLICAS   IMAGE
# abc123     transcargo_tracking     replicated  3/3        tracking:2.3.1
# def456     transcargo_routing      replicated  2/2        routing:1.8.0
# ghi789     transcargo_api-gateway  replicated  3/3        api-gateway:3.1.0
# jkl012     transcargo_postgres     replicated  1/1        postgres:16-alpine

Overlay networking, secrets и масштабирование

Swarm overlay networks обеспечивают изолированную сеть между контейнерами на разных физических серверах:

# Overlay-сеть создаётся автоматически при stack deploy
# Или вручную:
docker network create --driver overlay --internal backend-net

# internal: true — контейнеры в этой сети не имеют выхода в интернет
# Только сервисы, подключённые к backend, видят друг друга

# Service discovery через DNS:
# Внутри контейнера tracking можно обратиться к routing по имени:
curl http://routing:8080/api/v1/route
# Docker DNS автоматически резолвит имя сервиса в VIP (Virtual IP)

# Ingress routing mesh:
# Запрос на порт 8080 любого узла кластера попадёт в tracking
# Даже если tracking не запущен на этом узле
curl http://srv4.transcargo.local:8080/healthz  # Работает, хотя tracking на srv4 нет

Docker Secrets — встроенное управление секретами:

# Создание секретов
echo "SuperStr0ngP@ss" | docker secret create db_password -
echo "RedisP@ss123" | docker secret create redis_password -
docker secret create tls_cert ./certs/server.crt
docker secret create tls_key ./certs/server.key

# Секреты монтируются как файлы в /run/secrets/
# Внутри контейнера:
cat /run/secrets/db_password
# SuperStr0ngP@ss

# Ротация секрета:
echo "NewP@ssw0rd!" | docker secret create db_password_v2 -
docker service update \
  --secret-rm db_password \
  --secret-add db_password_v2 \
  transcargo_tracking

# Список секретов
docker secret ls
# ID         NAME              CREATED
# abc123     db_password       2 weeks ago
# def456     redis_password    2 weeks ago

Масштабирование — одна команда:

# Горизонтальное масштабирование
docker service scale transcargo_tracking=5
# transcargo_tracking scaled to 5

# Rolling update (обновление без даунтайма)
docker service update \
  --image registry.transcargo.local/tracking:2.4.0 \
  --update-parallelism 1 \
  --update-delay 30s \
  --update-order start-first \
  --update-failure-action rollback \
  transcargo_tracking

# Откат к предыдущей версии
docker service rollback transcargo_tracking

# Мониторинг обновления в реальном времени
docker service ps transcargo_tracking
# ID       NAME                IMAGE           NODE   STATE
# abc      tracking.1          tracking:2.4.0  srv4   Running
# def      tracking.2          tracking:2.4.0  srv5   Running
# ghi      tracking.3          tracking:2.3.1  srv4   Running (updating...)
# jkl       \_ tracking.3     tracking:2.3.1  srv5   Shutdown

Мониторинг Docker Swarm

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

# monitoring-stack.yml
version: "3.8"

services:
  prometheus:
    image: prom/prometheus:v2.51.0
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager
    ports:
      - "9090:9090"
    volumes:
      - prometheus_data:/prometheus
    configs:
      - source: prometheus_config
        target: /etc/prometheus/prometheus.yml
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:10.4.1
    deploy:
      replicas: 1
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/grafana_password
    secrets:
      - grafana_password
    networks:
      - monitoring

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.49.1
    deploy:
      mode: global    # На каждом узле кластера
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:v1.7.0
    deploy:
      mode: global
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
    networks:
      - monitoring

networks:
  monitoring:
    external: true

volumes:
  prometheus_data:
  grafana_data:

secrets:
  grafana_password:
    external: true

configs:
  prometheus_config:
    file: ./prometheus.yml

# Деплой мониторинга
docker stack deploy -c monitoring-stack.yml monitoring

Prometheus-конфигурация для auto-discovery сервисов Swarm:

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'cadvisor'
    dns_sd_configs:
      - names:
          - 'tasks.cadvisor'
        type: 'A'
        port: 8080

  - job_name: 'node-exporter'
    dns_sd_configs:
      - names:
          - 'tasks.node-exporter'
        type: 'A'
        port: 9100

  - job_name: 'docker-swarm'
    dockerswarm_sd_configs:
      - host: unix:///var/run/docker.sock
        role: tasks
    relabel_configs:
      - source_labels: [__meta_dockerswarm_service_name]
        target_label: service_name
      - source_labels: [__meta_dockerswarm_node_hostname]
        target_label: node

Когда Swarm достаточно vs когда нужен Kubernetes

После 3 месяцев эксплуатации Swarm в «ТрансКарго» мы составили чёткие критерии выбора:

Docker Swarm достаточно, если:

  • Команда маленькая (1-5 человек) и нет DevOps-экспертизы
  • Количество сервисов до 20-30
  • Нагрузка до 5000 RPS
  • Нет требований к сложному автомасштабированию (HPA по кастомным метрикам)
  • Нет необходимости в service mesh (Istio, Linkerd)
  • Нет multi-tenancy (несколько команд в одном кластере)
  • Stateful-нагрузки минимальны (1-2 базы данных)

Kubernetes нужен, если:

  • Более 30 сервисов или планируется быстрый рост
  • Несколько команд работают в одном кластере (RBAC, namespaces, quotas)
  • Нужны CRDs и операторы (PostgreSQL Operator, Kafka Operator)
  • Требуется GitOps (ArgoCD, Flux)
  • Автомасштабирование по бизнес-метрикам (KEDA)
  • Service mesh для mTLS, traffic splitting, circuit breakers
  • Сложные storage requirements (CSI drivers, dynamic provisioning)

Для «ТрансКарго» Swarm оказался идеальным выбором: 8 сервисов, маленькая команда, быстрый запуск. Через год, когда платформа выросла до 18 сервисов и потребовался GitOps, мы спланировали миграцию на Kubernetes.

Путь миграции с Swarm на Kubernetes

Миграция с Docker Swarm на Kubernetes — не переключение тумблера, а поэтапный процесс:

# Этап 1: Подготовка Kubernetes-кластера рядом со Swarm
# Используем k3s — легковесный K8s для bare-metal
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
  --cluster-init \
  --tls-san k8s.transcargo.local \
  --disable traefik" sh -

# Добавляем worker-ы (можно те же серверы, что и Swarm — разные порты)
curl -sfL https://get.k3s.io | K3S_URL=https://srv1:6443 \
  K3S_TOKEN=$(cat /var/lib/rancher/k3s/server/node-token) sh -

# Этап 2: Конвертация docker-compose.yml в Kubernetes-манифесты
# Инструмент kompose:
kompose convert -f docker-stack.yml
# -> tracking-deployment.yaml
# -> tracking-service.yaml
# -> routing-deployment.yaml
# -> routing-service.yaml
# -> ...

# Ручная доработка — kompose не конвертирует 100% корректно:
# - deploy.placement.constraints → nodeSelector/affinity
# - secrets → Kubernetes Secrets или ExternalSecrets
# - configs → ConfigMap
# - healthcheck → livenessProbe/readinessProbe
# - volumes → PV/PVC

# Этап 3: Параллельная работа
# Swarm продолжает обслуживать трафик
# Kubernetes поднимается рядом с теми же образами
# Nginx на фронте переключает трафик постепенно:

# nginx.conf (на внешнем балансировщике)
upstream tracking_backend {
    server swarm-vip:8080 weight=80;     # 80% на Swarm
    server k8s-ingress:8080 weight=20;   # 20% на K8s
}

# Этап 4: Постепенное переключение
# Мониторим ошибки и латентность на K8s
# Увеличиваем вес K8s: 20% → 50% → 80% → 100%
# Когда K8s стабилен — выключаем Swarm

# Этап 5: Очистка
docker stack rm transcargo
docker swarm leave --force  # На каждом узле

Типичные подводные камни при миграции:

  • Volumes — в Swarm bind mounts через driver_opts, в K8s нужны PV/PVC. Данные нужно мигрировать.
  • Networking — Swarm overlay vs K8s CNI (Calico, Flannel). Сетевые политики в K8s строже.
  • Secrets — Docker Secrets монтируются как файлы в /run/secrets/. В K8s секреты можно монтировать как файлы или environment variables. Приложение может потребовать адаптации.
  • DNS — в Swarm имя сервиса = DNS-имя. В K8s формат: <service>.<namespace>.svc.cluster.local.

Результаты и выводы

Итоги 12 месяцев эксплуатации Docker Swarm в «ТрансКарго»:

МетрикаДо (без оркестрации)Docker Swarm
Время деплоя40 мин (SSH + docker run)3 мин (docker stack deploy)
Downtime при обновлении5-15 мин0 (rolling update)
Восстановление после сбоя узла30 мин (ручной перезапуск)30 сек (автоматический reschedule)
Время настройки кластера4 часа (с нуля)
Время обучения команды2 дня
Uptime (SLA)97.5%99.8%

Ключевой вывод: Docker Swarm — отличный оркестратор для старта. Он решает 80% задач при 20% сложности Kubernetes. Не нужно стыдиться Swarm — он production-ready и используется тысячами компаний. Но если вы планируете рост до десятков сервисов, GitOps, service mesh и мульти-тенантность — закладывайте Kubernetes с самого начала или планируйте миграцию.

Если вам нужна помощь с выбором оркестратора или миграцией — обращайтесь к нам в itfresh.ru.

Часто задаваемые вопросы

Docker Swarm продолжает поддерживаться как часть Docker Engine. Он не deprecated, но развитие замедлилось — новые фичи почти не добавляются. Для существующих проектов Swarm остаётся стабильным и надёжным решением. Для новых проектов с долгосрочной перспективой стоит рассмотреть Kubernetes или его легковесные варианты (k3s, k0s).
Нечётное число: 3 для продакшена (выдерживает падение 1 ноды), 5 для критичных систем (выдерживает падение 2 нод). Один manager — single point of failure. Два manager-а хуже, чем один, потому что при падении одного кворум теряется и кластер блокируется. Более 7 manager-нод не рекомендуется — Raft-консенсус замедляется.
Swarm не имеет встроенного динамического provisioning как Kubernetes PV/PVC. Варианты: 1) bind mounts с placement constraints — контейнер БД всегда запускается на конкретном сервере с данными. 2) NFS volume plugin — общее хранилище доступно со всех узлов. 3) REX-Ray или Portworx — CSI-подобные плагины для Swarm. Для production-БД мы рекомендуем вариант 1 + регулярные бэкапы.
Не совсем. docker-compose.yml (Compose v2) и docker-stack.yml (Compose v3) имеют различия. Ключ deploy (replicas, update_config, placement) работает только в Swarm mode. Ключи build, depends_on, links игнорируются при docker stack deploy. Рекомендуется держать отдельные файлы: docker-compose.yml для локальной разработки и docker-stack.yml для Swarm.
Три настройки в секции deploy.update_config: 1) order: start-first — новый контейнер запускается до остановки старого. 2) healthcheck — Swarm считает контейнер готовым только после прохождения health check. 3) parallelism: 1 с delay: 30s — обновление по одному контейнеру с паузой. При такой конфигурации и replicas >= 2 пользователи не видят прерывания сервиса.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#docker swarm#kubernetes#container orchestration#swarm init#overlay network#docker stack deploy#swarm secrets#rolling update
Комментарии 0

Оставить комментарий

загрузка...