说实话这几年聊 Docker Swarm 的人越来越少了好像不扯两句 Kubernetes 都不好意思说自己搞容器。但你要是自己维护一个小团队、几台内网服务器或者就想在一套不用装一堆第三方组件的环境里快速把容器编排跑起来Docker Swarm 依然是那个开箱即用、心智负担最低的方案。我前前后后维护过几套 Swarm 集群从最早 docker run 一个个起容器到后来把所有核心业务都迁到 service 上中间踩过的坑基本都逃不开生命周期管理里最常见的那几关节点初始化、服务发布、配置轮转、数据卷、故障恢复、备份升级。这篇文章就把 10 个我自己反复用、也反复教别人用的精要实践范例整理出来从集群创建一直讲到退役适合正在用 Docker Compose、想往编排上走一步的小团队运维也适合已经上了 Swarm 但想系统补一遍操作细节的同学。1. 底子打不好后面全是坑节点初始化与集群创建1.1 范例1节点初始化与 Docker 引擎配置很多人的 Swarm 集群是从一台台裸机或者云主机上直接apt install docker.io开始的装完就docker swarm init后面出问题才回头补配置。我的建议是任何节点在加入集群之前先把 Docker 引擎本身调好否则后面服务起不来、日志撑爆磁盘、网络忽好忽坏排查起来非常痛苦。先看 daemon.json这是 Swarm 生命周期里第一个关键文件。我常用的配置长这样cat /etc/docker/daemon.json EOF { exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 }, labels: [usageprod, regioncn-east], live-restore: true } EOF systemctl restart docker这里每个字段都不是随手写的。cgroupdriver用 systemd 是因为现代 Linux 发行版都跑 systemd保持驱动一致能避免资源统计对不上日志驱动限制max-size10m、max-file3是为了防止容器把 / 写满这个坑我踩过太多次了一个服务疯狂打日志两天就把磁盘干满整个集群全部受影响。上面这组配置意味着每个容器最多保留 30MB 本地日志量大的服务记得另接日志采集。labels是给节点打固定标签的后面做任务调度约束要用。live-restore最容易被忽略它允许 Docker daemon 重启时节点上正在跑的容器不跟着一起挂掉——集群升级、改配置文件时全靠它兜底。网络层面Swarm 需要固定放行几个端口如果节点之间有防火墙漏掉一个就会导致集群初始化成功但任务调度异常。核心端口如下端口协议用途2377TCP集群管理通信、manager 间 Raft 同步7946TCP/UDP节点间心跳与 gossip4789UDPoverlay 网络 VXLAN 封装还有一个很多人忽略的点不要轻易关闭 Docker 自己维护的 iptables 规则。有些安全加固脚本会顺手把 FORWARD 链设为 DROP容器间的 overlay 网络立刻就不通。如果你确实需要自己管理防火墙务必在改动前后用docker service create --publish 端口 服务名起一个测试服务验证一下再跑业务迁移。1.2 范例2集群初始化与节点加入引擎配置好之后第一次初始化集群要做的决定其实比你想的多。执行 init 的时候--advertise-addr是我最推荐的参数尤其是多网卡机器docker swarm init \ --advertise-addr 10.0.0.11 \ --task-history-limit 5为什么一定写--advertise-addr因为节点上有多个 IP 的时候Swarm 会猜一个一旦猜错比如选了 Doker 内部虚拟网卡的地址其他节点 join 进来之后 overlay 网络就是通的但 manager 之间的 Raft 心跳会莫名其妙中断。提前把业务网段的 IP 写清楚省掉后面一大轮排查。初始化完拿到 join 命令的方式也要养成习惯。别去翻终端历史记录里那串 token你应该随时能用下面的命令重新获取# 在 manager 节点上 docker swarm join-token worker docker swarm join-token manager如果你怀疑 token 泄露了或者某个节点离开后想彻底踢掉直接轮换docker swarm join-token --rotate worker节点 join 进来之后docker node ls能看到整体状态。一个新手常犯的错误是不管 manager 数量觉得两台机器每台都是 manager 很稳。实际上 Swarm 的 Raft 过半机制决定了奇数个 manager 最安全3 个 manager 允许同时挂 1 个5 个允许挂 2 个反而是 2 个 manager 的配置最危险挂掉任何一个都凑不够过半整个集群会进入只读甚至瘫痪状态。所以我的原则是要么 1 个 manager 保底要么 3 个 manager 起步生产环境尽量别用双 manager。初始化时还可以顺手开启 autolock。docker swarm init --autolock或者之后用docker swarm update --autolocktrue集群会把敏感数据加密重启 daemon 后需要 unlock key 才能交给 manager 相关节点。第一次用的时候大概率会不习惯总得去翻 key但安全性确实上了一个台阶。这个 key 建议打印出来放到线下密码箱里别只存在服务器上。2. 服务的创建到滚动更新参数就是命根子2.1 范例3创建服务时就把参数想清楚很多人把 Swarm 当成远程版 docker run创建服务时只写镜像名和端口后面出了问题又用 docker service update 慢慢补。实际经验是创建服务时多花两分钟把参数定全后面能省出好几个通宵。我常用的标准服务模板长这样docker service create \ --name web \ --replicas 5 \ --constraint node.labels.roleweb \ --limit-memory 1g \ --limit-cpu 0.8 \ --reserve-memory 256m \ --restart-condition any \ --update-delay 15s \ --update-parallelism 1 \ --update-order start-first \ --rollback-monitor 20s \ --health-cmd curl -f http://localhost/health || exit 1 \ --health-interval 10s \ --publish published80,target8080,modeingress \ --with-registry-auth \ nginx:1.25逐个说说关键参数。--limit-memory和--limit-cpu是上限--reserve-memory是预留这两个一起配合调度器才会合理地分配任务密度。只写 limit 不写 reserve 的后果是Swarm 会按最宽裕的情况把服务都堆到同一台机器上机器内存有 1G 它敢给你堆 10 个 1G 的任务直到 OOM 全部重启。--update-order start-first是个特别容易被忽略的细节。默认先停旧任务再起新任务短暂中断期在更新时是能实际感知到的改成 start-first 后Swarm 会先在目标节点上把新任务跑起来确认正常后再把旧的撤掉。对零停机有要求的服务这个参数比什么花哨流量切换都直接。健康检查我建议在创建时就用参数写明因为 Swarm 的滚动更新、任务调度都依赖它来判断任务是否可用没有健康检查的服务更新时只能靠发呆等超时。上面模板里写的--health-cmd是容器内执行的命令你根据自己应用实际的路由来改关键是返回码 0 才算健康。--with-registry-auth是给私有镜像仓库用的添加这个参数以后Swarm 会把节点的仓库认证信息同步给其他节点避免任务被调度到别的机器上时因为拉不下来镜像而反复重启。特别是镜像仓库开启权限认证之后忘了这个参数服务就会一直卡在 pulling 状态。最后说发布模式。modeingress是默认的流量先进 Swarm 内部的 ingress 网络再由调度器转发给对应任务好处是任何节点上的端口都能访问到整个服务modehost则是只绑定任务实际所在节点的端口适合端口冲突敏感的场景。日常业务用 ingress 就够了。2.2 范例4滚动发布与自动回滚服务上线之后日常最频繁的操作就是更新镜像。用 docker service update 之前我一般会用上一节创建服务时那组参数做一次完整更新这样行为是确定的docker service update \ --image registry.example.com/web:v2 \ --update-delay 15s \ --update-parallelism 2 \ --update-failure-action rollback \ web--update-delay的意思是每批任务更新完成后等 15 秒再更新下一批。这 15 秒不只是礼貌性等待而是给健康检查留出确认窗口让问题暴露在最小影响范围内。--update-parallelism控制每批同时更新的任务数2 表示每次动 2 个副本机器多的集群可以调大一点。--update-failure-action rollback是自动回滚的开关一旦新任务启动失败或健康检查不过Swarm 会按旧镜像把服务恢复回更新前状态。如果更新完之后你想手动回到上一版本一条命令就够了docker service rollback web回滚时有几个实际问题要提醒。第一镜像 tag 一定要保留旧的别因为都用 latest 了还要什么旧 tag这种想法把原来版本覆盖掉。我之前就干过这种事更新后发现有 bug想回滚结果镜像仓库里 v1 已经被清理了只能现场重新构建业务停了一个多小时。第二回滚操作也是滚动执行不是瞬间全部换回来所以同样会有过渡时间发布窗口期一定要预留够。第三如果创建服务时带上了健康检查更新时 Swarm 会等新任务通过状态检查后再继续下一批没有健康检查的话这个自动确认机制就不存在了。这里分享一个我踩过的大坑有一次更新时把--update-failure-action写成了continue新版本因为一个配置错误一直连接不上数据库结果服务 ps 列表里任务状态全是 running但线上流量全是 502。我盯了十分钟才反应过来是健康检查没配。所以现在的习惯是所有面向用户的 web 服务创建时必须带 healthcheck更新时 failure action 也必须写 rollback宁可在监控里多看到一次回滚记录也不要在业务不可用的状态下干着急。3. 配置、密钥和有状态服务生产环境绕不开的三座山3.1 范例5config 和 secret 的正确用法很多入门者把数据库密码、应用配置直接写进服务的环境变量里图省事。这个习惯在 Swarm 环境里要改因为docker inspect能直接把服务定义和运行时环境变量一起查出来等于把敏感信息明文暴露给任何有节点权限的人。Swarm 原生提供的 secret 机制就是为了解决这个问题。先看创建方式printf mysecretdb123 | docker secret create db_pass_20260110 -用命令行标准输入创建的好处是不会在 shell 历史记录里留下密码内容。secret 创建后在服务里引用docker service create \ --name api \ --secret sourcedb_pass_20260110,targetdb_pass,mode0400 \ --publish 8080:8080 \ registry.example.com/api:v1容器运行时secret 会以 tmpfs 文件的形式挂载到/run/secrets/db_pass应用从这里读密码而不是从环境变量里拿。mode 权限位设成 0400 是保证只有容器内 root 能读。secret 本身是不可变的创建后不能修改内容更新只能先创建新版本再换引用。所以我的命名习惯是带日期后缀db_pass_20260110。轮换流程也固定为四步创建新版本 secret、用 docker service update 把服务切到新 secret、确认业务正常、删掉旧 secret。每个 swarn 集群上我都配了一个简单的发布检查清单每次轮换按清单走出错概率直线下降。config 的用法和 secret 几乎一样区别只在内容不敏感、可以大家都看得见。比如改 nginx 配置docker config create nginx_conf_20260110 ./nginx.conf docker service create \ --name nginx \ --config sourcenginx_conf_20260110,target/etc/nginx/nginx.conf \ nginx:1.25config 同样不能原地改所以要养成配置文件名带版本号的习惯更新配置本质上是创建新 config、更新服务引用、清理旧 config。这套流程看起来比直接改挂载文件麻烦但它保证了每个任务拿到的配置版本完全一致不会出现有的节点是旧配置、有的节点是新配置的灵异现象。3.2 范例6有状态服务与数据卷Swarm 对无状态服务非常友好任务挂了就换个节点重新拉起数据在镜像里、在配置里丢了也无所谓。但数据库这类有状态服务就麻烦得多因为数据得持久化。很多人第一反应是直接挂个本地卷结果任务被调度走之后发现数据不跟着走。先看本地卷的正确写法docker service create \ --name postgres \ --constraint node.labels.dbpostgres \ --publish 5432:5432 \ --mount typevolume,srcpgdata,dst/var/lib/postgresql/data \ --env-file ./postgres.env \ postgres:14这里必须想明白一个关键机制本地卷pgdata是在任务实际运行的节点上创建的如果 Swarm 把 postgres 任务重新调度到另一台节点新节点上没有这个卷容器会带着空数据启动等于数据库瞬间被格式化了。所以有状态服务我用--constraint node.labels.dbpostgres把任务固定在这台机器上配合节点标签让调度器明白这个任务只能去那台机器。把任务固定到一个节点之后这台机器就成了整个集群里的单点所以必须配套备份策略。我一般每天凌晨做一次逻辑备份把 SQL dump 传到独立存储上。还有一个选择是 NFS 或其他共享存储这样任务调度到任何节点都能挂到同一份数据docker service create \ --name shared-storage-test \ --mount typevolume,srcnfsdata,dst/data,volume-driverlocal,\ volume-opttypenfs,volume-optdevice:/data,\ volume-optoaddr10.0.0.20,rw,nfsvers4 \ nginx:1.25NFS 方案的好处是数据不绑死在单台机器上坏处是 NFS 服务器本身又成了单点而且网络存储的 IO 稳定性直接决定数据库性能。我对 Swarm 上跑数据库的最终建议是小规模业务用节点固定 逻辑备份的简单方案规模大了就别硬用原生卷直接上云数据库或者专用的高可用方案别在 Swarm 里硬撑多副本数据库那才是真的给自己挖坑。4. 高可用、监控与排障别等翻车才想起4.1 范例7故障转移与 Raft quorum 的维护Swarm 自己会处理任务级的故障转移某个 worker 节点宕机后调度器会把这个节点上的任务重新调度到其他满足约束的健康节点上。前提是你的服务不是有状态服务前面提过有状态服务靠固定节点宕了就是宕了只能通过恢复流程救回来。维护高可用的第一习惯是保持节点 availability 状态符合预期。比如要重启某台机器先把它变成 draindocker node update --availability drain node2drain 的效果是Swarm 会把这个节点上的任务平滑疏散到其他节点同时不会再给它分配新任务。等机器维护完成再切回 activedocker node update --availability active node2我见过有人在生产环境直接重启节点完全不管 availability 状态结果节点起回来后上面的任务因为状态错乱反复重启业务时断时续。记住凡是计划内的维护都必须先 drain 再动手。manager 节点的高可用又是另一套逻辑。Swarm 的 manager 之间通过 Raft 选主和同步数据必须有过半节点在线才能继续正常工作。3 个 manager 挂 1 个没问题5 个挂 2 个也没问题但超过这个数就会导致集群进入不稳定状态所有管理命令都会超时。这里最危险的场景就是两台 manager 跑生产挂一台就只剩一台不够过半整个集群直接失联连 service ps 都查不了。如果你真的遇到了 manager 大面积宕机、quorum 丢失的情况官方提供的最后手段是在幸存节点上重新初始化docker swarm init --force-new-cluster --advertise-addr 10.0.0.11注意这条命令会以当前节点的数据重建一个单 manager 集群相当于急救模式原来的 worker 节点基本都要重新加入。它绝不是在集群还健康时用来重置的常规操作误用了等于自断双臂。我提醒团队里的成员这条命令就是消防斧只有确认 quorum 无法恢复时才允许碰。日常高可用要做的还有一件小事每季度检查一下docker node ls看看有没有节点状态变成 Down、有没有 manager 长期处于 Reachable 之外的状态。这个习惯我坚持了好几年每次检查都发现不了问题但有一次真的抓到了一台内存有故障的节点长期处于半死状态如果不是提前发现下一次滚动更新可能就出大事。4.2 范例8监控与日志收集Swarm 自带的排障命令是你最先应该用的别一上来就搭监控平台。任务起不来第一件事查任务状态和历史docker service ps web --no-trunc这个命令的输出会显示每个任务的当前状态、上一状态、是正常退出还是异常退出、在哪个节点运行、启动尝试次数。服务反复重启时从这里能看到任务是被 OOM 杀了还是被健康检查弹掉的比对着 CPU 曲线猜答案高效得多。日志查看也有专门的姿势docker service logs --tail 200 --follow web--tail限制条数--follow实时跟踪排障时够了。如果需要永久留存日志我建议把 Docker daemon 默认的 json-file 日志驱动换成 GELF 或者 Fluentd。GELF 的配置大概这样{ log-driver: gelf, log-opts: { gelf-address: udp://10.0.0.30:12201 } }这里的 10.0.0.30 就是你日志采集服务的地址。实际经验是先在少数服务上切换日志驱动测试确认日志格式没问题再大面积铺开不然日志量暴涨会直接把采集端打爆。资源监控方面如果不想上整套 Kubernetes 的监控体系用 Prometheus 加 node-exporter 就足够 Swarm 用了。node-exporter 以 global 模式部署保证每个节点都会跑一个docker service create \ --name node-exporter \ --mode global \ --publish 9100:9100 \ --mount typebind,src/proc,dst/host/proc \ --mount typebind,src/sys,dst/host/sys \ --mount typebind,src/,dst/rootfs \ --mode global \ prom/node-exporter:latest--mode global是每台节点部署一个任务的调度模式非常适合指标采集这类 agent 型工作负载。node-exporter 抓的数据主要是节点的 CPU、内存、磁盘、网络配合 Grafana 面板就能满足日常容量观察。容器层面的指标可以再加一个 cAdvisor不过对大多数小集群来说node-exporter 加 docker stats 就够了不要为了监控而监控维护监控系统本身也是成本。5. 备份、升级和退役给集群一个体面的结局5.1 范例9集群备份与恢复很多人的备份思路是定期给数据库 dump这在单机 Docker 时代够用但 Swarm 集群要备份的远不止数据库。Swarm 的集群状态——包括 manager 的 Raft 数据、节点信息、服务定义——都存放在/var/lib/docker/swarm目录下。没了它你的集群就丢了灵魂所有服务定义都要手动重建。我习惯的备份流程很简单systemctl stop docker tar -czf /backup/swarm-$(date %F).tar.gz /var/lib/docker/swarm systemctl start docker为什么先停 Docker因为正在运行的 daemon 可能在内存里有尚未落盘的 Raft 数据直接复制目录容易得到不一致的备份。在 manager 节点上短暂停 Docker 对业务的影响很小服务任务不会立即受管理命令影响但做备份时我一般选在业务低谷期并且一台一台地备份不要同时停多个 manager。服务定义本身也应该备份下来这样即使集群彻底没了至少还能按原样重建for s in $(docker service ls -q); do docker service inspect $s svc-${s}.json done恢复流程比备份复杂一点我建议在离线机器上先演练一遍不然真到故障时手忙脚乱。大致步骤是安装 Docker、停 daemon、删掉默认的/var/lib/docker/swarm、把备份的目录放回去、启动 daemon然后用docker swarm init --force-new-cluster重新收敛出一个可用集群。等集群恢复后再用 docker service create 逐个重建服务或者直接解析之前 inspect 出来的 JSON 文件重新创建。这里有三个容易翻车的细节。第一备份文件不要留在节点本地要定期同步到对象存储或另一台独立服务器上不然节点硬盘挂了等于备份全丢。第二镜像本身也要留存备份 Raft 数据不等于备份镜像层镜像仓库的镜像一旦被清理重建服务时照样拉不到。第三验证备份最好的方式不是某天真的出事了才试而是每两个月在一台新机器上跑一遍恢复流程我和团队就是靠着这种演练在真正面对一次 manager 全部宕机的故障时才没有手忙脚乱。5.2 范例10节点维护、升级与集群退役前面提过节点维护的核心操作是 drain。这里再补一个细节如果你要维护的是 manager 节点还有一个额外的工序——搞清楚谁是最新的 Raft leaderdocker node ls输出里 Leader 那一列能看清当前 leader。维护顺序最好是先动 follower再动 leader每次只动一台。为什么因为 leader 一旦 down 掉集群要重新选主Raft 数据要重新同步多台 manager 同时维护等于把整个集群踢出 quorum风险完全不可控。升级 Docker 版本也是生命周期里的固定环节。Swarm 集群升级的推荐顺序是先升级非 leader 的 manager再升级 leader最后升级 worker 节点。每个节点升级期间该节点的 daemon 会重启如果章节 1.1 里你配了 live-restore正在运行的容器不会中断。这也是我为什么反复强调 daemon.json 里那项配置的原因——很多团队升级 Docker 版本时出现业务闪断排查来排查去发现就是少了 live-restore。如果要彻底退掉一个 worker 节点流程是三步# 在 worker 节点上 docker swarm leave # 回到 manager 节点上确认节点已经消失 docker node rm worker1如果这个节点是 manager先降级再离开docker node demote node2然后在 node2 上执行docker swarm leave最后在活跃 manager 上docker node rm node2。记住不要在只剩两个 manager 的集群里随便移除一个 manager除非你做好了单 manager 运行的准备。集群退役这个事也值得认真处理。很多临时集群用完就扔节点上的任务停了但服务定义、网络、卷都还在。标准的清理顺序是# 停止所有服务 docker service ls -q | xargs docker service rm # 将节点退出集群 docker swarm leave --force # 清理残留资源和卷 docker system prune -a --volumesdocker system prune会把停用的容器、不再使用的网络、无主的镜像和数据卷全部清理掉。最后一步最容易忘的是清理主机上的 /var/lib/docker 目录和对应的防火墙规则不然重装 Docker 时总会遇到端口被占或者网络冲突的奇怪问题。集群退役不是按一下删除键就万事大吉数据备份、凭据销毁、资源回收都要按顺序来这才是善始善终。最后再分享一个我自己坚持了很久的习惯每个 Swarm 集群我都会建一个 README 文档把节点 IP、标签含义、备份位置、升级顺序、上次演练恢复的日期都写进去。别嫌麻烦等你凌晨三点被服务告警叫醒的时候会发现这些记录比任何监控面板都管用。Swarm 的难点从来不在某个高级特性而在于把节点、服务、数据、备份这些基础环节一样样做对——这才是全生命周期管理的真实含义。