在CentOS系统上同时使用firewalld和Docker时,经常遇到容器端口映射不生效、容器内无法访问外网、或者重启防火墙后容器网络全部断开。这类现象并不是网络配置错误,而是两个服务同时管理iptables规则导致的冲突。firewalld作为系统防火墙前端,会维护自己的规则集;而Docker为了给容器提供NAT和端口发布,会直接向iptables的nat表和filter表写入大量规则。当firewalld重新加载或重启时,会清空某些链并重建规则,Docker编写的规则可能被删除。另外,Docker默认启用iptables=true,它会绕过firewalld直接操作内核netfilter,这也会让firewalld的图形界面或命令行看不到容器的策略,造成管理混乱。下面从现象、根因、修复方案几个层面梳理。

冲突的典型表现和底层原因
用docker run启动一个带端口映射的容器后,curl宿主机IP加端口有时可以访问,有时不行;重启firewalld服务后,容器之间的DNS解析出现异常,或者宿主机无法访问容器暴露的端口。最典型的是在firewalld reload之后执行docker ps显示容器正常,但外部访问全部超时。
原因在于Docker的端口发布依赖iptables的nat表。Docker会创建DOCKER链、DOCKER-ISOLATION-STAGE-1、DOCKER-USER等链,并在PREROUTING、OUTPUT、FORWARD等链上插入跳转。firewalld同样管理filter表的INPUT、FORWARD链,以及nat表的PREROUTING、POSTROUTING链。两者没有协调机制,firewalld重启时会调用iptables-restore或nft flush,把Docker写入的规则覆盖掉。CentOS 7默认使用iptables后端,CentOS 8及以上使用nftables,但Docker在某些版本仍然以iptables-legacy模式写规则,这会导致更隐蔽的冲突。
还有一个容易被忽略的点:firewalld的默认区域public会阻止转发流量。即使Docker的NAT规则存在,如果firewalld对FORWARD链的策略是DROP,而Docker网桥接口没有被加入可信区域,容器跨网段通信也会失败。所以冲突本质上是三层问题:规则互相覆盖、转发策略默认拒绝、以及后端工具不统一。
方案一:将Docker网桥加入firewalld可信区域
最快速且不破坏Docker自动管理规则的方式,是把Docker创建的网桥接口(通常是docker0)加入firewalld的trusted区域。trusted区域允许所有流量进出,并且不会对该接口做过滤。这样即使firewalld重新加载,接口对应的zone配置仍被保留,转发流量也不会再被FORWARD链的默认策略拦截。
执行以下命令查看当前区域和接口:
# 查看默认区域 firewall-cmd --get-default-zone # 查看所有区域及绑定的接口 firewall-cmd --get-active-zones # 查看docker0接口所属区域(通常无) firewall-cmd --get-zone-of-interface=docker0
如果docker0没有所属区域,将它添加到trusted:
firewall-cmd --permanent --zone=trusted --add-interface=docker0 firewall-cmd --reload
添加后可以用 firewall-cmd --get-active-zones 确认docker0已经出现在trusted区域。这种方式的好处是Docker仍然可以自主管理iptables规则,firewalld只是对docker0接口放行,不会主动清理Docker链。但对自定义网络创建的网桥(如br-xxxx)也需要逐个添加。
如果容器使用自定义bridge网络,建议在创建网络时就规划好子网,并把对应的网桥接口一并加入trusted。批量操作可以写一个简单的循环脚本:
for i in $(ip -o link show type bridge | awk -F': ' '{print $2}' | grep '^br-'); do
firewall-cmd --permanent --zone=trusted --add-interface=$i
done
firewall-cmd --reload
这个脚本会找到所有br-开头的网桥接口并加入可信区域。注意不同发行版的ip命令输出格式可能存在差异,生产环境建议先打印接口名确认,再批量执行。
方案二:调整Docker的iptables管理行为
如果希望由firewalld完全接管防火墙策略,不再让Docker直接写iptables,可以修改Docker守护进程配置。编辑或创建 /etc/docker/daemon.json,加入以下内容:
{
"iptables": false,
"ip6tables": false
}
然后重启Docker:
systemctl restart docker
关闭Docker的iptables管理后,Docker不会再创建NAT规则,也就不会和firewalld发生覆盖冲突。但这意味着所有端口发布和容器间网络的NAT规则都需要手动使用firewall-cmd或者rich rule来添加。例如要让外部访问宿主机的8080端口并转发到容器172.17.0.2的80端口,需要手动执行:
firewall-cmd --permanent --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=172.17.0.2 firewall-cmd --reload
但是手动管理容易遗漏,且容器IP可能在重启后变化,适合对网络规则有严格审计要求的场景。如果只是偶尔使用容器,不推荐关闭iptables,因为会失去Docker很多开箱即用的网络能力。
需要注意:较新版本的Docker(20.10及以上)建议保留iptables开启,并配合firewalld的docker区域使用,而不是直接关闭。因为在Swarm或Kubernetes场景下,关闭iptables会导致服务发现异常。
方案三:使用firewalld的docker区域和direct规则
CentOS 8及以上版本的firewalld自带了一个名为docker的区域。该区域已经预设了对Docker网桥的放行策略。如果你的系统没有自动将docker0加入docker区域,可以手动执行:
firewall-cmd --permanent --zone=docker --add-interface=docker0 firewall-cmd --reload
docker区域与trusted区域类似,都允许转发和NAT。但docker区域还包含了一些针对容器网络的icmp规则,比trusted更贴合Docker场景。查看docker区域的配置可以用:
firewall-cmd --info-zone=docker
如果使用自定义网络,同样需要把br-xxx接口加入docker区域。另外,为了在firewalld重启后快速恢复Docker规则,可以在firewalld中配置direct规则,将Docker的链重新链接。不过direct规则管理复杂,不如接口区域方式直观。
对于CentOS 7的用户,可能没有docker区域,可以直接使用trusted区域。版本差异是实际运维中必须考虑的因素,先确认系统版本:cat /etc/redhat-release。
验证修复效果与持久化注意事项
完成上述任一方案后,需要做完整验证。首先启动一个测试容器并映射端口:
docker run -d --name web-test -p 8080:80 nginx:alpine
然后在宿主机上访问 curl http://127.0.0.1:8080,从外部机器访问 http://宿主机IP:8080。接着模拟故障场景,重启firewalld:
systemctl restart firewalld
再次访问端口,如果仍然连通,说明修复生效。如果还是失败,可以检查Docker规则是否完整:
iptables -t nat -L -n -v | grep -A5 DOCKER iptables -L -n -v | grep -A5 DOCKER firewall-cmd --get-active-zones
对于使用nftables后端的系统,iptables命令可能显示的是nft版本,可以用 nft list ruleset | less 查看真实规则。检查docker0或br-xxx接口是否确实位于trusted或docker区域中,且区域没有在reload后丢失。
持久化方面,使用 --permanent 参数写入的firewalld配置会保存到 /etc/firewalld/ 下,重启不会丢失。Docker的daemon.json修改同样持久。如果修改了默认区域,记得使用 firewall-cmd --set-default-zone=xxx 并加 --permanent。
最后,如果生产环境对防火墙要求较高,建议在测试机上把所有方案完整演练一遍。不同Docker版本、不同CentOS发行版的iptables/nftables后端存在差异,只有结合自己的环境才能确定最稳定的配置。一般来说,优先选择将docker网桥加入可信区域,既简单又不会破坏Docker的原生网络模型。