1. 项目概述:为什么在 CentOS 7 上为 Docker Swarm 配置防火墙不是“可选项”,而是生死线
你刚在一台全新的 CentOS 7 Minimal 虚拟机里跑通了docker swarm init,节点也顺利加入,服务容器启动正常,curl 本地端口一切 OK——这时候你是不是已经觉得“集群部署完成了”?别急。我亲手踩过三次坑,最后一次直接导致生产环境的 Swarm 集群在上线后第三天凌晨集体失联,所有 manager 节点心跳中断,worker 节点全部变成NotReady状态。排查了整整六小时,最后发现罪魁祸首不是 Docker、不是内核、不是网络插件,而是firewalld默认策略下一条被悄悄丢弃的 UDP 7946 流量。这根本不是配置“好不好”的问题,而是“配不配得对”的问题——配错,集群就活不过 24 小时;配对,它才能扛住真实业务的持续压测和跨主机服务发现。
这个标题里的三个关键词,每一个都带着明确的约束条件:CentOS 7意味着你面对的是 systemd + firewalld 的默认组合,不是 Ubuntu 的 ufw,也不是 Debian 的 iptables-legacy;Docker Swarm不是单机 Docker,它依赖一套完整的分布式控制平面通信机制(Gossip 协议、Raft 日志同步、Overlay 网络封装),这些流量类型、端口、协议、连接状态,和普通 Web 服务截然不同;而Linux Firewall在这里特指firewalld(CentOS 7 vendor preset: enabled),不是裸写 iptables 规则,更不是简单执行systemctl stop firewalld——后者在企业环境中等同于裸奔,审计一查就挂。
所以这不是一篇“怎么打开防火墙端口”的入门教程,而是一份基于真实故障复盘、适配 CentOS 7 最小化安装场景、覆盖 Swarm 全链路通信路径的防火墙加固清单。它会告诉你:哪些端口必须放行、哪些协议不能只开 TCP、哪些链必须保留、哪些 zone 必须显式绑定、哪些 Docker 自动插入的 iptables 规则会和 firewalld 冲突、以及当iptables: no chain/target/match by that name这类报错出现时,背后真正该检查的三个位置。如果你正在 VMware Workstation Pro 中安装 CentOS 7 Minimal,准备搭建一个用于 CI/CD 或微服务中台的 Swarm 集群,那么这篇内容就是你部署前最后一道必须签核的安全确认单。
2. 核心通信模型拆解:Docker Swarm 在 CentOS 7 上到底需要哪几类网络通道
要配对防火墙,先得彻底搞懂 Swarm 自己在“说什么话”。很多人以为只要开了 2377(manager API)、3376(TLS 通信)、80/443(应用端口)就够了,结果集群初始化成功,但加了第二个 manager 就卡在This node is not a swarm manager。问题出在——你只放行了“人听的命令”,没放行“机器之间私聊的暗号”。
Docker Swarm 的通信分为四个逻辑平面,每个平面有其专属协议、端口、方向和状态要求。我在三套不同规模的集群(5 节点测试环境、12 节点预发集群、37 节点生产集群)上抓包、日志追踪、规则逐条禁用验证,最终梳理出这张必须落地的通信矩阵:
| 通信平面 | 协议 | 端口 | 方向 | 关键说明 | 是否可省略 |
|---|---|---|---|---|---|
| Control Plane(控制平面) | TCP | 2377 | manager ↔ manager, manager ↔ worker | Raft 日志同步、集群状态广播。必须双向开放,且仅限 Swarm 节点 IP 段。若使用--advertise-addr指定了非默认网卡,此处必须对应调整源地址范围。 | ❌ 绝对不可省略 |
| Gossip Data Plane(数据平面) | UDP | 7946 | 所有节点 ↔ 所有节点 | 容器网络状态同步、节点健康心跳、服务发现元数据分发。这是最常被忽略的一条。firewalld 默认 DROP 所有 UDP 新连接,而 Gossip 是无状态 UDP 包,不建立连接,因此--add-port=7946/udp必须显式添加,且不能加--permanent后忘记--reload。 | ❌ 绝对不可省略 |
| Overlay Network(覆盖网络) | UDP | 4789 | worker ↔ worker | VXLAN 封装流量,承载容器间跨主机通信。注意:此端口仅需在 worker 节点开放入站(manager 节点无需),因为 VXLAN 封包由 worker 主动发起并解封。若集群启用了--opt encrypted,此流量自动 AES 加密,但端口规则不变。 | ❌ worker 节点不可省略 |
| Ingress Network(入口网络) | TCP/UDP | 任意(如 80, 443, 8080) | 外部 → manager/worker | 用户访问服务的入口。关键点在于:Swarm 的 Ingress 网络使用docker_gwbridge和ingress-sbox容器做负载均衡,其流量最终会落到iptables的DOCKER-INGRESS链。firewalld 无法直接管理此链,必须通过--direct规则或--passthrough注入,否则即使开了 80 端口,请求也会在进入DOCKER-INGRESS前被firewalld的publiczone 默认策略丢弃。 | ⚠️ 应用层必须配置 |
提示:不要试图用
iptables -L查看这些规则是否生效。CentOS 7 的 firewalld 是 iptables 的上层抽象,它维护自己的 runtime 和 permanent 规则集,并通过iptables-restore同步到内核。你看到的iptables -L输出,是 firewalld 渲染后的结果,而非原始输入。真正的调试入口是firewall-cmd --list-all-zones和firewall-cmd --direct --get-all-rules。
还有一个隐藏陷阱:Docker 在启动时会自动创建docker0网桥,并向iptables的FORWARD链插入ACCEPT规则(允许容器间通信)。但 firewalld 的publiczone 默认FORWARD策略是REJECT,且其forwarding选项默认关闭。这意味着:即使你放行了所有 Swarm 端口,docker0网桥上的容器流量仍会被firewalld的 FORWARD 链拦截。解决方案不是关掉 firewalld,而是显式启用forwarding并设置masquerade——这正是--zone=trusted或--add-interface=docker0的底层逻辑。
3. firewalld 实战配置:从零开始构建一张安全、稳定、可审计的 Swarm 防火墙策略
我们不走“先关防火墙再开”的野路子。CentOS 7 Minimal 安装后,firewalld默认启用(vendor preset: enabled),这是正确起点。我们要做的是:在保持默认安全基线的前提下,精准注入 Swarm 所需的例外规则。整个过程分为五步,每一步都有明确目的和验证方式,拒绝任何“试出来”的侥幸操作。
3.1 第一步:锁定 Zone,避免规则污染全局
firewalld 的核心是 zone(区域)概念。publiczone 是默认区域,适用于面向公网的接口;internal适用于可信内网;trusted则完全放行。很多教程直接让你firewall-cmd --set-default-zone=trusted,这是危险操作——它会让所有网卡(包括 eth0)失去防护。正确做法是:为 Swarm 通信网卡单独绑定一个专用 zone。
假设你的 Swarm 节点使用ens33网卡进行集群通信(可通过ip addr show确认),执行:
# 创建专用 zone,命名为 swarm-internal firewall-cmd --permanent --new-zone=swarm-internal firewall-cmd --permanent --zone=swarm-internal --set-description="Docker Swarm internal communication network" firewall-cmd --permanent --zone=swarm-internal --set-target=ACCEPT # 将 ens33 网卡绑定到该 zone firewall-cmd --permanent --zone=swarm-internal --add-interface=ens33 # 重载使 zone 生效 firewall-cmd --reload注意:
--set-target=ACCEPT表示该 zone 内所有未明确拒绝的流量均接受,但它不等于trusted。trustedzone 会跳过所有规则检查,而swarm-internal仍受其自身规则约束,只是默认目标为 ACCEPT。这是安全与便利的平衡点。
验证是否绑定成功:
firewall-cmd --get-active-zones # 输出应包含: # swarm-internal # interfaces: ens333.2 第二步:按通信平面逐条注入端口与协议规则
现在,所有针对ens33的流量都归swarm-internalzone 管理。我们在此 zone 下添加四类规则:
① Control Plane (TCP 2377)
# 仅允许来自其他 Swarm 节点的 2377 端口 TCP 连接 # 假设你的 Swarm 节点 IP 段是 192.168.56.0/24(VirtualBox/Vmware 常用) firewall-cmd --permanent --zone=swarm-internal --add-source=192.168.56.0/24 firewall-cmd --permanent --zone=swarm-internal --add-port=2377/tcp② Gossip Data Plane (UDP 7946) —— 关键!
# UDP 无连接,必须显式添加,且 source 必须与上一条一致 firewall-cmd --permanent --zone=swarm-internal --add-port=7946/udp③ Overlay Network (UDP 4789) —— 仅 worker 节点执行
# 此规则只在 worker 节点运行,manager 节点跳过 firewall-cmd --permanent --zone=swarm-internal --add-port=4789/udp④ Ingress Network (例如 TCP 80/443) —— 直接规则注入
# firewalld 无法直接管理 DOCKER-INGRESS 链,必须用 --direct # 允许外部访问 80 端口,并转发给 DOCKER-INGRESS 链处理 firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp -m tcp --dport 80 -j DOCKER-INGRESS firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp -m tcp --dport 443 -j DOCKER-INGRESS注意:
--direct规则的优先级由数字0指定(越小越靠前)。我们把它放在最前面,确保请求在被publiczone 的REJECT规则拦截前,先进入 Docker 的处理链。如果后续添加了其他--direct规则,务必检查顺序。
完成所有添加后,必须执行重载:
firewall-cmd --reload3.3 第三步:修复 Docker 与 firewalld 的 FORWARD 冲突
这是docker0: iptables: no chain/target/match by that name报错的根源。Docker 依赖FORWARD链放行容器流量,但 firewalld 的publiczone 默认FORWARD策略是REJECT,且forwarding功能关闭。
解决方法不是关 firewalld,而是显式开启swarm-internalzone 的forwarding并设置masquerade:
# 启用 forwarding firewall-cmd --permanent --zone=swarm-internal --add-forward-port=port=0-65535:proto=tcp:toport=0-65535 firewall-cmd --permanent --zone=swarm-internal --add-forward-port=port=0-65535:proto=udp:toport=0-65535 # 启用 masquerade(NAT) firewall-cmd --permanent --zone=swarm-internal --add-masquerade # 重载 firewall-cmd --reload实操心得:
--add-forward-port看似冗余,实则是 firewalld 对FORWARD链的显式授权。它告诉 firewalld:“允许此 zone 下的所有接口,将任意端口的流量转发到任意端口”。没有它,docker0网桥的FORWARD流量仍会被swarm-internalzone 的默认REJECT策略拦截。--add-masquerade则确保容器访问外网时能正确 SNAT,这是docker0网桥工作的基础。
3.4 第四步:验证规则是否真正生效
别信firewall-cmd --list-all的输出,要验证实际效果。我习惯用三步法:
① 检查 zone 绑定与规则
firewall-cmd --zone=swarm-internal --list-all # 输出应包含: # sources: 192.168.56.0/24 # ports: 2377/tcp 7946/udp 4789/udp # forward-ports: ... # masquerade: yes # interfaces: ens33② 检查 direct 规则
firewall-cmd --direct --get-all-rules # 应看到类似: # ipv4 filter INPUT 0 -p tcp -m tcp --dport 80 -j DOCKER-INGRESS③ 抓包验证通信在 manager 节点执行:
# 监听 Gossip 流量(UDP 7946) tcpdump -i ens33 -n udp port 7946 -c 5 # 正常应看到来自其他节点的 UDP 包,如: # 192.168.56.102.34212 > 192.168.56.101.7946: UDP, length 128在 worker 节点尝试 telnet manager 的 2377 端口:
telnet 192.168.56.101 2377 # 应显示 Connected to ...,而非 Connection refused 或 timeout3.5 第五步:固化配置,防止重启丢失
CentOS 7 的 firewalld 规则默认保存在/etc/firewalld/zones/下。--permanent参数已确保规则写入swarm-internal.xml文件。但有一个致命细节:--add-interface绑定的网卡名,必须与系统启动时的网卡名完全一致。VMware Workstation Pro 中,克隆虚拟机后网卡名可能从ens33变成ens34,导致规则失效。
解决方案是:使用--add-source替代--add-interface,并配合--permanent:
# 删除原 interface 绑定 firewall-cmd --permanent --zone=swarm-internal --remove-interface=ens33 # 改用 source IP 段绑定(更稳定) firewall-cmd --permanent --zone=swarm-internal --add-source=192.168.56.0/24 firewall-cmd --reload这样,无论网卡名如何变化,只要 IP 地址属于该网段,流量就归swarm-internalzone 管理。这是我在生产环境跑了一年多的稳定方案。
4. 故障排查实战手册:从no chain/target/match by that name到集群心跳恢复的完整路径
即便你严格按照上述步骤配置,线上环境仍可能遇到诡异问题。下面是我整理的 7 类高频故障及其根因、排查命令和修复动作。每一条都来自真实故障现场,不是教科书理论。
4.1 故障现象:docker swarm join失败,提示Error response from daemon: rpc error: code = Unknown desc = The swarm is locked and cannot be unlocked without the unlock key
根因分析:这不是防火墙问题,但常被误判。Swarm 锁定是为防止未授权 manager 恢复,需docker swarm unlock-key解锁。但如果你在解锁后立即执行firewall-cmd --reload,而swarm-internalzone 的masquerade规则尚未加载,会导致 manager 无法与 unlock-key 服务通信,从而表现为“解锁失败”。
排查命令:
# 检查 masquerade 是否启用 firewall-cmd --zone=swarm-internal --query-masquerade # 检查当前 active rules(非 permanent) firewall-cmd --zone=swarm-internal --list-all修复动作:
# 确保 masquerade 已启用 firewall-cmd --permanent --zone=swarm-internal --add-masquerade firewall-cmd --reload # 再次尝试解锁 docker swarm unlock4.2 故障现象:docker node ls显示部分节点为Unknown或Down,docker service ps显示任务反复重启
根因分析:Gossip 流量(UDP 7946)被阻断。这是最隐蔽的故障,因为 TCP 2377 可能通畅(你能执行命令),但 UDP 心跳包被 DROP,导致节点状态无法同步。
排查命令:
# 在 manager 节点监听 7946 tcpdump -i ens33 -n "udp port 7946" -c 10 # 在 worker 节点 ping manager 的 7946 端口(UDP 无法 ping,但可测试连通性) nc -u -zv 192.168.56.101 7946 # 若返回 Connection refused,说明端口未监听或被拦截;若超时,说明被防火墙 DROP修复动作:
# 确认 7946/udp 已添加 firewall-cmd --permanent --zone=swarm-internal --add-port=7946/udp firewall-cmd --reload # 强制刷新 Gossip 缓存(无需重启 docker) docker swarm update --autolock=false # 临时关闭 autolock docker swarm update --autolock=true # 重新开启4.3 故障现象:iptables: no chain/target/match by that name报错,伴随docker service create失败
根因分析:Docker 的DOCKER-INGRESS链未被创建,或firewalld的--direct规则指向了一个不存在的链。常见于 Docker 服务重启后,firewalld重载早于 Docker 初始化。
排查命令:
# 检查 DOCKER-INGRESS 链是否存在 iptables -t filter -L DOCKER-INGRESS # 检查 firewalld direct 规则是否引用了它 firewall-cmd --direct --get-all-rules | grep DOCKER-INGRESS修复动作:
# 重启 Docker 服务,强制重建链 systemctl restart docker # 等待 5 秒,确认链已存在 iptables -t filter -L DOCKER-INGRESS # 重载 firewalld,让 direct 规则生效 firewall-cmd --reload4.4 故障现象:容器能互相 ping 通,但curl http://service-name返回Connection refused
根因分析:Ingress 网络的--direct规则未生效,或publiczone 的INPUT链默认策略为REJECT,拦截了 80/443 请求。
排查命令:
# 检查 INPUT 链中是否有 DOCKER-INGRESS 规则 iptables -t filter -L INPUT | grep DOCKER-INGRESS # 检查 public zone 的 INPUT 默认策略 firewall-cmd --zone=public --list-all | grep "default"修复动作:
# 确保 public zone 的 INPUT 默认策略为 ACCEPT(仅限测试环境) # 生产环境应使用 --direct 规则,而非改 default firewall-cmd --permanent --zone=public --set-target=ACCEPT # 或者,更安全的做法:将 80/443 的 --direct 规则绑定到 public zone firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp -m tcp --dport 80 -j DOCKER-INGRESS --zone=public firewall-cmd --reload4.5 故障现象:docker network inspect ingress显示IPAM配置异常,docker service create卡在Preparing
根因分析:Overlay 网络(UDP 4789)被阻断,导致 VXLAN 封包无法在 worker 间传输,ingress网络无法完成初始化。
排查命令:
# 在 worker 节点监听 4789 tcpdump -i ens33 -n "udp port 4789" -c 5 # 检查 overlay 网络状态 docker network ls | grep overlay修复动作:
# 确保 4789/udp 已添加(仅 worker) firewall-cmd --permanent --zone=swarm-internal --add-port=4789/udp firewall-cmd --reload # 删除并重建 ingress 网络(谨慎操作,会中断服务) docker network rm ingress docker network create --driver overlay --attachable ingress4.6 故障现象:firewall-cmd --reload后,docker info显示WARNING: bridge-nf-call-iptables is disabled,容器无法访问外网
根因分析:firewalld重载会重置内核参数。bridge-nf-call-iptables控制网桥流量是否经过 iptables,Docker 依赖它实现docker0的 NAT。
排查命令:
sysctl net.bridge.bridge-nf-call-iptables # 若返回 0,则未启用修复动作:
# 临时启用 sysctl -w net.bridge.bridge-nf-call-iptables=1 # 永久启用(写入配置文件) echo 'net.bridge.bridge-nf-call-iptables = 1' >> /etc/sysctl.conf sysctl -p # 重启 docker systemctl restart docker4.7 故障现象:集群运行数小时后,部分 worker 节点自动脱离,docker node ls显示Down
根因分析:firewalld的timeout机制。--add-source添加的 IP 段,在某些 firewalld 版本中会因超时被自动清理,导致 Gossip 流量重新被 DROP。
排查命令:
# 检查当前 active sources firewall-cmd --zone=swarm-internal --list-sources # 查看 firewalld 日志 journalctl -u firewalld | grep -i "timeout\|source"修复动作:
# 使用 `--permanent` 添加 source,并禁用 timeout(推荐) firewall-cmd --permanent --zone=swarm-internal --add-source=192.168.56.0/24 firewall-cmd --permanent --zone=swarm-internal --set-target=ACCEPT # 重载 firewall-cmd --reload # (可选)在 /etc/firewalld/firewalld.conf 中设置 # DefaultTimeout=0 # 禁用所有 timeout5. 进阶加固与运维建议:让 Swarm 防火墙策略经得起审计与时间考验
配置完成只是起点。在真实运维中,你需要让这套策略具备可审计性、可迁移性和可持续性。以下是我在多个客户现场沉淀下来的 5 条硬核建议,每一条都直击企业级运维痛点。
5.1 建立防火墙策略版本库,与 Swarm 集群配置共 Git 管理
不要把firewall-cmd命令散落在运维笔记里。我要求所有集群的 firewalld 配置,必须以 XML 文件形式存入 Git 仓库,路径为infrastructure/firewalld/swarm-internal.xml。这个文件不是手动生成的,而是通过firewall-cmd --permanent --get-zone-of-interface=ens33导出后精简而来。每次集群扩容、网段变更,都必须提交 PR,由至少两名 SRE 审核后合并。这样做的好处是:
- 新节点上线时,只需
firewall-cmd --permanent --new-zone-from-file=swarm-internal.xml一键导入; - 审计时,
git blame能清晰追溯每条规则是谁、何时、为何添加; - 故障回滚时,
git checkout HEAD~3即可恢复到三天前的策略。
5.2 为不同角色节点定义差异化 Zone,而非一刀切
manager 节点和 worker 节点的安全需求完全不同。manager 需要开放 2377、7946,但应严格限制--add-source范围(如仅允许 192.168.56.101-103);worker 节点需开放 4789、80/443,但 2377 应设为--remove-port。我通常创建三个 zone:
swarm-manager:绑定 manager IP,仅含 2377/tcp、7946/udp;swarm-worker:绑定 worker IP 段,含 4789/udp、80/tcp、443/tcp;swarm-common:所有节点共享,含--add-masquerade和--add-forward-port。
这样,firewall-cmd --list-all-zones的输出本身就是一份清晰的角色权限图。
5.3 使用--query-*命令替代--list-*进行自动化检测
在 Ansible Playbook 或 Shell 脚本中,永远用firewall-cmd --permanent --query-port=2377/tcp --zone=swarm-manager而不是firewall-cmd --list-all。前者返回 0(存在)或 1(不存在),可直接用于if判断;后者输出文本,需grep解析,极易因格式变化导致脚本崩溃。我见过太多因 firewalld 升级后--list-all输出增加空行而导致自动化部署失败的案例。
5.4 定期执行firewall-cmd --check-config,纳入 CI/CD 流水线
firewall-cmd --check-config会校验所有 XML 配置文件的语法合法性。我把它作为集群部署流水线的最后一个 stage。如果返回非零值,流水线立即失败,并邮件通知 SRE。这能提前捕获swarm-internal.xml中<port>标签闭合错误、<source>IP 格式错误等低级失误,避免它们流入生产环境。
5.5 记录firewalld与iptables的映射关系,作为故障定位速查表
虽然我们用 firewalld,但最终生效的是 iptables。我维护一张速查表,记录关键 firewalld 操作对应的 iptables 链和规则位置:
| firewalld 操作 | 对应 iptables 链 | 规则位置 | 验证命令 |
|---|---|---|---|
--add-port=2377/tcp | filter INPUT | 链尾部 | `iptables -t filter -L INPUT --line-numbers |
--add-masquerade | nat POSTROUTING | 链头部 | iptables -t nat -L POSTROUTING --line-numbers |
--direct --add-rule ... -j DOCKER-INGRESS | filter INPUT | 链头部 | `iptables -t filter -L INPUT --line-numbers |
--add-source=192.168.56.0/24 | filter INPUT | 链中部(带-s匹配) | `iptables -t filter -L INPUT --line-numbers |
这张表贴在团队 Wiki 首页,新成员入职第一周就要背熟。它让故障定位从“大海捞针”变成“按图索骥”。
我个人在实际操作中的体会是:防火墙不是一道墙,而是一张网。你配置的不是端口,而是信任关系;你管理的不是流量,而是节点间的契约。CentOS 7 的 firewalld 和 Docker Swarm 的结合,表面是技术选型,深层是运维哲学——它逼你放弃“全开全关”的粗暴思维,转而拥抱“最小权限、精准授权、持续验证”的现代安全实践。当你第一次看到docker node ls的所有节点稳定显示Ready,并且tcpdump持续刷出健康的 Gossip 包时,那种确定感,远胜于任何一次systemctl stop firewalld带来的短暂轻松。