技术博客

Docker Swarm 集群实战:节点管理、Service 部署与滚动更新

系统讲解 Docker Swarm 集群的完整运维体系:Swarm 初始化与节点加入、Manager/Worker 节点的职责与高可用(3/5 Manager 奇数原则)、Service 创建与约束(副本数/资源限制/放置策略)、Stack 部署多服务应用、滚动更新与回滚策略配置、健康检查联动自动替换故障容器,以及 Swarm 集群的日常监控与故障处理。

DockerSwarm集群ServiceStack滚动更新高可用企业级

Docker Swarm 是 Docker 原生的集群编排方案,与 Kubernetes 相比配置更简单、运维门槛更低,适合中小规模(百容器级别)的生产部署。本文讲解 Swarm 从初始化到日常运维的完整体系。

Swarm vs Kubernetes

对比维度          Docker Swarm         Kubernetes
────────────────────────────────────────────────────
学习曲线          低(复用 Docker 命令) 高(大量新概念)
集群规模          <1000 容器            万级容器
高可用            ✅ Manager HA         ✅ Control Plane HA
自动伸缩          ❌ 需手动或外部工具    ✅ HPA/VPA
存储管理          基础                  丰富(CSI 生态)
网络策略          基础                  Calico/Cilium 等
适用场景          中小规模,快速上线      大规模,复杂需求

结论:已有 docker-compose 经验,快速上生产 → Swarm
      长期规划,需要完整生态 → Kubernetes

一、集群初始化

高可用 Swarm 集群规划

生产 Swarm 集群节点规划:

Manager 节点(奇数个,推荐 3 或 5):
  swarm-manager-01  192.168.1.10  主 Manager(Leader)
  swarm-manager-02  192.168.1.11  备用 Manager
  swarm-manager-03  192.168.1.12  备用 Manager

Worker 节点:
  swarm-worker-01   192.168.1.20
  swarm-worker-02   192.168.1.21
  swarm-worker-03   192.168.1.22

Manager 数量与容错:
  1 个 Manager → 容忍 0 个 Manager 故障(不推荐生产)
  3 个 Manager → 容忍 1 个 Manager 故障(推荐)
  5 个 Manager → 容忍 2 个 Manager 故障(大规模)
  
原理:Raft 共识协议,需要 (N+1)/2 个 Manager 存活才能工作

初始化流程

# 第一台 Manager 节点初始化
docker swarm init \
    --advertise-addr 192.168.1.10 \  # 对外通告的 IP
    --data-path-addr 192.168.1.10    # overlay 网络流量 IP(可与 advertise 相同)

# 输出:
# Swarm initialized: current node (xxx) is now a manager.
# To add a worker to this swarm, run the following command:
#     docker swarm join --token SWMTKN-1-xxx-yyy 192.168.1.10:2377
# To add a manager to this swarm, run 'docker swarm join-token manager'

# 获取 Manager 加入 token
docker swarm join-token manager
# docker swarm join --token SWMTKN-1-xxx-zzz 192.168.1.10:2377

# 获取 Worker 加入 token
docker swarm join-token worker

# 第二、三台 Manager 节点加入
docker swarm join \
    --token SWMTKN-1-xxx-zzz \
    192.168.1.10:2377     # Manager 节点 IP:端口

# Worker 节点加入
docker swarm join \
    --token SWMTKN-1-xxx-yyy \
    192.168.1.10:2377

# 在任意 Manager 节点查看集群状态
docker node ls
# ID             HOSTNAME              STATUS  AVAILABILITY  MANAGER STATUS  ENGINE VERSION
# abc123 *       swarm-manager-01      Ready   Active        Leader          27.3.1
# def456         swarm-manager-02      Ready   Active        Reachable       27.3.1
# ghi789         swarm-manager-03      Ready   Active        Reachable       27.3.1
# jkl012         swarm-worker-01       Ready   Active                        27.3.1

节点管理

# 给节点打标签(用于服务放置约束)
docker node update --label-add role=web swarm-worker-01
docker node update --label-add role=db swarm-worker-02
docker node update --label-add zone=az1 swarm-worker-01
docker node update --label-add zone=az2 swarm-worker-02

# 临时下线节点(排空工作负载,维护时使用)
docker node update --availability drain swarm-worker-01
# 所有在 worker-01 上的 Task 会迁移到其他节点

# 维护结束,恢复节点
docker node update --availability active swarm-worker-01

# 将 Worker 提升为 Manager(增强 HA)
docker node promote swarm-worker-01

# 降级 Manager 为 Worker
docker node demote swarm-manager-03

# 移除节点(先在该节点离开 Swarm)
# 在要移除的节点上:
docker swarm leave
# 在 Manager 上:
docker node rm swarm-worker-01

二、Service — Swarm 的工作单元

Service 是 Swarm 中运行容器的声明:指定镜像、副本数、资源约束,Swarm 负责保持状态。

# 创建服务(最基础)
docker service create \
    --name nginx-web \
    --replicas 3 \
    --publish published=80,target=80 \
    nginx:1.25

# 查看服务
docker service ls
# ID       NAME       MODE         REPLICAS  IMAGE         PORTS
# abc123   nginx-web  replicated   3/3       nginx:1.25    *:80->80/tcp

# 查看服务详情(每个 Task 在哪个节点上)
docker service ps nginx-web
# ID       NAME          IMAGE      NODE              DESIRED STATE  CURRENT STATE
# xxx      nginx-web.1   nginx:1.25 swarm-worker-01   Running        Running 2m
# yyy      nginx-web.2   nginx:1.25 swarm-worker-02   Running        Running 2m
# zzz      nginx-web.3   nginx:1.25 swarm-worker-03   Running        Running 2m

完整 Service 创建参数

docker service create \
    --name api \
    \
    # 镜像
    --image harbor.company.com/backend/api:v1.2.3 \
    \
    # 副本
    --replicas 5 \
    \
    # 端口(Swarm 会在所有节点开监听,即使该节点没有 Task)
    --publish published=8080,target=8080,mode=ingress \
    # mode=ingress(默认):Routing Mesh,所有节点转发
    # mode=host:只在运行 Task 的节点上开端口
    \
    # 资源限制
    --limit-cpu 1.5 \
    --limit-memory 512m \
    --reserve-cpu 0.5 \          # 最低保证(调度时考虑)
    --reserve-memory 256m \
    \
    # 重启策略
    --restart-condition on-failure \
    --restart-delay 5s \
    --restart-max-attempts 3 \
    \
    # 放置约束(服务只调度到有指定标签的节点)
    --constraint "node.labels.role==web" \
    --constraint "node.role==worker" \      # 只调度到 Worker 节点
    \
    # 放置偏好(尽量分散到不同 zone)
    --placement-pref "spread=node.labels.zone" \
    \
    # 网络
    --network app-overlay \
    \
    # 环境变量
    --env APP_ENV=production \
    --env-file .env \
    \
    # 挂载
    --mount type=volume,source=app_data,target=/data \
    \
    # 健康检查
    --health-cmd "curl -f http://localhost:8080/health || exit 1" \
    --health-interval 30s \
    --health-retries 3

三、滚动更新

# 更新服务镜像(触发滚动更新)
docker service update \
    --image harbor.company.com/backend/api:v1.3.0 \
    --update-parallelism 2 \      # 每批更新 2 个副本
    --update-delay 10s \          # 每批之间等待 10 秒
    --update-failure-action rollback \   # 更新失败自动回滚
    --update-monitor 30s \        # 每个副本更新后监控 30 秒
    --update-max-failure-ratio 0.3 \  # 超过 30% 失败率则停止
    api

# 查看更新进度
docker service ps api
# ID       NAME      IMAGE                 NODE    DESIRED  CURRENT
# aaa      api.1     api:v1.3.0            node-1  Running  Running 1m  ← 已更新
# bbb      api.2     api:v1.3.0            node-2  Running  Running 30s ← 已更新
# ccc      api.3     api:v1.2.3            node-3  Running  Running 5m  ← 待更新
# ddd  \_ api.3  api:v1.2.3  node-3  Shutdown Shutdown 30s  ← 旧版本已停

滚动更新参数详解

# 更新策略配置(在 service create 时就可以设置默认值)
docker service create \
    --name api \
    --update-parallelism 1 \          # 每次更新 1 个(最保守)
    --update-delay 15s \              # 批间间隔 15 秒
    --update-order start-first \      # start-first=先启动新容器再停旧容器(零停机)
    # 默认 stop-first=先停旧容器再启动新容器
    --update-failure-action rollback \
    --update-monitor 60s \
    --rollback-parallelism 2 \        # 回滚时每批 2 个
    --rollback-delay 5s \
    --rollback-order stop-first \
    harbor.company.com/backend/api:v1.2.3

手动回滚

# 回滚到上一个版本
docker service rollback api

# 查看回滚进度
docker service ps api --filter desired-state=running

四、Stack — 多服务应用编排

Stack 是 Swarm 版本的 docker-compose,用 YAML 描述多服务集群应用。

# stack.yml

services:
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - type: bind
        source: /data/nginx/conf
        target: /etc/nginx/conf.d
        read_only: true
    networks:
      - frontend
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
        failure_action: rollback
      resources:
        limits:
          cpus: '0.5'
          memory: 128M
      placement:
        constraints:
          - node.role == worker

  api:
    image: harbor.company.com/backend/api:${APP_VERSION}
    environment:
      - DB_HOST=db
      - REDIS_HOST=redis
      - APP_ENV=production
    secrets:
      - db_password                   # Swarm Secret(加密存储)
    networks:
      - frontend
      - backend
    deploy:
      replicas: 4
      update_config:
        parallelism: 2
        delay: 15s
        order: start-first
        failure_action: rollback
        monitor: 30s
        max_failure_ratio: 0.2
      resources:
        limits:
          cpus: '2'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
      placement:
        preferences:
          - spread: node.labels.zone  # 跨 zone 分散

  db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: myapp
      MYSQL_USER: appuser
    secrets:
      - db_password
      - db_root_password
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - backend
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.role == db    # 只在 db 节点运行
      resources:
        limits:
          cpus: '4'
          memory: 4G

  redis:
    image: redis:7.2-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - backend
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.role == cache

networks:
  frontend:
    driver: overlay
  backend:
    driver: overlay
    attachable: false                 # 禁止手动 attach(安全)

volumes:
  db_data:
    driver: local
  redis_data:
    driver: local

secrets:
  db_password:
    external: true                    # 提前创建的 Swarm Secret
  db_root_password:
    external: true
# 提前创建 Swarm Secret(加密存储在 Raft 中)
echo "db_secret_123" | docker secret create db_password -
echo "root_secret_456" | docker secret create db_root_password -

# 部署 Stack
APP_VERSION=v1.2.3 docker stack deploy \
    -c stack.yml \
    myapp

# 查看 Stack
docker stack ls
# NAME    SERVICES  ORCHESTRATOR
# myapp   4         Swarm

docker stack ps myapp
docker stack services myapp

# 更新(修改 stack.yml 后重新 deploy 即可,Swarm 自动处理差异)
APP_VERSION=v1.3.0 docker stack deploy -c stack.yml myapp

# 删除 Stack(删除所有 Service,但 Volume 保留)
docker stack rm myapp

五、Swarm Secret

# 创建 Secret
echo "SuperSecret123" | docker secret create api_key -
docker secret create ssl_cert /etc/ssl/cert.pem
docker secret create ssl_key /etc/ssl/private.key

# 在 Service 中使用
docker service create \
    --name api \
    --secret api_key \              # 挂载到 /run/secrets/api_key
    --secret source=ssl_cert,target=/etc/nginx/cert.pem \
    myapp:v1

# 容器内读取
cat /run/secrets/api_key           # 读取 Secret 值
# SuperSecret123

# Secret 特点:
# - 加密存储在 Swarm Raft 数据库中
# - 只传输给需要它的 Task 所在节点
# - 传输时 TLS 加密
# - 只以 tmpfs 形式挂载到容器(不落盘)

# 轮换 Secret(先创建新 Secret,再更新 Service)
echo "NewSecret456" | docker secret create api_key_v2 -
docker service update \
    --secret-rm api_key \
    --secret-add api_key_v2 \
    api
docker secret rm api_key

六、集群监控与故障处理

# 查看 Swarm 节点状态
docker node ls

# 查看节点详情(资源使用、标签等)
docker node inspect swarm-worker-01 --pretty

# 查看服务的所有 Task(包括历史)
docker service ps api --no-trunc

# 查看 Task 失败原因
docker service ps api --filter "desired-state=shutdown" \
    --format "{{.Name}}: {{.Error}}"

# 查看 Swarm 事件
docker events --filter scope=swarm

# Leader 选举(当 Leader 故障时自动选举)
# 确认当前 Leader
docker node ls | grep Leader

# 强制新 Leader 选举(运维场景)
docker node demote swarm-manager-01  # 将当前 Leader 降级
# Swarm 自动选举新 Leader

# 扩缩容服务
docker service scale api=8           # 扩到 8 个副本
docker service scale api=2 worker=4  # 同时调整多个 Service

# 强制重新分配(解决 Task 分布不均)
docker service update --force api

小结

Docker Swarm 的核心概念链:Node(节点)→ Service(服务声明)→ Task(实际运行的容器)→ Stack(多 Service 集合)。

生产关键配置三点:Manager 节点奇数个(Raft 共识需要)、滚动更新用 start-first(先起新容器再停旧容器,零停机)、Secret 替代明文环境变量(加密存储,只传给需要的节点)。

Swarm 的主要局限:有状态服务(数据库)副本数只能设 1,存储不如 K8s 灵活,自动伸缩需要外部工具。对于这些需求,K8s 是更好的选择。