
我们经常在群里看到一类求助Swarm集群跑了大半年一切正常某天运维在机房里拔错了一根网线或者一台云主机直接报废然后整个集群的调度就瘫痪了——服务不重启、新任务不调度所有健康检查全亮红灯。这时候才发现所谓集群高可用并不是装好就完事了你需要理解Swarm的可用性模型提前做对部署规划并且把故障恢复路径练成肌肉记忆。这篇文章接上一篇的Swarm基础专门聊高可用HA部署和故障恢复这套东西。适合那些已经能跑通Swarm基础服务正准备上生产环境、或者已经在生产环境里被故障教育过的朋友。我会把原理、实操命令、真实故障场景的恢复路径一起讲清楚尽量让你看完之后心里对Swarm到底能不能扛住故障这件事有个准确判断。1. 高可用部署的底层逻辑Swarm靠什么撑住不宕机先想清楚一个问题Docker Swarm的高可用到底高在哪一层是节点不宕机吗当然不是任何物理机、虚拟机、云主机都有挂掉的可能。Swarm的HA核心是在部分节点失效时集群仍然能对外提供调度和故障转移的能力。也就是说高可用不是防止故障而是在故障发生后系统还能维持可用状态。1.1 Swarm架构里的三个角色与配额逻辑Swarm集群里的节点分为两类Manager管理节点和Worker工作节点。Manager负责集群状态管理、服务编排、API请求处理Worker负责实际运行容器任务。Manager内部还区分Leader领导者和Follower跟随者Leader负责对外下发调度指令Follower负责同步状态并随时准备接替。需要特别注意的一点Manager节点既可以当管理节点也可以同时运行任务容器。生产环境的建议是纯管理节点尽量不打业务负载即对Manager执行docker node update --availability drain防止它被业务流量拖垮影响整个集群的调度能力。但这个小技巧先按下不表后面部署时细说。1.2 Raft仲裁机制为什么Manager必须是奇数Swarm的集群状态依赖Raft一致性协议来维护。所有关于服务、任务、节点的元数据都保存在Raft日志里只有大多数quorumManager节点确认写入这个变更才算成功。这里有个关键公式如果总共有N个Manager节点只有存活节点数大于N/2集群才能继续正常工作。具体来说1个Manager允许0台故障。Manager挂了集群就完全不可用。3个Manager允许1台故障。还能维持仲裁。5个Manager允许2台故障。更多容错。所以生产环境至少3个Manager如果预算和技术底气都够5个也行——3个已经能满足绝大多数场景。这里要强调一个常见误区很多人觉得4个Manager比3个更稳其实是错的。4个Manager同样只允许1台故障因为213不大于4/22不对仲裁需要大于4/2即大于2即至少3台存活4台里挂2台剩2台就失去仲裁挂1台剩3台还能运行。所以4个Manager的容错能力和3个完全一样却白白多维护一台节点还可能在网络分区时更容易触发两边都不足仲裁的尴尬。1.3 服务调度与自愈闭环故障转移的源头当服务的期望状态是replicas 3Swarm会把3个任务调度到可用节点上。某个节点宕机后Leader会通过心跳超时感知到节点失联将该节点标记为Down然后在其他健康节点上把任务重建出来。这个过程的快慢取决于服务是否配置了健康检查health check健康检查的间隔和失败阈值决定任务何时被判定为不健康。节点的故障检测延迟默认心跳间隔可以在dockerd启动参数里调整。任务重启策略restart-condition默认是any只要进程退出就会尝试在同一节点重启节点Down时Swarm会把任务移到别的节点。理解了这个闭环就能明白为什么明明集群里有空闲资源服务却迟迟不在新节点上启动——多半是某个环节的判定延迟或者placement约束把候选节点排除了。2. 生产环境部署实操五节点三Manager打底高可用不是靠运气是靠一开始的架构设计。下面这套方案是我在多个环境里跑过、验证过比较稳妥的配置适合拿来做生产集群的基础模板。2.1 服务器规划与端口清单我推荐最小生产配置5台机器。其中3台做Manager2台做Worker。如果资源紧张至少也要3台——3台全部做Manager同时承担工作负载。不过那种方案只适合验证环境。先看必须放通的端口这是无数人踩过坑的地方端口用途是否必须2377/tcpSwarm管理通信集群管理必须7946/tcp, 7946/udp节点间 gossip 协议必须4789/udpoverlay 网络 VXLAN 数据面必须500/udpIPsec 加密网络--encrypted 时使用使用加密 overlay 时必须8080/tcp可选Ingress 入口或示例应用按需提示云厂商安全组、企业内部防火墙、主机自身iptables三层都要检查。我见过太多Swarm端口在安全组放开了但主机的firewalld没放行导致节点加入超时的案例。2.2 初始化Manager并接入Worker在3台Manager里任选一台作为第一台初始化以中控机或node01为例docker swarm init --advertise-addr 192.168.1.101 --listen-addr 0.0.0.0:2377输出结果里会带有两段不同的join命令分别是作为Worker加入和作为Manager加入。把Manager那段的token妥善保存。然后分别在另外两台Manager上执行docker swarm join \ --token SWMTKN-1-xxxxx-manager-token-xxxxx \ 192.168.1.101:2377在两台Worker上执行Worker的join命令把上面命令里的manager token换成worker token即可。全部加入后到任意Manager节点检查成员状态docker node ls理想状态应该类似这样ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS abc123def456 node01 Ready Active Leader def456abc789 node02 Ready Active Reachable ghi789abc012 node03 Ready Active Reachable jkl012abc345 worker01 Ready Active mno345abc678 worker02 Ready Active2.3 服务发布前的预防性配置这部分内容非常重要但很多人是在服务已经跑起来之后才补的。给Manager节点排空业务负载docker node update --availability drain node01 docker node update --availability drain node02 docker node update --availability drain node03这样业务容器只会在Worker上调度Manager只负责管状态。要业务容器想要固定在奇数个Worker那是另一套玩法看具体负载类型。给节点打标签用于placement约束docker node update --label-add roleworker worker01 docker node update --label-add roleworker worker02 docker node update --label-add zoneaz1 worker01 docker node update --label-add zoneaz2 worker02发布服务时就能精确管控调度位置比如只允许跑在worker上、且分散在不同可用区docker service create \ --name web \ --replicas 2 \ --constraint node.roleworker \ --constraint node.labels.zone!worker01 \ nginx:latest关闭Manager上的iptables自动改规则那倒不必。但要注意Swarm在初始化时会自动修改iptables如果你管理的主机上有自己维护的防火墙策略建议先把规则备份好。Swarm的ingress网络会占用一个服务发布端口默认是docker_gwbridge网段这些都在docker network ls里能看到。确认网络没问题再继续。3. 故障恢复全流程演练真挂一台会怎样配置做完接下来进入本篇的重头戏故障恢复。这里不像教科书那样只告诉你要备份我直接按故障场景来每个场景都给出完整的排查链路和恢复命令。3.1 Manager节点故障从仲裁降级到恢复场景设定3个Manager、2个Worker的集群node01作为Leader突然宕机。故障现象客户端执行docker service ls还能正常看到服务因为请求被其他Manager节点接管了。如果客户端只连node01的IPAPI请求会直接失败。所以生产环境一定要把Manager集群IP挂到负载均衡器后面客户端连VIP而不是单个节点。docker node ls在幸存的Manager节点上执行能看到node01状态变更为Down。为什么集群还在工作3个Manager宕机1台还剩2台2 3/2 1.5仲裁仍然存在所以集群继续提供调度能力。恢复步骤把node01拉回集群物理可用状态修复机器、重启服务。如果node01只是暂时失联原样恢复后docker node ls会自动变回Ready。如果node01永久报废新开一台机器拿到Manager token重新加入docker swarm join \ --token SWMTKN-1-xxxxx-manager-token-xxxxx \ 192.168.1.102:2377关键的排查链路判断集群是否健康不要只看docker node ls里节点数量要看MANAGER STATUS列里Leader是否存在。如果一整列都是空值说明仲裁已经挂了只能按下面3.3节的方式处理。3.2 Worker节点故障任务自动重调度场景设定worker01物理机宕机worker01上有两个运行中的服务任务。故障现象docker service ps service会看到worker01上的任务状态从Running变成Shutdown或Failed状态取决于具体原因同时在另一个节点上出现New task并逐渐进入Running。这中间通常有30秒到几分钟的窗口期取决于心跳超时和服务配置。恢复步骤如果worker01是临时故障恢复后节点自动回到Ready状态Swarm不会自动把任务迁回去仍然会留在新节点上跑。除非你在service update里重新调整约束。如果worker01需要下线退役先确认上面的任务都已经在其他节点正常运作了然后docker node demote worker01 # 如果它是Worker这步可省略如果是Manager这里先降级 docker node rm worker01等等docker node demote是从管理节点降级为Worker对纯Worker节点不需要。直接docker node rm worker01如果docker node rm报错提示节点不是Down状态先执行docker node update --availability drain worker01再docker node rm --force worker01。这里有个容易忽略的细节Worker节点宕机后新任务被调度到了其他节点但旧节点的数据卷并不会跟着过去。如果你的服务写入了本地volume新任务是一个全新的空目录。所以有状态服务一定不要指望Swarm默认帮你做数据迁移后面第4.2节专门聊这个。3.3 极端场景全部Manager同时丢失场景设定3个Manager全部宕机或者机房网络分区导致Manager之间互相隔离集群失去仲裁能力。故障现象Swarm的API完全不可用docker service ls返回错误Error response from daemon: rpc error: code Unknown desc The swarm does not have a leader. It is possible that too few managers are online.Worker上的现有容器还在运行因为容器由本地containerd守护但没有任何调度和任务编排能力服务更新和扩缩容全部瘫痪。恢复步骤这是最麻烦的情况也是我最想提醒你提前准备的部分。先恢复至少一台Manager到在线状态让它能够跟其他存活的Worker通信。如果旧Manager只是暂时离线全部恢复后集群自动恢复仲裁。如果旧Manager永久丢失但还有一台Manager存活尝试把它提升为Leaderdocker node promote node-id如果存活的那台已经不在swarm状态里比如它自己也被重启过就需要用强制初始化新集群的方式恢复docker swarm init --force-new-cluster --advertise-addr 192.168.1.102注意--force-new-cluster会基于当前节点的Raft数据重新创建一个单Manager集群。它会丢掉其他的Manager和Worker但服务定义和任务历史会从旧数据里恢复。它适合只剩一台节点还活着、且数据完整的场景。集群恢复后把丢失的Worker节点重新加入。注意旧Worker上的服务任务由于和旧集群失去联系很可能被标记为异常最好的办法是docker node rm后重新join或者直接让它们在存活节点上重建。复盘建议全部Manager丢失是最恐怖的故障它的恢复代价本身就很高。我在实际工作中几乎没遇到过全部Manager同时宕机的但遇到过Manager数据盘损坏导致Raft日志丢失的情况那比宕机更致命——节点活着但Swarm数据没了。所以Manager节点的数据盘一定要做快照/备份备份路径是/var/lib/docker/swarm这是全部Raft日志和集群元数据所在。4. 服务层面的高可用设计让业务自己扛故障集群再稳服务设计拉胯也一样宕。Swarm能帮业务做的是调度和自愈但业务应用本身要有足够的健康检查和优雅退出机制。这一节讲服务发布阶段如何打好底子。4.1 副本数、健康检查与滚动更新最小副本数生产服务至少2个副本这是最简单的可用性保障。但副本数超过2时要注意一点Swarm默认把任务尽量分散在不同节点。如果你没有指定placement约束Swarm在调度时会优先往副本少、负载轻的节点上放。健康检查health check健康检查直接影响任务是否被判定为异常。以Web服务为例docker service create \ --name web \ --replicas 3 \ --health-cmd curl -f http://localhost/health || exit 1 \ --health-interval 10s \ --health-timeout 5s \ --health-retries 3 \ --restart-condition any \ -p 8080:80 \ nginx:stable这里健康检查时间间隔10秒、超时5秒、连续失败3次判定为不健康。也就是说应用真正被判定为故障需要约1530秒实际算法是interval*retries附近。如果你的服务本身启动需要30秒而这个健康检查在启动阶段就failSwarm会杀掉任务重启——所以健康检查命令要在服务稳定运行后才应该返回成功。滚动更新更新服务镜像时Swarm默认是一次更新一个任务并等待新任务变成Running。用--update-parallelism 2 --update-delay 10s控制并发数和间隔防止一次性把所有副本都换掉导致业务窗口期。同理回滚用docker service rollback。4.2 数据存储与有状态服务的HA思路Swarm对有状态服务支持比较弱这是它的痛点也是选型时必须想清楚的。Swarm内置的volume类型是local数据存在节点本地任务被重调度后数据不跟随所以本地volume只能用于无状态服务或缓存类数据。生产环境的数据库、中间件这类有状态服务要么不用Swarm调度单独跑在集群外的机器上要么挂共享存储NFS、Ceph RBD、云盘等。挂载共享存储的示例docker service create \ --name mysql \ --replicas 1 \ --mount typevolume,srcmysql-data,dst/var/lib/mysql,volume-driverlocal,volume-opttypenfs,volume-optdevice:/data/mysql,volume-optoaddr192.168.1.200 \ -e MYSQL_ROOT_PASSWORDsomething \ mysql:8当然--replicas 1的MySQL本身就没有HA这里只是说明如何让数据不随容器漂移。真正的数据库高可用还是要靠数据库自身的主从复制、半同步机制而不是靠Swarm。我的经验Swarm适合跑无状态集群比如Web前端、API网关、消息消费者这类。数据库、Redis这类有状态的要么独立部署做HA要么用K8s的有状态集StatefulSet方案。这并不是Swarm的缺陷而是它的设计定位决定的。4.3 网络层面的高可用细节Swarm的overlay网络默认不带加密如果跨公网或跨不可信网络部署必须开启加密docker network create --driver overlay --opt encryptedtrue --attachable my-overlay注意加密overlay的IPsec端口是500/udp别忘了在防火墙上放行。另外一个和HA直接相关的点Ingress入口网络。Swarm的-p发布端口是靠Ingress网络实现的任务可以在任何节点上但通过任何一个节点的发布端口都能访问到服务。这意味着Swarm集群本身就提供了一个简单的负载均衡入口。如果你想在Swarm前面再加一层负载均衡注意不要把发布端口绑定到具体节点上而是让LB轮询所有节点的发布端口。5. 监控、数据备份与日常运维细节高可用是运维出来的不是搭建出来的。最后这部分我把自己在实战中总结的监控指标、备份策略和一些坑分享给大家。5.1 该盯哪几个指标集群层面的指标docker node ls里所有节点的STATUS是否为Ready、AVAILABILITY是否为Active。MANAGER STATUS里始终存在Leader且Manager数量始终是奇数。Raft日志落后量在Manager节点上执行docker system events看是否有raft相关的异常日志或者开启Docker daemon debug日志观察。服务层面的指标docker service ps service里每个任务的状态特别是有没有反复退出重启。用脚本定期拉取这些指标然后接入告警。一个最简单的巡检脚本每天定时检查集群仲裁#!/bin/bash # check-swarm-health.sh LEADER$(docker node ls --format {{.ManagerStatus}} | grep -c Leader) TOTAL_MANAGERS$(docker node ls --filter rolemanager -q | wc -l) if [ $TOTAL_MANAGERS -lt 3 ]; then echo ERROR: Manager count is $TOTAL_MANAGERS exit 2 fi if [ $LEADER -lt 1 ]; then echo ERROR: Swarm has no leader exit 2 fi echo OK: Swarm healthy5.2 定期演练清单不能光搭不练。至少每季度做一次故障演练建议按下面的清单来停掉一台Worker节点上的docker服务确认任务迁移。停掉一台Manager节点确认Leader切换、API连续可用客户端连的是负载均衡VIP。模拟网络分区用iptables或云安全组限制两台Manager之间的通信确认集群不会出现双Leader。日常巡检时把Manager节点的数据盘快照做一次恢复演练确保备份真的能用。演练过程中观察到的响应时间、日志报错全都记录归档这对下一次故障处理帮助极大。5.3 我踩过的几个坑坑一Manager节点时钟不同步。Raft协议对时间偏移很敏感。NTP同步必须做好否则Manager之间可能出现不必要的leader切换甚至永久丢仲裁。加一行定时任务0 * * * * /usr/sbin/ntpdate time.cloudflare.com更推荐的做法是直接部署chronyd并配置好conf源。坑二Swarm初始化时的默认网段冲突。Swarm的docker_gwbridge默认网段是172.18.0.0/16如果你VPC里的其他服务恰好用了172.18网段等到服务发布时发现容器无法访问VPC资源排查到怀疑人生。方案是初始化时自定义网段docker swarm init \ --advertise-addr 192.168.1.101 \ --data-path-port 4789对默认bridge网段冲突需要删除现有bridge并重建指定网段在加入集群之前就必须调整好。初始化时也可以直接指定docker swarm init --advertise-addr 192.168.1.101然后优先确认docker network inspect docker_gwbridge的IPAM取值如果冲突就把bridge删掉重建docker network rm docker_gwbridge docker network create \ --subnet 172.20.0.0/16 \ --gateway 172.20.0.1 \ --opt com.docker.network.bridge.namedocker_gwbridge \ --opt com.docker.network.bridge.enable_iccfalse \ --opt com.docker.network.bridge.enable_ip_masqueradetrue \ docker_gwbridge坑三镜像tag生产环境用latest。Swarm在任务重建时如果用latest且镜像在节点上已经存在默认不会重新拉取这样会导致不一样的新节点拉到了新镜像、旧节点还跑着旧镜像的情况故障转移后行为不一致。老老实实用不可变tag版本号commit哈希需要更新时滚动upgrade。坑四任务绑定特定节点导致调度死锁。如果你的服务做了--constraint node.idxxxx且该节点down了任务永远不会被调度到其他节点。这种绑定只能用于特殊场景比如背后依赖本地硬件平时尽量用标签约束副本级别调度。写到这里正好把Swarm HA部署和故障恢复这条线完整捋了一遍。从Raft仲裁原理、生产环境部署、到各角色故障的恢复路径再到服务和监控层面的预防一整套链路基本齐了。最后分享一个小经验高可用集群的运维节奏一定是平时多演练、多压测故障后做复盘、做改进——你真的去模拟了Manager挂掉、Worker挂掉、网络分区才会对Swarm的行为有体感才知道告警规则应该怎么调。别等老板半夜给你打电话才后悔。