☰
Swarm Worker节点深度解析:网络、调度与运维实践
2026/10/14 4:33:49 网站建设 项目流程

我们常聊 Swarm 管理节点、聊 Raft 共识、聊调度策略,但真正让业务流量落地的,是集群里默默干活的 Worker 节点。这个系列做到第 3 篇,我把标题定为“Swarm Worker-补充”,不是想重复官网上那些基础概念,而是想把 Worker 在架构里被一笔带过的部分挖出来补完整——它如何接入网络、流量如何经过它转发、调度器按什么逻辑把任务塞给它、节点出问题时你又该怎么安全摘除和替换。

这篇内容主要面向两类人:一类是刚把 Swarm 集群搭起来、想搞明白 Worker 在整体架构里承担什么职责的初学者;另一类是已经在生产环境维护 Swarm、正准备规划扩容或排查 Worker 故障的实操者。我会把 Worker 相关的配置命令、流量路径、调度原理、运维手法和踩坑经验都过一遍,内容会偏工程实践,同时尽量讲清楚背后的“为什么”,因为很多问题排查到最后,拼的就是你对这些机制的准确理解。

1. 为什么架构里要单独划分出 Worker 这个角色

1.1 Worker 与 Manager 的分工边界

Swarm 集群里的节点默认分为两类角色:Manager 和 Worker。Manager 负责集群状态管理、任务调度、API 入口,以及维护 Raft 共识日志;Worker 则只负责一件事——接收 Manager 下发的任务,创建并运行对应的容器,再把任务状态反馈回去。从 Docker 官方文档的表述来看,Worker 节点参与的是 Swarm 的服务执行面,它不参与 Raft 共识,也不参与调度决策。

但很多人会忽略一个细节:Manager 节点本身也可以当作 Worker 来运行任务。默认情况下,Manager 节点同样拥有可调度属性,也就是说 Swarm 可以把服务副本调度到 Manager 上。只有当 Manager 的 Availability 被设置为 Drain 时,它才会停止接收新任务。所以在实际生产环境里,常见做法是让 Manager 只承担控制面职责,把所有业务负载放在专用 Worker 节点上。这样做的本质是把控制平面和数据平面隔离开,避免业务容器占用管理节点资源后,影响集群调度和状态同步的稳定性。

这个设计和 Kubernetes 里控制面与数据面分离的思路很像,只是 Swarm 的实现更轻量,它没有像 kubelet 那样复杂的组件分层,而是通过 Engine 内置的 Swarm 模块来区分角色。对于中小规模的集群,这种内聚设计反而带来了部署简单的优势——不需要额外安装 Agent,也不需要独立部署控制面组件,一条docker swarm init就能把管理面拉起来。

1.2 单独划分 Worker 的三个实际收益

第一个收益是故障域的隔离。如果把一批节点设为 Worker,Manager 节点出现故障时,Worker 上运行的服务不会因为控制面短暂不可用而自动停止。任务执行是独立的,容器生命周期由本地 Docker Engine 管理,Manager 挂了只会导致新的调度决策暂时无法下发,存量业务不会立刻中断。

第二个收益是资源规划的边界清晰。把 Worker 与管理节点分开,可以针对性地做资源预留。例如给 Manager 节点预留足够的 CPU 和内存来支撑 API 请求、Raft 心跳和调度计算,给 Worker 节点则按业务容器的资源需求做规格选型。没有角色划分的集群,容器混部很容易导致管理面资源被业务挤占,造成调度变慢、心跳超时,甚至集群脑裂的一系列连锁反应。

第三个收益是维护操作更安全。当你需要对 Worker 做内核升级、硬件更换、网络维护时,可以先把节点置为 Drain 并驱逐任务,再执行操作。而 Manager 节点数量本身就受 Raft 奇数规则约束,维护窗口和风险控制比 Worker 复杂得多。角色分离以后,你可以在最小化控制面影响的前提下,灵活调整业务承载面的规模。

1.3 角色变更的灵活性与边界

Swarm 支持节点角色动态调整,你可以用docker node promote把一个 Worker 提升为 Manager,也可以用docker node demote把 Manager 降级为 Worker。这个灵活性很实用,比如你临时需要一个高可用管理面,就可以把某个备用 Worker 提升上来;反过来,某个老旧的 Manager 需要退役,也可以先降级再安全移除。

但角色变更不是无代价的。Manager 数量的变化会直接影响 Raft 共识的安全阈值。Swarm 使用 Raft 协议保证集群状态一致,存活 Manager 必须超过半数才能正常作出决策。一个 Worker 被提升为 Manager 后,它就开始参与日志复制和选举投票,这意味着集群里任何一次 Manager 节点的变更,都需要你重新计算可用容错数。比如原来 3 个 Manager 最多允许挂 1 个,变成 5 个 Manager 后允许挂 2 个,这个变化会直接影响故障转移的容错空间。

2. Worker 侧的网络模型与业务流量走向

2.1 Overlay 网络与 VXLAN 数据面的关系

Swarm 模式下创建 Overlay 网络,本质上是为跨节点的容器通信搭建了一张虚拟二层网络。这张网络默认基于 VXLAN 技术实现,数据包会被封装在 UDP 报文里,通过宿主机的物理网络传输。Worker 节点在这张网络里承担的角色,是 VXLAN 的 VTEP 端点之一,每个 Worker 都会为 Overlay 网络维护一个本地的转发平面。

很多朋友看 Overlay 网络觉得抽象,我用一个生活化的类比来解释:可以把网络数据报看作是快递包裹,容器是包裹的收寄人,Worker 节点是小区里的快递驿站。不同节点上的容器要通信,快递先发到各自小区的驿站,驿站负责把包裹按小区编号重新封装,再通过城市主干道送到对方小区驿站,最后由驿站转交给收件人。VXLAN 在这里就是那套跨小区封装的规则,VTEP 就是驿站的处理柜台,每个 Worker 上的 Overlay 网络都会创建对应的虚拟网卡接口(通常是 docker_gwbridge 之外的另一个 bridge 域)。

创建 Overlay 网络时,Swarm 会在所有 Worker 节点上同步网络定义,但只有在节点上有容器接入该网络时,才会真正创建对应的网络命名空间和端口。这也是为什么你在一台新建 Worker 上执行docker network ls时,看不到集群里某个 Overlay 网络——直到有任务被调度到这台节点上,网络接口才会初始化。

2.2 Ingress Routing Mesh 在 Worker 上的流转过程

Swarm 的 Routing Mesh(路由网格)是容易被误解的机制之一。我遇到过不少同事以为服务端口只在运行实例的节点上暴露,实际上 Swarm 的做法是:当创建一个发布端口(Publish Port)的服务时,集群内所有 Worker 和 Manager 节点都会监听这个端口,无论该节点上是否跑着对应服务的容器。

流量到达任意一个 Worker 节点后,会先经过宿主机的 iptables DNAT 规则,把目标地址从宿主机 IP 和发布端口改写为服务对应的 VIP 和内部服务端口。这个 VIP 是 Swarm 为每个服务分配的虚拟 IP,通过内核 IPVS 规则在所有节点上做负载均衡,最终把请求转发到某个具体的容器副本所在节点的 VIP 地址。也就是说,Worker 不仅运行容器,还承担了服务入口负载均衡器的角色。

这里有个实际案例:假设你有一个全局模式的监控服务,副本数为 3,但集群里有 5 个 Worker。外部请求只要打到其中任意一个 Worker 的 8080 端口,都会被 Routing Mesh 转发到这个服务在集群内部的某个 VIP 上,再由 IPVS 挑选一个健康的容器副本处理。哪怕目标副本不在当前节点,数据包也会通过 Overlay 网络跨节点转发过去。所以你在任意节点上执行ss -lntp,都能看到服务发布端口的监听状态。

2.3 端口规划与三个常见问题

端口规划直接决定 Worker 之间数据面是否稳定。Swarm 集群需要开放的端口主要包括:TCP 2377 用于集群管理指令,TCP/UDP 7946 用于节点间通信,UDP 4789 用于 VXLAN Overlay 网络。Worker 节点理论上只需要开放 7946 和 4789,但在实际部署中我建议把 2377 也放通,因为 Worker 也会作为 API 请求的转发目标,而且你可能随时需要执行docker node promote把 Worker 提升为 Manager,到时再开端口反而容易出现配置遗漏。

常见问题有三个:

第一个是防火墙只放行了 TCP 端口,漏了 UDP 4789。Overlay 网络的 VXLAN 封装依赖 UDP 通信,漏掉就会导致跨节点容器网络不通,但单节点内部容器通信正常。排查时非常容易误判为业务问题,实际上就是宿主机间数据面断了。

第二个问题是同一集群节点间时钟偏差过大。VXLAN 封装本身不强制时钟同步,但 Swarm 的证书机制和 Gossip 协议对时间敏感,节点间时钟偏差会导致 TLS 证书校验失败和 gossip 信息不同步。生产环境里我会要求所有 Worker 统一走同一套时间同步源,偏差控制在 1 秒以内。

第三个问题是服务发布端口与宿主机其它服务端口冲突。Swarm 在创建服务时如果发现端口冲突,会报错并拒绝创建,但如果你修改了宿主机上已有服务的监听端口,Swarm 不会实时感知。结果就是请求被宿主机本地的其它进程截获,表现为服务间歇性不可达。遇到这类问题,优先检查节点上有没有非 Docker 进程占用了相同的监听端口。

3. 调度器如何挑选 Worker 并部署任务

3.1 调度决策链路

当我们执行docker service create --replicas 3 --name demo nginx:latest时,命令会先打到 Manager 节点,Manager 把服务定义写入 Raft 日志,然后由内置调度器为每个副本选择目标节点。调度器的决策依据主要有三类:节点可用性、资源满足度、约束条件。

节点可用性由节点的 Availability 状态决定。Active 状态的节点可以接收新任务,Pause 状态的节点不会接收新任务但已有任务继续运行,Drain 状态的节点既不会接收新任务,还会把已有任务逐渐迁移到其它可用节点上。这个状态设计非常实用,尤其做节点维护时可以先把 Availability 改为 Drain,让 Swarm 帮忙做优雅驱逐,而不是直接强制停止节点上的容器。

资源满足度是指 Swarm 在调度时会参考容器声明的 CPU、内存预留(Reserve)和最大限制(Limit)。这一点和 Docker Compose 里的资源限制写法很相似,但在 Swarm 里如果没有显式声明,调度器会认为容器没有资源请求,因此可能把大量副本塞到同一台 Worker 上,造成实际运行时资源争抢。

约束条件则包括节点标签(Node Label)、服务角色(Role)、节点 ID 等。你可以通过docker node update --label-add给 Worker 打标签,然后在服务创建时用--constraint限制副本必须落在带有某个标签的节点上。这是 Swarm 里实现亲和性部署最直接的手段。

3.2 一个实际调度例子的完整拆解

我在运维一个多可用区业务时,会通过节点标签把 Worker 按区域划分。假设有 6 台 Worker,3 台标记zone=a,3 台标记zone=b,部署服务时执行:

docker service create \ --name web \ --replicas 6 \ --constraint 'node.labels.zone == a' \ --constraint 'node.labels.usage == web' \ --publish published=8080,target=80 \ nginx:latest

这条命令把 6 个副本全部约束到 zone=a 且 usage=web 的节点上。不加资源声明时,调度器会尽可能把副本分散到不同节点,也就是所谓的 Spread 策略。这个策略默认存在,目的是提高容错性:如果一个节点宕机,只有该节点上的副本受影响,不会引起多个副本同时丢失。

但你要明白,Swarm 的调度器不是强分布式的。它的过滤和优选过程在 Manager 端完成,Manager 会把节点按照资源余量和当前副本分布排序,然后逐个为副本挑选目标节点。实际运维中我见过一种情况:执行 scale 扩容后,新副本几乎全部堆在同一台 Worker 上,原因是这台 Worker 的资源剩余量显著高于其他节点,并且之前没有声明资源请求。这个问题不是 Swarm 的 bug,而是资源声明缺失导致调度器认为它的“容量”无限大。

3.3 Worker 节点的资源宣告与监控指标

让 Worker 的资源被调度器准确感知,最直接的手段是在服务定义里写清楚--reserve-memory和--reserve-cpu。例如:

docker service create \ --name api \ --replicas 4 \ --reserve-memory 256m \ --reserve-cpu 0.25 \ --limit-memory 512m \ --limit-cpu 0.5 \ your-registry.api:latest

这里--reserve-memory告诉调度器容器最少需要 256MB 内存,--limit-memory表示运行时最多使用 512MB。调度器在排布副本时,会基于节点总资源减去已保留资源得到剩余资源,然后判断能否容纳新的保留值。注意,保留值和限制值不等同于 cgroup 的 quota,它更多影响的是调度决策,而限制值会被转换为容器运行时的资源约束。所以生产环境里我会同时设置这两个参数,既能合理分布副本,又能防止某个容器把 Worker 节点的内存耗尽。

另外,我建议在每个 Worker 上起一个轻量级的系统监控容器,采集 CPU、内存、磁盘吞吐、网络带宽和句柄数。Swarm 本身自带docker node ls和docker service ps,但这些都是集群状态层面的视图,看不到节点内核级指标。把监控维度分开:Swarm 看任务分布和健康状态,外部监控看节点资源水位。排查问题时,两套数据互相印证会快很多。

4. Worker 节点维护与扩缩容实践

4.1 把新 Worker 接入集群的正确流程

新 Worker 接入 Swarm 集群的标准动作非常简单:在 Manager 上执行docker swarm join-token worker获取加入命令,然后在 Worker 节点上执行docker swarm join。看起来一行命令就能搞定,但我建议在做生产级扩容时加上几个前置检查。

先确认新 Worker 的 Docker 版本与集群内现有节点保持一致。Swarm 模式对版本差异容忍度有限,尤其是跨大版本的节点,可能会出现节点间 TLS 握手失败或 Raft 日志同步异常。我的习惯是先在测试环境验证版本兼容性,再上线生产集群。

再检查防火墙和端口。前面提过 2377/7946/4789 三条链路都要通,另外还要确保 Worker 能访问到 Manager 的 API 端口。如果 Manager 配置了 TLS 证书认证,还要检查 Worker 加入时使用的 Token 是否有效。Token 有默认有效期,过期后需要重新获取。

执行加入命令后,在 Manager 上通过docker node ls确认节点状态。这里要注意 STATUS 显示为 Ready 只代表节点与 Manager 的通信正常,真正影响调度的是 AVAILABILITY 列。新加入的 Worker 默认 Availability 是 Active,可以立即接收任务。如果想先观察节点状态再让它承载业务,可以先用docker node update --availability drain把该节点置为不可调度,等确认一切正常后再恢复 Active。

4.2 摘除、晋升与驱逐的实操姿势

维护 Worker 节点时,最安全的操作路径是:先标记为不可调度、再等待任务迁移完成、然后下线节点。具体命令如下:

# 在 Manager 上执行 docker node update --availability drain worker-01

执行后,Swarm 调度器会将该节点上所有任务重新调度到其它 Active 节点。对于有状态服务,这个过程会先触发旧任务停止、再在新节点启动新任务,中间存在短暂停机窗口。如果你希望尽量缩短中断时间,可以在服务创建时增加--update-order start-first,让新副本先启动并健康检查通过后,再停止旧副本。这个参数在 Worker 下线维护时特别有用。

节点上的任务全部迁移完成后,可以在 Worker 本机执行docker swarm leave,也可以直接在 Manager 上执行docker node rm --force。我通常会先在 Worker 上调用docker swarm leave,再在 Manager 上删除节点记录,这样可以避免 Manager 端残留过期的节点状态。

把 Worker 晋升为 Manager 的操作是:

docker node promote worker-01

晋升后,需要留意该节点是否已经更新了防火墙规则。Worker 原本不要求开放 2377 端口,晋升后它要参与 Raft 通信,如果 2377 被防火墙拦截,会导致 Manager 之间无法正常同步,严重情况下会触发新的选举。所以你在做角色晋升前,一定要确保所有计划成为 Manager 的节点都放通了完整的集群通信端口。

4.3 批量替换 Worker 的操作顺序

生产环境里常常会遇到机房整批替换硬件的情况。假设你有 6 台旧 Worker 需要替换为 6 台新 Worker,最优的做法不是一台一台直接删掉旧的再建新的,而是先把新节点全部加入集群,再逐步对旧节点做 Drain 和删除。

我建议整个替换流程分为三个阶段:

第一阶段,新节点加入后先不打业务标签,保持 Active 但通过约束条件让它们暂时不承载流量。例如服务都带node.labels.usage == legacy的约束,新节点默认不带这个标签,就不会被调度。

第二阶段,把新节点的标签补成和旧节点一致,然后对第一台旧 Worker 执行docker node update --availability drain,观察任务是否平滑迁移到新节点上。确认集群心跳、服务健康、网络吞吐都正常后,再继续处理下一台旧节点。

第三阶段,所有旧节点任务清空后,在 Manager 上执行docker node rm移除旧节点记录,再在旧节点本机执行docker system prune -a清理残留的容器和镜像。这样整个集群就完成了一次无感知的 Worker 批量替换,并且任何一步出现异常都可以通过调整标签和 Availability 快速回滚。

5. Worker 常见故障排查与避坑清单

5.1 Worker 加入集群失败

加入失败最常见的报错是Error response from daemon: rpc error: code = 7 desc = Permission denied或context deadline exceeded。前者多半是 Token 失效,后者多半是网络不通。

Token 失效的解决办法是重新生成加入 Token:

docker swarm join-token worker

输出中会自带一条完整的docker swarm join命令,复制到 Worker 上重新执行即可。需要提醒的是,生成新 Token 不会让旧 Token 立即全部失效,Docker 会同时保留新旧 Token 一段时间,但生产环境为了安全,建议在变更后执行docker swarm join-token --rotate worker让旧的 Token 作废。

网络不通的排查则要先确认 Worker 到 Manager 的三条链路是否全部放通。可以用telnet或nc分别验证 2377、7946 和 4789 的连通性。我之前遇到过一种诡异情况:2377 端口能通,但节点始终加入失败,后来发现是 Manager 节点上的 Docker 服务监听的是内网 IP,而 Worker 解析 Manager 主机名时走了公网 DNS,导致 TLS 证书里的 IP 地址不匹配。解决办法是在docker swarm join命令里显式指定 Manager 的内网 IP,并在/etc/hosts里固定主机名解析。

5.2 任务一直卡在 Preparing 或 Failed

docker service ps看到任务状态一直是Preparing,说明镜像拉取出了问题。这种情况最常见的原因是 Worker 节点无法访问镜像仓库,比如私有仓库未配置 TLS 信任、镜像地址写错、或者仓库服务临时不稳定。如果看到Failed状态,则需要用docker service ps --no-trunc查看完整报错信息。千万不要直接看docker logs,因为服务还没有成功启动,容器本身并不存在日志可看。

镜像拉取问题还有一个容易忽视的坑:Worker 节点磁盘空间不足。镜像拉取会先落盘再解压,如果根分区或者 Docker 数据目录所在分区空间不够,拉取就会反复失败。排查时可以执行df -h查看磁盘使用率,以及docker system df查看镜像和容器的空间占用。

任务卡在Preparing还可能和工作目录权限有关。如果你用了 bind mount 类型的卷,需要确保 Worker 节点上对应的宿主机目录存在且权限正确。Swarm 不会帮你自动创建宿主机目录,目录不存在时容器启动会直接报错。

5.3 证书轮换与节点重启后的 Bug 排查

Swarm 节点之间的通信使用内置的 TLS 证书,默认有效期是 90 天。距离过期时间不足 30 天时,节点的证书轮换是自动完成的。你可以通过docker system info查看Swarm: NodeID、Is Manager、ClusterID等状态里的证书信息,也可以直接去/var/lib/docker/swarm/certificates/目录看一下证书文件的时间戳。

如果证书轮换失败,常见表现是某台 Worker 在docker node ls里显示Unknown或者Down,但节点的 Docker 进程看起来是正常的。处理方法是在 Worker 节点上重启 Docker 服务,让它重新拉取证书:

systemctl restart docker

如果重启后依然无法恢复,可以试试强制重新加入集群。做法是先docker swarm leave --force,再重新获取 Token 加入。要注意的是,这个操作会丢弃这台节点曾经运行过的所有服务任务状态,所以操作前一定要确认该节点上的服务已经通过其它节点完成副本冗余。

5.4 常见问题速查表

症状可能原因快速排查命令解决方向
Worker 无法加入Token 过期 / 端口不通docker swarm join-token worker重新获取 Token,检查 2377/7946/4789
任务反复 Failed镜像拉取失败 / 磁盘满docker service ps --no-trunc检查仓库连通性,清理磁盘空间
节点 Down证书过期 / 网络抖动docker node ls重启 Docker,或强制重新加入
Overlay 网络不通UDP 4789 未放行iptables -L开放 UDP 4789,检查 VXLAN 端口
服务访问间歇失败端口冲突 / 本地进程占用ss -lntp确认宿主机端口占用并调整发布端口
副本集中在某节点缺少资源声明docker service inspect增加 reserve 参数并重建服务

这张表是排查问题的第一道抓手。实际排障时,我通常先用docker service ps和docker node ls把集群状态看一遍,再结合对应节点的journalctl -u docker定位具体错误。大多数问题并不会同时爆发,锁定状态异常的节点和服务,排查范围就能缩小很多。

6. 给新手的额外忠告与我的运维体会

我在实际维护 Swarm 集群的过程中,最深刻的感受是:Worker 节点永远不会成为集群里最亮眼的角色,但它恰恰是决定业务稳定性的底座。调度器再聪明,控制面再高可用,Worker 节点资源规划不合理、网络链路配置错误,业务照样会出问题。

第一个忠告是,千万不要在一台 Worker 上塞满所有副本。虽然 Swarm 的 Spread 策略默认会分散副本,但你如果手动指定了--constraint让所有副本都落在同一台节点上,那就等于把整个业务的可用性押在一台机器的硬件健康和网络状态上。我在生产环境里见过单台 Worker 挂了以后整个服务不可用的事故,原因就是服务约束写了node.id == xxx,把副本全锁死在了那台机器上。

第二个忠告是,Worker 的升级和重启一定要有节奏。别看 Worker 不参与 Raft,但它参与 Gossip 协议和证书轮换,大量 Worker 同时重启会造成集群通信风暴。我通常会在维护窗口内分批重启,每次只处理总节点数的四分之一,然后观察节点重新加入集群是否稳定。稳妥永远比速度重要。

第三个建议是在 Worker 上开启日志轮转。Docker 容器日志默认会无限增大,如果 Worker 节点的磁盘容量没有提前规划,日志会先占满磁盘,接着镜像拉取失败、容器启动失败,最终整个节点变成一个只会报错的“废节点”。建议在/etc/docker/daemon.json里配置log-driver为json-file,并设置max-size为10m、max-file为3,然后重启 Docker 服务生效。

第四个延伸方向是结合自动弹性能力来使用 Worker 标签。你可以在集群外部通过调用 Manager API 或定时任务,根据 Worker 的资源水位自动调整服务的副本数或节点状态。比如在业务高峰前,把一批预备 Worker 的 Availability 从 Drain 改为 Active,让调度器把副本扩展上去;高峰结束后再切回 Drain,把多余的副本回收。这样既不需要频繁增减节点,也能让 Swarm 的调度能力更贴合业务的真实波动。

Swarm Worker 这个角色看起来简单,但它牵扯到网络、调度、安全认证、存储、系统维护多个层面。把这一块理解透,你对整个 Swarm 架构的掌控力会明显上一个台阶。后面如果时间允许,我还会继续在这个系列里补充 Manager 节点选主细节、服务更新策略和存储卷在 Worker 上的挂载方案,大家可以持续关注。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询