单机 Docker 跑得好好的,为什么要上 Swarm
大多数个人站长一开始都是单机 Docker:一台 VPS,一个 compose 文件,把所有服务塞进去。这套方案简单、够用,直到遇到下面几种情况:
- 单点故障:这台机器宕机或机房抖动,网站全部下线。你开始想「要是有第二台能接管就好了」。
- 资源瓶颈:某台机器 CPU 或内存吃满,而你手里还有一台闲置的小鸡,想把负载分过去。
- 滚动更新:手动
compose up -d更新时有几十秒 downtime,你想要「先起新的再停旧的」。
一提到多机容器编排,很多人直接跳向 Kubernetes。但 K8s 对一个只跑几个网站的站长来说是过度工程——它自己的控制平面就要吃掉一个节点的资源,学习曲线陡到离谱。Docker Swarm(现在正式叫 Swarm mode)是那个被严重低估的中间答案:它是 Docker 内置的集群能力,命令风格和单机几乎一致,学习成本极低,资源占用小,足够支撑个人站点的多机高可用需求。
Swarm 的三个核心概念,用人话讲
先建立心智模型,不然光敲命令会晕:
- Node(节点):加入集群的一台机器。分两种角色——Manager(管理节点,负责调度和决策)和 Worker(工作节点,只负责跑容器)。
- Service(服务):你在 Swarm 里发布的单位。它描述「我要 3 个副本,用这个镜像,映射这个端口」。Swarm 自动把副本分布到各节点。
- Task(任务):副本的实例。一个 service 有 N 个副本,就有 N 个 task,每个 task 落在某个节点上跑成容器。
和单机的区别一句话总结:单机你管的是「容器」,Swarm 你管的是「服务」,Swarm 负责把服务铺到节点上,并在某个节点挂了之后自动把副本挪到健康节点重启。这就是高可用的来源。
前置准备:两台机器的基础条件
假设你有两台同机房的 VPS,能内网互通(这点非常重要,跨机房延迟会让 Raft 共识不稳)。检查清单:
- Docker 版本一致(28.x 及以上,避开老版本已知 bug);
- 各节点时间同步(chrony 或 systemd-timesyncd 已跑起来,时间漂移会导致节点被踢);
- 防火墙放通集群通信端口:2377/tcp(集群管理)、7946/tcp+udp(节点通信)、4789/udp(overlay 网络 VXLAN);
- 宿主机 hostname 唯一,别两台都叫 localhost。
# 每台机器上确认时间同步
timedatectl | grep "System clock"
# 确认端口没有被别的服务占用
ss -tlnp | grep -E '2377|7946'第一步:初始化集群
在第一台机器(将成为 manager)上执行:
docker swarm init --advertise-addr 内网IP--advertise-addr 一定要显式指定内网 IP。如果不指定,Docker 可能选到公网 IP,导致集群通信走公网(慢且不安全)。成功后它会打印一段 docker swarm join --token ... 的命令。
如果机器有多张网卡,命令会报「could not choose an IP address to advertise」,这时候必须显式加这个参数指定。
保存好那条 join 命令(token 可以随时用 docker swarm join-token worker 重新查看)。
第二步:把工作节点加进来
在第二台机器上执行 manager 打印的那条命令:
docker swarm join --token SWMTKN-1-xxxxx... 内网IP:2377看到 This node joined a swarm as a worker. 就成功了。回到 manager 上验证:
docker node ls你应该看到两个节点,manager 那行有个 Leader 标记,worker 那行 STATUS 是 Ready、AVAILABILITY 是 Active。
生产建议:manager 至少 3 台(Raft 需要多数派,1 台挂了就失去调度能力)。但个人站两台的情况下,把 worker 也能升成 manager 意义不大——2 节点 Raft 只要挂一台就失去多数派。所以更现实的做法是接受「单 manager、多 worker」的降级,或者干脆 3 台起步。这点要清醒,别被「集群就一定高可用」误导。
第三步:发布你的第一个服务
Swarm 里发布服务用 docker service create。以 Nginx 为例:
docker service create \
--name web \
--replicas 2 \
--publish published=80,target=80 \
nginx:1.27-alpine逐项说明:
--replicas 2:要两个副本。Swarm 会尽量把它们分到不同节点上。--publish published=80,target=80:这是 ingress 模式的端口发布,Swarm 会在每个节点的 80 端口上都监听,然后做负载均衡——无论你访问哪台节点的 80,请求都会被转发到某个副本。
查看服务状态:
docker service ls
docker service ps webservice ps 会显示每个副本落在哪个节点、是否运行中。
用 Stack 管理完整应用(推荐姿势)
一条条 service create 太原始。Swarm 支持 docker stack deploy,它读的是和 compose 几乎一样的 YAML,但注意——Swarm 的 compose 规范是 v3,很多现代 compose 特性(如 depends_on 的条件启动)不支持。这是最容易踩的坑之一。
示例 stack.yml:
version: "3.8"
services:
web:
image: typecho/typecho:latest
ports:
- "80:80"
deploy:
replicas: 2
restart_policy:
condition: on-failure
max_attempts: 5
update_config:
order: start-first
parallelism: 1
delay: 10s
volumes:
- web_data:/var/www/html
networks:
- webnet
db:
image: mariadb:11.4
environment:
MARIADB_ROOT_PASSWORD: 强密码
MARIADB_DATABASE: typecho
deploy:
replicas: 1
placement:
constraints:
- node.role == manager
volumes:
- db_data:/var/lib/mysql
networks:
- webnet
volumes:
web_data:
db_data:
networks:
webnet:
driver: overlay部署:
docker stack deploy -c stack.yml mystack几个关键点:
deploy.update_config.order: start-first:先起新副本、确认健康后再停旧的,实现零停机更新。默认是stop-first(先停后起,有 downtime)。db用placement.constraints固定到 manager 节点。数据库这种有状态服务必须固定节点 + 挂持久卷,绝不能让 Swarm 随便调度。- 服务之间通过
overlay网络互访,用服务名当主机名(web 里连数据库就写db:3306)。
滚动更新与回滚
更新镜像版本:
docker service update --image typecho/typecho:v2 web
# 或 stack 场景
docker stack deploy -c stack.yml mystackSwarm 会按 update_config 的节奏逐个替换副本。观察过程:
docker service ps web --no-trunc如果新版有问题要回滚:
docker service rollback web这就是 Swarm 相比手工更新最大的价值——内置的滚动更新和回滚能力,不用自己写脚本。
高可用验证:亲手杀掉一个节点
集群搭好了,别急着信它「高可用」。做一次实战演练:找个跑着副本的 worker,把它的 Docker 停掉模拟宕机:
# 在该节点执行
systemctl stop docker然后在 manager 上看:
docker node ls
docker service ps web你会看到该节点变成 Down,而落在它上面的副本会显示 Shutdown,Swarm 随后在健康的节点上重新拉起副本。网站应该只有极短的抖动甚至无感。这个演练非常重要——它能暴露你从未想到的坑(比如固定到 manager 的数据库副本无法迁移,或者卷数据在两个节点上不同步)。
四个必踩的坑
坑一:overlay 网络跨机不通
最常见的原因是防火墙没放通 4789/udp(VXLAN)和 7946。表现是服务起得来,但容器之间 ping 不通、连不上数据库。用 docker network inspect 看有没有节点掉出去。
坑二:有状态服务被调度到错误节点,数据丢了
如果数据库没有固定节点,Swarm 某次调度把它挪到另一台,而卷是本地卷,新节点上是个空目录——数据「消失」了。这是新手最容易丢数据的地方。解决办法:有状态服务固定节点,或用共享存储(NFS/GlusterFS)。
坑三:把最新版 compose 文件直接扔给 stack deploy
你在单机用的 compose 文件可能含 depends_on、profiles 等 Swarm 不认的字段,docker stack deploy 会直接报错或悄悄忽略。要么用带 deploy: 段的 v3 写法,要么接受 Swarm 的能力边界。
坑四:manager 单点,挂了一台就失去调度
前面强调过——manager 靠 Raft 共识,1 台 manager 挂了就直接失去管理能力(虽然已跑的服务还在跑),3 台 manager 才能容忍挂 1 台。个人站要理性评估:要么接受这个风险,要么凑 3 台。
Swarm vs 单机:我最后的选择
坦白说,对绝大多数个人站长,单机 Docker + 完善的备份 + 一台冷备机(出问题手动切 DNS)是性价比更高的方案。Swarm 适合的是这样的你:
- 有 2 台以上同机房机器,且愿意维护;
- 需要滚动更新/零停机发布,且不想上 K8s;
- 服务基本无状态,或能接受有状态服务固定节点;
- 愿意花一个周末搞懂 Raft、overlay 网络这些概念。
如果你符合这些,Swarm 会用极低的学习成本,给你一套够用的多机高可用。如果不符合,把精力花在单机的备份和监控上,回报更实在。
小结
Docker Swarm 被 K8s 的光芒盖住了,但它在「轻量多机编排」这个生态位上依然没有对手。它复用了你已经会的 Docker 命令,加上节点、服务、overlay 网络三个新概念,就能把单机 Docker 升级成能扛住单点故障的集群。记住四条底线:内网互通、时间同步、有状态服务固定节点、manager 数量按 Raft 多数派规划。做到这几点,你就能用最小代价换来「一台机器挂了网站还在」的踏实感。