云计算
悠悠
2026年4月11日

Docker Swarm 搞定高可用集群,生产环境再也不怕服务挂掉了

Docker Swarm是什么?

Docker Swarm 是 Docker 官方推出的容器集群管理工具,基于 Go 语言实现。代码开源在:https://github.com/docker/swarm 使用它可以将多个 Docker 主机封装为单个大型的虚拟 Docker 主机,快速打造一套容器云平台。

Docker Swarm 是生产环境中运行 Docker 应用程序最简单的方法。作为容器集群管理器,Swarm 最大的优势之一就是 100% 支持标准的 Docker API。各种基于标准 API 的工具比如 Compose、docker-py、各种管理软件,甚至 Docker 本身等都可以很容易的与 Swarm 进行集成。大大方便了用户将原先基于单节点的系统移植到 Swarm 上,同时 Swarm 内置了对 Docker 网络插件的支持,用户可以很容易地部署跨主机的容器集群服务。

Docker Swarm 和 Docker Compose 一样,都是 Docker 官方容器编排工具,但不同的是,Docker Compose 是一个在单个服务器或主机上创建多个容器的工具,而 Docker Swarm 则可以在多个服务器或主机上创建容器集群服务,对于微服务的部署,显然 Docker Swarm 会更加适合。

为什么选 Docker Swarm 而不是 K8s

说实话,Kubernetes 确实强大,但对我们这种中小团队来说,K8s 的学习成本和维护成本都太高了。光是搭建一套高可用的 K8s 集群就得折腾好几天,更别说日常维护了。

Docker Swarm 就不一样了,它是 Docker 原生自带的编排工具,学过 Docker 的基本上一个小时就能上手。而且功能足够强大,服务发现、负载均衡、滚动更新、健康检查这些该有的都有,完全能满足大部分生产场景的需求。

客户现在的业务规模大概有 30 多个微服务,每天访问量在百万级别,用 Docker Swarm 完全够用。如果你的规模跟我差不多,真心建议别一上来就搞 K8s,Swarm 真的够用了。

环境准备

5 台服务器,配置如下:

  • 3 台 Manager 节点:16G 内存,4 核 CPU
  • 2 台 Worker 节点:8G 内存,2 核 CPU

为什么 Manager 要 3 台?这个后面会详细说。系统用的是 CentOS 7.9,Docker 版本是 20.10.14。

在开始之前,有几件事得先做好:

1. 主机名和 hosts 配置

每台机器都要设置不同的主机名,不然等会儿集群建起来都叫 localhost,看着就头疼。

# 在 manager1 上执行
hostnamectl set-hostname manager1
# 其他机器类似,改成 manager2、manager3、worker1、worker2

然后修改 /etc/hosts 文件,把所有节点的 IP 和主机名都写进去:

192.168.1.101 manager1
192.168.1.102 manager2
192.168.1.103 manager3
192.168.1.104 worker1
192.168.1.105 worker2

2. 关闭防火墙或者开放端口

Docker Swarm 需要几个端口:

  • 2377/tcp:集群管理通信
  • 7946/tcp+udp:节点间通信
  • 4789/udp:覆盖网络流量

生产环境建议还是开防火墙,只开放这些端口。测试环境图省事直接关了也行:

systemctl stop firewalld
systemctl disable firewalld

3. 时间同步

这个特别重要!如果节点时间不同步,集群选举会出问题,服务调度也会乱套。我们用 chrony 做时间同步:

yum install -y chrony
systemctl start chronyd
systemctl enable chronyd

初始化 Swarm 集群

准备工作做完了,现在开始正式搭建集群。

在 manager1 上执行初始化命令:

docker swarm init --advertise-addr 192.168.1.101

执行完会输出一串命令,大概是这样的:

Swarm initialized: current node (dxn1zf6l61qsb1josjja83ngz) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssm7h7g6w7br5d7vwlh 192.168.1.101:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

这个 token 特别重要,一定要保存好,后面添加节点都要用它。

现在集群已经有一个 Manager 节点了,查看一下集群状态:

docker node ls

输出类似这样:

ID                            HOSTNAME    STATUS   AVAILABILITY   MANAGER STATUS   ENGINE VERSION
dxn1zf6l61qsb1josjja83ngz *   manager1    Ready     Active         Leader           20.10.14

能看到 manager1 是 Leader 状态。

添加其他 Manager 节点

前面说了要用 3 个 Manager 节点,为什么要奇数个?这个跟 Raft 共识算法有关。

Docker Swarm 用 Raft 算法来管理集群状态,需要半数以上的 Manager 节点在线才能正常工作。如果你有 2 个 Manager,挂掉 1 个,剩下 1 个不满足"半数以上"的条件,集群就不可用了。但如果是 3 个 Manager,挂掉 1 个,剩下 2 个还能正常工作。

所以奇数个 Manager 是最佳实践,一般 3 个就够用了,能容忍 1 个节点故障。如果想要更高的容错能力,可以用 5 个,能容忍 2 个节点故障。[2]

在 manager1 上获取 Manager 加入令牌:

docker swarm join-token manager

会输出一个命令,复制到 manager2 和 manager3 上执行:

docker swarm join --token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssm7h7g6w7br5d7vwlh 192.168.1.101:2377

执行完后再看集群状态:

docker node ls
ID                            HOSTNAME    STATUS   AVAILABILITY   MANAGER STATUS   ENGINE VERSION
dxn1zf6l61qsb1josjja83ngz *   manager1    Ready     Active         Leader           20.10.14
rfm5vi4vpxpmhjw6vz0t4wqdv     manager2    Ready     Active         Reachable        20.10.14
y6jcg5z4m7x0tz1f0qz3n5k8s     manager3    Ready     Active         Reachable        20.10.14

看到 3 个 Manager 都在线了,manager1 是 Leader,其他两个是 Reachable 状态。

添加 Worker 节点

Worker 节点只负责运行容器,不参与集群管理。在 manager1 上获取 Worker 加入令牌:

docker swarm join-token worker

复制输出的命令到 worker1 和 worker2 上执行:

docker swarm join --token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssm7h7g6w7br5d7vwlh 192.168.1.101:2377

再看集群状态:

docker node ls
ID                            HOSTNAME    STATUS   AVAILABILITY   MANAGER STATUS   ENGINE VERSION
dxn1zf6l61qsb1josjja83ngz *   manager1    Ready     Active         Leader           20.10.14
rfm5vi4vpxpmhjw6vz0t4wqdv     manager2    Ready     Active         Reachable        20.10.14
y6jcg5z4m7x0tz1f0qz3n5k8s     manager3    Ready     Active         Reachable        20.10.14
n8xkp2z3m5q1rt7v9w2j4h6c0     worker1     Ready     Active                          20.10.14
t3h7m9k2p8q5wv1n4x6j0r2b8     worker2     Ready     Active                          20.10.14

5 个节点的集群就搭建完成了。

创建覆盖网络

集群搭好了,但服务之间怎么通信呢?这就需要创建覆盖网络(Overlay Network)。

覆盖网络允许不同节点上的容器互相通信,就像在同一个局域网里一样。

docker network create --driver overlay --attachable my-overlay

--attachable 参数很重要,它允许独立容器连接到这个网络。如果不加这个参数,只有 Swarm 服务才能用这个网络。

查看网络:

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
n33wq0p5m6q7        ingress             overlay             swarm
r2t5v8x1p4m9        my-overlay          overlay             swarm

部署一个测试服务

光搭集群没意思,咱们部署一个实际的服务试试。

我这里用 Nginx 做演示,部署 3 个副本:

docker service create \
  --name nginx-test \
  --replicas 3 \
  --network my-overlay \
  --publish 80:80 \
  nginx:latest

参数解释一下:

  • --name:服务名称
  • --replicas:副本数量,这里启动 3 个容器
  • --network:使用的网络
  • --publish:端口映射,把容器的 80 端口映射到宿主机的 80 端口

查看服务状态:

docker service ls
ID                  NAME           MODE         REPLICAS   IMAGE          PORTS
k8z2m9p4n7q1        nginx-test     replicated   3/3        nginx:latest   *:80->80/tcp

REPLICAS 显示 3/3,说明 3 个副本都正常运行。

查看服务详情:

docker service ps nginx-test
ID                  NAME           IMAGE          NODE       DESIRED STATE   CURRENT STATE           ERROR     PORTS
t3h7m9k2p8q5        nginx-test.1   nginx:latest   worker1    Running         Running 2 minutes ago           
r2t5v8x1p4m9        nginx-test.2   nginx:latest   manager2   Running         Running 2 minutes ago           
n8xkp2z3m5q1        nginx-test.3   nginx:latest   worker2    Running         Running 2 minutes ago           

能看到 3 个容器分别运行在 worker1、manager2 和 worker2 上。Swarm 会自动把容器调度到不同的节点,保证负载均衡。

现在随便访问哪个节点的 80 端口,都能看到 Nginx 的欢迎页面。这就是 Swarm 的负载均衡功能,外部请求会自动分发到各个容器。

服务高可用测试

说了半天高可用,到底高可用在哪里?

我们来模拟一下节点故障。直接把 worker1 关机:

# 在 worker1 上执行
shutdown -h now

然后等个几十秒,再查看服务状态:

docker service ps nginx-test
ID                  NAME                 IMAGE          NODE       DESIRED STATE   CURRENT STATE                ERROR     PORTS
t3h7m9k2p8q5        nginx-test.1         nginx:latest   worker1    Shutdown        Shutdown 30 seconds ago             
k7v2m9p4n7q1        nginx-test.1         nginx:latest   manager3   Running         Running 10 seconds ago              
r2t5v8x1p4m9        nginx-test.2         nginx:latest   manager2   Running         Running 5 minutes ago                 
n8xkp2z3m5q1        nginx-test.3         nginx:latest   worker2    Running         Running 5 minutes ago

看到了吗?原来在 worker1 上运行的容器自动关闭了,Swarm 在 manager3 上重新启动了一个新的容器。整个过程完全自动,不需要人工干预。

这就是高可用的核心:当某个节点挂掉,上面的服务会自动迁移到其他健康节点上运行。

再测试一下 Manager 节点故障。我们把 manager1(Leader)关机:

# 在 manager1 上执行
shutdown -h now

等一会儿,在 manager2 或 manager3 上查看集群状态:

docker node ls
ID                            HOSTNAME    STATUS     AVAILABILITY   MANAGER STATUS   ENGINE VERSION
dxn1zf6l61qsb1josjja83ngz     manager1    Down       Active         Unreachable      20.10.14
rfm5vi4vpxpmhjw6vz0t4wqdv *   manager2    Ready      Active         Leader           20.10.14
y6jcg5z4m7x0tz1f0qz3n5k8s     manager3    Ready      Active         Reachable        20.10.14
n8xkp2z3m5q1rt7v9w2j4h6c0     worker1     Ready      Active                          20.10.14
t3h7m9k2p8q5wv1n4x6j0r2b8     worker2     Ready      Active                          20.10.14

manager1 显示 Down 和 Unreachable,而 manager2 自动变成了 Leader。这就是 Raft 协议的功劳,剩下的 2 个 Manager 节点重新选举,manager2 成了新的 Leader。

集群依然正常工作,服务不受影响。

部署真实业务服务

测试服务跑通了,现在部署一个真实的业务。我们有个用户中心服务,用 Spring Boot 写的,依赖 MySQL 和 Redis。

1. 创建 MySQL 服务

docker service create \
  --name mysql \
  --replicas 1 \
  --network my-overlay \
  --mount type=volume,source=mysql-data,target=/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=your_password \
  -e MYSQL_DATABASE=user_center \
  --constraint 'node.role==worker' \
  mysql:5.7

这里用了 --constraint 'node.role==worker',强制 MySQL 只运行在 Worker 节点上。因为 Manager 节点已经够忙了,数据库这种重服务还是放 Worker 上比较好。

--mount 参数挂载了数据卷,保证数据持久化。如果不挂载卷,容器重建后数据就丢了。

2. 创建 Redis 服务

docker service create \
  --name redis \
  --replicas 1 \
  --network my-overlay \
  --mount type=volume,source=redis-data,target=/data \
  --constraint 'node.role==worker' \
  redis:6.2

3. 创建用户中心服务

docker service create \
  --name user-center \
  --replicas 2 \
  --network my-overlay \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e MYSQL_HOST=mysql \
  -e REDIS_HOST=redis \
  --publish 8080:8080 \
  your-registry.com/user-center:latest

注意这里的环境变量 MYSQL_HOST=mysqlREDIS_HOST=redis,直接用服务名就能访问,这就是服务发现的好处。Swarm 内置了 DNS 服务器,会自动把服务名解析到对应的容器 IP。

服务部署完了,看看整体状态:

docker service ls
ID                  NAME           MODE         REPLICAS   IMAGE                                PORTS
k7v2m9p4n7q1        mysql          replicated   1/1        mysql:5.7                         
r2t5v8x1p4m9        redis          replicated   1/1        redis:6.2                         
t3h7m9k2p8q5        user-center    replicated   2/2        your-registry.com/user-center:latest  *:8080->8080/tcp

所有服务都正常运行。

使用 Docker Compose 文件部署

上面的命令行方式适合单个服务,如果服务多了,管理起来很麻烦。Docker Swarm 支持用 Docker Compose 文件部署,方便多了。

创建一个 docker-compose.yml 文件:

version: '3.8'

services:
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: your_password
      MYSQL_DATABASE: user_center
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - my-overlay
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == worker

  redis:
    image: redis:6.2
    volumes:
      - redis-data:/data
    networks:
      - my-overlay
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == worker

  user-center:
    image: your-registry.com/user-center:latest
    environment:
      SPRING_PROFILES_ACTIVE: prod
      MYSQL_HOST: mysql
      REDIS_HOST: redis
    ports:
      - "8080:8080"
    networks:
      - my-overlay
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3

networks:
  my-overlay:
    external: true

volumes:
  mysql-data:
  redis-data:

这个文件里加了几个重要的配置:

  • deploy.update_config:滚动更新配置,一次更新 1 个容器,间隔 10 秒,失败自动回滚
  • deploy.restart_policy:重启策略,失败后重启,最多重试 3 次

部署服务:

docker stack deploy -c docker-compose.yml myapp

查看 stack:

docker stack ls
NAME      SERVICES
myapp     3

查看 stack 里的服务:

docker stack services myapp

用 stack 的好处是一次性部署多个服务,而且更新的时候也很方便,改完配置文件重新执行 deploy 命令就行。

滚动更新实战

业务迭代是常事,更新服务是家常便饭。传统方式更新服务,得先停掉旧版本,再启动新版本,中间会有服务中断。

Docker Swarm 的滚动更新就优雅多了,它会逐步用新版本替换旧版本,保证服务始终可用。

假设我们要把 user-center 服务从 v1.0 更新到 v2.0:

docker service update \
  --image your-registry.com/user-center:v2.0 \
  --update-parallelism 1 \
  --update-delay 10s \
  --update-failure-action rollback \
  myapp_user-center

参数说明:

  • --update-parallelism 1:一次更新 1 个容器
  • --update-delay 10s:更新间隔 10 秒
  • --update-failure-action rollback:更新失败自动回滚

更新过程中查看状态:

docker service ps myapp_user-center
ID                  NAME                      IMAGE                                NODE       DESIRED STATE   CURRENT STATE            ERROR     PORTS
k7v2m9p4n7q1        myapp_user-center.1       your-registry.com/user-center:v2.0   worker1    Running         Running 5 seconds ago             
t3h7m9k2p8q5         \_ myapp_user-center.1   your-registry.com/user-center:v1.0   worker1    Shutdown        Shutdown 6 seconds ago           
r2t5v8x1p4m9        myapp_user-center.2       your-registry.com/user-center:v1.0   worker2    Running         Running 2 minutes ago             

能看到 worker1 上的容器已经更新到 v2.0,worker2 上的还是 v1.0。等 10 秒后,worker2 上的也会更新。

如果新版本有问题,比如容器启动失败,Swarm 会自动停止更新并回滚到旧版本。这个功能太实用了,再也不用担心更新把服务搞挂了。

服务扩缩容

业务高峰期,服务扛不住了,需要快速扩容。Docker Swarm 扩容只需要一条命令:

docker service scale myapp_user-center=5

瞬间就把 user-center 服务扩展到 5 个副本。Swarm 会自动把新容器调度到空闲的节点上。

查看扩容结果:

docker service ls
ID                  NAME                   MODE         REPLICAS   IMAGE                                PORTS
t3h7m9k2p8q5        myapp_user-center      replicated   5/5        your-registry.com/user-center:v2.0   *:8080->8080/tcp

缩容也一样简单:

docker service scale myapp_user-center=2

Swarm 会自动选择要停止的容器,保证负载均衡。

私有镜像仓库配置

生产环境的镜像一般都存在私有仓库里,Docker Swarm 拉取私有镜像需要认证。

假设我们用 Harbor 搭建了私有仓库,地址是 registry.example.com。

1. 创建 Docker 登录认证

在任意 Manager 节点登录私有仓库:

docker login registry.example.com

输入用户名和密码,登录成功后,认证信息会保存在 ~/.docker/config.json 文件里。

2. 创建 Docker Secret

把认证信息创建成 Swarm Secret:

docker secret create registry-auth ~/.docker/config.json

3. 创建服务时使用 Secret

docker service create \
  --name user-center \
  --replicas 2 \
  --network my-overlay \
  --secret source=registry-auth,target=/root/.docker/config.json \
  --publish 8080:8080 \
  registry.example.com/user-center:latest

这样服务就能从私有仓库拉取镜像了。

监控和日志

集群跑起来了,监控不能少。我们用 Portainer 来管理 Swarm 集群,用 ELK 收集日志。

部署 Portainer

Portainer 是个轻量级的容器管理界面,支持 Docker Swarm。

docker service create \
  --name portainer \
  --replicas 1 \
  --publish 9000:9000 \
  --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
  --mount type=volume,src=portainer-data,dst=/data \
  portainer/portainer-ce:latest

访问任意节点的 9000 端口,就能看到 Portainer 的管理界面。可以查看所有服务、容器、网络、卷,还能在 Web 界面直接操作,非常方便。[5]

日志收集

容器的日志默认存在本地,不方便查看和分析。我们用 ELK(Elasticsearch + Logstash + Kibana)来收集日志。

不过 ELK 部署比较复杂,这里就不展开了。简单点的方案是用 Filebeat 收集日志,发送到 Elasticsearch,然后用 Kibana 查看。

踩过的坑

搭建过程中遇到不少坑,记录下来给大家避雷。

坑 1:Manager 节点不要跑太多服务

刚开始我把所有服务都往 Manager 节点上放,结果 Manager 负载太高,集群管理都受影响。后来加了 --constraint 'node.role==worker',把重服务都限制在 Worker 节点上,问题解决。

坑 2:数据卷挂载要小心

MySQL 这种有状态的服务,一定要挂载数据卷,不然容器重建后数据就丢了。而且挂载的时候要用 type=volume,不要用 type=bind,因为 bind 挂载依赖宿主机的目录结构,迁移起来很麻烦。

坑 3:端口冲突

多个服务不能用同一个宿主机端口。我有次部署两个服务都用 8080 端口,结果第二个服务启动失败。解决办法是每个服务用不同的端口,或者用 Ingress 路由。[4]

坑 4:节点时间不同步

前面说过,节点时间不同步会导致集群选举出问题。我有次重启服务器后,时间同步服务没起来,结果集群状态一直不正常。后来加了监控,定期检查时间同步状态。

坑 5:网络问题

Docker Swarm 用覆盖网络实现跨节点通信,如果网络配置有问题,容器之间会无法通信。有次我忘了开放 4789/UDP 端口,结果容器之间 ping 不通。排查了半天才发现是防火墙的问题。

生产环境的一些建议

在测试环境跑通了,上生产环境还得注意几点:

1. 资源限制

给每个服务设置资源限制,防止某个服务占用太多资源影响其他服务:

docker service update \
  --limit-cpu 2 \
  --limit-memory 4G \
  --reserve-cpu 1 \
  --reserve-memory 2G \
  myapp_user-center

2. 健康检查

给服务加上健康检查,Swarm 会定期检查容器状态,不健康就自动重启:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 40s

3. 备份策略

定期备份 Manager 节点的 /var/lib/docker/swarm 目录,里面存着集群的状态数据。如果所有 Manager 都挂了,可以用备份恢复集群。

4. 监控告警

部署监控(Prometheus + Grafana),设置告警规则。集群状态、服务状态、资源使用情况都要监控。

5. 灰度发布

更新服务时,不要一次性全部更新。可以先更新 1 个副本,观察没问题后再继续更新。用 --update-parallelism 参数控制。

写在最后

Docker Swarm 虽然不如 Kubernetes 强大,但对中小团队来说,绝对够用了。搭建简单,维护成本低,功能也齐全。我们公司用了快一年,除了刚开始踩了几个坑,后面一直很稳定。

最重要的是,它真的解决了我们单点故障的问题。现在随便挂一台服务器,服务都能自动迁移到其他节点,再也不用半夜起来重启服务了。

如果你们团队也在考虑容器编排方案,不妨先试试 Docker Swarm。等业务规模大了,再考虑迁移到 Kubernetes 也不迟。

这篇文章写了好久,把搭建过程和踩过的坑都详细记录下来了。如果你也在用 Docker Swarm,或者准备搭建 Swarm 集群,希望这篇文章能帮到你。

有问题欢迎在评论区留言,我会尽量回复。如果觉得文章有用,点个赞再走呗,也欢迎转发给需要的朋友。


公众号:运维躬行录

个人博客:躬行笔记

关注公众号,获取更多运维干货文章,一起成长,一起进步!

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码