1. 为什么 CentOS 8 默认只认 firewalld,而 iptables 命令却“还能用”?
刚接手一台新部署的 CentOS 8 服务器时,我习惯性敲下
iptables -L
,终端干净利落地返回了空列表——不是报错,不是权限拒绝,就是什么规则都没显示。我皱了皱眉,又试了
systemctl status iptables
,结果提示
Unit iptables.service could not be found
。这不对劲:明明
/sbin/iptables
这个二进制文件还在,
which iptables
能查到路径,
iptables --version
也能输出版本号,可服务却根本不存在。
后来翻阅 Red Hat 官方文档才彻底理清逻辑:CentOS 8(及其上游 RHEL 8)已将
iptables 工具链彻底降级为“兼容层”
,而非真正的防火墙后端。它不再管理内核 netfilter 规则表,而是通过
iptables-nft
模块,把传统 iptables 命令语法“翻译”成 nftables 的内部指令,再交由 nftables 内核子系统执行。换句话说,你敲
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
,firewalld 在后台早已通过 nftables 接口完成了等效操作;而你直接调用
iptables
命令,只是在和一个“语法翻译器”对话,它不感知 firewalld 的策略状态,也不参与 zone、service、rich rule 等高级抽象——它看到的永远是“当前生效的原始规则快照”,而这个快照,恰恰是 firewalld 动态生成并维护的。
提示:
iptables -L在 CentOS 8 上返回空,并非规则被清空,而是 firewalld 默认不启用iptables兼容视图。它实际运行的是nft list ruleset所展示的完整规则集。你可以用nft list ruleset | grep -A5 'inet firewalld'快速验证 firewalld 是否正在接管流量。
这种设计不是偷懒,而是架构演进的必然。iptables 的链式结构(INPUT/FORWARD/OUTPUT)在面对复杂策略(如基于源 IP 的动态 zone 切换、服务依赖链、富规则条件组合)时,配置维护成本指数级上升。firewalld 引入了
zone(区域)
这一语义化抽象层:
public
区域默认拒绝所有入站连接,仅开放 ssh;
trusted
区域则完全放行;
internal
区域允许 DHCP、DNS、Samba 等内网服务。管理员不再需要记忆
-A INPUT -i eth0 -s 192.168.1.0/24 -p udp --dport 53 -j ACCEPT
这类易错长命令,只需
firewall-cmd --zone=internal --add-service=dns --permanent
,firewalld 就会自动解析
dns
服务定义(位于
/usr/lib/firewalld/services/dns.xml
),将其展开为对应端口、协议、辅助模块(如 conntrack)的完整 nftables 规则,并持久化到磁盘。
我第一次在生产环境误用
iptables -F
清空规则后,发现 SSH 连接瞬间中断——不是因为规则被删,而是 firewalld 检测到底层规则集与自身内存状态不一致,触发了自动保护机制,强制重载其缓存策略,而该策略恰好未包含当时临时添加的调试规则。这个“意外断连”让我彻底放弃直接操作 iptables,转而拥抱 firewalld 的声明式管理范式:你告诉它“我要什么”,而不是“怎么写规则”。
2. firewalld 的核心骨架:zones、services、ports 三者如何协同工作?
firewalld 的配置模型不是扁平的规则列表,而是一个三层嵌套的语义网络。理解这三层的职责边界与联动逻辑,是避免配置冲突、实现精准控制的前提。我把它比作一家餐厅的运营体系: zone 是餐厅的营业区域划分(大厅/包间/露台),service 是预设的菜单套餐(川菜套餐/粤菜套餐),port 则是单点的某道菜(麻婆豆腐) 。你不能直接对“露台区域”说“上麻婆豆腐”,而必须先确认露台是否提供川菜套餐,或单独为露台加一道麻婆豆腐。
2.1 Zone:策略容器与信任等级标尺
CentOS 8 自带 9 个预定义 zone,每个 zone 对应一套独立的默认策略模板:
| Zone 名称 | 默认目标动作 | 典型适用场景 | 关键特性 |
|---|---|---|---|
drop
|
DROP
所有入站包
| 高风险暴露面(如公网 DMZ 主机) | 不发 ICMP 回应,最隐蔽 |
block
|
REJECT
并返回 icmp-host-prohibited
| 需要明确拒绝反馈的测试环境 | 比 drop 更易排查 |
public
|
REJECT
(默认)
| 默认外网接口,仅开放必要服务 | 严格限制,适合云服务器 |
external
|
REJECT
+ masquerade 启用
| 作为 NAT 网关时的外网口 | 自动开启 IP 伪装 |
internal
|
ACCEPT
(默认)
| 内网接口,信任局域网设备 | 开放 DHCP/DNS/Samba |
dmz
|
REJECT
| 非军事区,托管部分对外服务 | 介于 public 与 internal 之间 |
work
|
ACCEPT
| 办公内网,信任同事设备 | 类似 internal,但更宽松 |
home
|
ACCEPT
| 家庭网络,信任家人设备 | 同上,语义更亲和 |
trusted
|
ACCEPT
| 完全信任的管理网络 | 所有流量放行 |
关键细节在于:
一个网络接口(interface)只能属于一个 zone,但一个 zone 可绑定多个接口
。例如,你有
eth0
(公网)、
eth1
(内网)、
docker0
(Docker 网桥),可以将
eth0
绑定到
public
,
eth1
和
docker0
绑定到
internal
。firewalld 会为每个 zone 生成独立的 nftables 表(如
inet firewalld_public
),确保策略隔离。
注意:
--get-active-zones命令返回的 zone 列表,仅显示当前有活跃接口绑定的 zone。若你执行firewall-cmd --zone=public --remove-interface=eth0,publiczone 将从该列表消失,但其配置(如已添加的服务)仍保留在/etc/firewalld/zones/public.xml中,下次绑定接口时自动恢复。
2.2 Service:可复用的策略模块包
service
是 firewalld 的“策略原子单元”。每个 service 定义(XML 文件)封装了端口、协议、辅助模块(helper)等完整信息。以
ssh
服务为例,其定义
/usr/lib/firewalld/services/ssh.xml
内容如下:
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>SSH</short>
<description>Secure Shell (SSH) is a protocol for logging into and executing commands on remote machines.</description>
<port protocol="tcp" port="22"/>
</service>
而更复杂的
samba-client
服务则包含多端口与 helper:
<service>
<short>Samba Client</short>
<port protocol="udp" port="137"/>
<port protocol="udp" port="138"/>
<port protocol="tcp" port="139"/>
<port protocol="tcp" port="445"/>
<module name="nf_conntrack_netbios_ns"/>
</service>
<module>
标签至关重要:它加载内核连接跟踪辅助模块,确保 Samba 的 NetBIOS 名称服务(UDP 137)能被正确关联到后续的会话流中。若仅开放端口而不加载 module,Samba 流量可能被 conntrack 误判为无效连接而丢弃。
我曾在线上部署 Samba 时,只执行了
firewall-cmd --add-port=137-139/tcp --add-port=445/tcp
,结果 Windows 客户端始终无法浏览共享。抓包发现 UDP 137 的 Name Query 请求发出后,无响应。最终定位到缺失
nf_conntrack_netbios_ns
模块,补上
firewall-cmd --add-module=nf_conntrack_netbios_ns
后立即恢复正常。这印证了 service 定义的价值——它把专家经验固化为可复用的、经过验证的策略包。
2.3 Port:面向未来的灵活补充手段
当标准 service 无法覆盖需求时(如自定义 Web 应用监听 8080 端口),
port
是最直接的补充方式。但需警惕其“裸规则”属性:
firewall-cmd --add-port=8080/tcp
本质是在当前 zone 的规则链中插入一条
tcp dport 8080 accept
,它不携带任何上下文(如是否需要 conntrack helper),也不参与 service 依赖管理。
因此,我的实操原则是: 优先使用 service,其次 port,最后才考虑 rich rule 。对于 8080 端口,我通常会创建自定义 service:
# 复制模板
sudo cp /usr/lib/firewalld/services/http.xml /etc/firewalld/services/myapp.xml
# 编辑 myapp.xml,修改端口为 8080
sudo firewall-cmd --reload
# 然后像标准 service 一样启用
sudo firewall-cmd --zone=public --add-service=myapp --permanent
这样做的好处是:未来若需为 myapp 添加 UDP 健康检查端口(如 8081/udp),只需编辑 XML 文件并重载,无需记忆多条 port 命令;且
firewall-cmd --list-services
能清晰列出所有启用的服务,运维交接一目了然。
3. 从零配置到生产就绪:一个真实 CentOS 8 服务器的 firewalld 实战流程
我以一台新装的 CentOS 8 云服务器(IP: 203.0.113.10)为例,完整复现从初始化到上线的 firewalld 配置链路。每一步都标注了“为什么这么做”和“不这么做会怎样”,这是我在多次线上事故后沉淀的 checklist。
3.1 初始化检查与安全基线设定
登录服务器后,第一件事不是急着开服务,而是确认 firewalld 当前状态与默认行为:
# 查看 firewalld 服务状态(必须 active (running))
sudo systemctl status firewalld
# 查看默认 zone(新装系统默认为 public)
sudo firewall-cmd --get-default-zone
# 查看当前活跃 zone 及绑定接口
sudo firewall-cmd --get-active-zones
# 查看 public zone 当前开放的服务(初始应只有 ssh)
sudo firewall-cmd --zone=public --list-services
此时,
--list-services
输出应为
ssh
。如果看到
dhcpv6-client
或其他服务,说明安装过程或镜像预置了额外规则,需核查来源。我曾遇到某云厂商镜像默认开启了
cockpit
(Web 管理界面),导致 9090 端口意外暴露,成为安全审计漏洞。
关键操作:立即将默认 zone 设为
drop(最保守起点),再逐步放开:sudo firewall-cmd --set-default-zone=drop # 此时所有入站连接(包括 SSH)将被静默丢弃!必须在本地终端或带控制台的云平台操作 # 验证:从远程 SSH 断开,用云平台 VNC 控制台登录,确认能连上
将默认 zone 设为
drop
是黄金法则。它迫使你显式声明每一个需要的访问入口,杜绝“忘记关闭的调试端口”这类低级错误。很多团队的安全基线要求,新服务器上线前必须完成此步骤。
3.2 为 SSH 服务构建弹性访问策略
SSH 是命脉,但直接
--add-service=ssh
过于粗放。生产环境需兼顾可用性与安全性:
# 方案一:仅限特定 IP 段(推荐用于办公网络固定出口)
sudo firewall-cmd --permanent --zone=public --add-source=203.0.113.0/24
sudo firewall-cmd --permanent --zone=public --add-service=ssh
# 方案二:使用 rich rule 实现更精细控制(如限制连接速率)
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" limit value="3/m" accept'
# 方案三:为跳板机(Bastion Host)配置,允许任意源但强限速
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule service name="ssh" limit value="2/m" accept'
limit value="2/m"
是防暴力破解的关键。它利用 nftables 的
limit
表达式,在内核层面限制每分钟最多 2 个新连接请求。超过阈值的包直接被
drop
,不进入用户态处理,CPU 开销极低。我对比过:未启用限速时,
hydra -l root -P wordlist.txt ssh://203.0.113.10
一分钟可尝试 1200+ 密码;启用
2/m
后,hydra 报告“all passwords tried”,实际仅发起 2 次连接即被阻断。
注意:
--add-rich-rule必须配合--permanent使用,否则重启 firewalld 后失效。rich rule 的语法虽强大,但易出错。我建议将常用规则保存为脚本:# /root/firewall-ssh-safe.sh firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" limit value="3/m" accept' firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" service name="ssh" log prefix="ssh-bruteforce" level="info" limit value="1/m" reject' firewall-cmd --reload
最后一行
log prefix="ssh-bruteforce"
会在
/var/log/messages
中记录被拒绝的暴力破解尝试,为安全审计提供原始日志。
3.3 部署 Web 应用:HTTP/HTTPS 与健康检查端口协同
假设我们部署一个 Node.js 应用,监听
8080/tcp
(HTTP)、
8443/tcp
(HTTPS)、
8081/tcp
(健康检查)。按前述原则,先创建自定义 service:
sudo tee /etc/firewalld/services/nodeapp.xml << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>NodeJS Application</short>
<description>Production web application serving HTTP, HTTPS and health checks.</description>
<port protocol="tcp" port="8080"/>
<port protocol="tcp" port="8443"/>
<port protocol="tcp" port="8081"/>
</service>
EOF
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --zone=public --add-service=nodeapp
但仅此不够。现代 Web 架构常含反向代理(如 Nginx),其健康检查可能使用
curl http://localhost:8081/health
。若应用服务器本身也需访问外部 API(如支付网关),则需配置
masquerade
(NAT 伪装):
# 启用 masquerade,使服务器能以自身 IP 为源访问外网
sudo firewall-cmd --permanent --zone=public --add-masquerade
# 允许出站连接(firewalld 默认允许,但显式声明更清晰)
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" destination address="0.0.0.0/0" accept'
--add-masquerade
是关键。它在 nftables 中添加
masquerade
语句,将服务器发出的包的源 IP 替换为出站接口的 IP。没有它,应用服务器访问外网时,响应包可能因路由不对称而丢失。我曾在线上遇到 Node.js 应用调用微信支付 API 超时,排查数小时才发现
masquerade
未启用,导致响应包被网关丢弃。
3.4 持久化与验证:确保重启后策略不漂移
所有
--permanent
操作仅修改 XML 配置文件,不会立即生效。必须执行
--reload
才能加载到运行时:
# 1. 应用所有永久配置
sudo firewall-cmd --reload
# 2. 验证运行时状态(必须与预期一致)
sudo firewall-cmd --list-all # 查看 default zone 全量配置
sudo firewall-cmd --zone=public --list-all # 查看 public zone 详情
# 3. 验证 nftables 底层规则(终极确认)
sudo nft list chain inet firewalld_public IN_public_pre 2>/dev/null | grep -E "(8080|8443|8081)"
--list-all
输出应清晰显示
services: ssh nodeapp
和
ports:
(为空,因为我们用了 service)。若看到
ports: 8080/tcp
,说明你误用了
--add-port
而非
--add-service
,需清理:
sudo firewall-cmd --permanent --zone=public --remove-port=8080/tcp
sudo firewall-cmd --reload
最后,进行端口连通性验证。我编写了一个轻量脚本
/root/validate-firewall.sh
:
#!/bin/bash
# 测试 SSH(应通)
nc -zv 203.0.113.10 22 2>&1 | grep -q "succeeded" && echo "✅ SSH OK" || echo "❌ SSH FAILED"
# 测试 Web 端口(应通)
nc -zv 203.0.113.10 8080 2>&1 | grep -q "succeeded" && echo "✅ HTTP OK" || echo "❌ HTTP FAILED"
# 测试非法端口(应不通)
nc -zv 203.0.113.10 2222 2>&1 | grep -q "succeeded" && echo "❌ UNAUTHORIZED PORT OPEN!" || echo "✅ BLOCKED PORT OK"
每次配置变更后运行此脚本,确保策略符合预期。自动化验证是避免“配置正确但没生效”这类低级错误的最后一道防线。
4. 故障排查全景图:从连接失败到日志溯源的完整链路
即使最严谨的配置,也会遭遇“明明开了端口,却连不上”的经典困境。我梳理了一套标准化的七步排查法,覆盖从网络层到应用层的所有可能性。这套方法已在数十台 CentOS 8 服务器上验证有效。
4.1 第一步:确认 firewalld 服务与 zone 绑定状态
这是最容易被忽略的起点。很多“连不上”问题,根源是 firewalld 服务根本没运行,或接口未绑定到正确 zone:
# 检查服务状态(必须 active (running))
sudo systemctl status firewalld
# 检查接口绑定(eth0 必须出现在某个 zone 下)
sudo firewall-cmd --get-active-zones
# 若 eth0 未出现,手动绑定(假设公网接口名是 eth0)
sudo firewall-cmd --permanent --zone=public --add-interface=eth0
sudo firewall-cmd --reload
我曾协助一个团队排查,他们执行了
firewall-cmd --add-service=http
,但
--list-services
始终为空。最终发现
eth0
接口被错误绑定到了
trusted
zone(
firewall-cmd --get-active-zones
显示
trusted: eth0
),而
http
服务只添加到了
public
zone。解决方案是
firewall-cmd --permanent --zone=public --add-interface=eth0
,然后
--reload
。
4.2 第二步:验证运行时规则与持久化配置一致性
--permanent
和运行时是两套独立状态。常见错误是修改了 XML 文件但忘了
--reload
,或执行了
--add-service
但漏了
--permanent
:
# 比较运行时与永久配置
sudo firewall-cmd --list-all # 运行时
sudo firewall-cmd --permanent --list-all # 永久配置(需 --permanent)
# 检查特定 zone 的服务列表
sudo firewall-cmd --zone=public --list-services
sudo firewall-cmd --permanent --zone=public --list-services
若两者不一致,执行
sudo firewall-cmd --reload
同步。注意:
--reload
会短暂中断连接(毫秒级),但比
--restart
更安全,后者会完全停止服务再启动。
4.3 第三步:穿透 firewalld,直查 nftables 底层规则
当 firewalld 命令显示“已添加”,但连接仍失败时,必须下沉到 nftables 层验证:
# 查看 firewalld 为 public zone 生成的完整规则集
sudo nft list chain inet firewalld_public IN_public_pre
# 搜索目标端口(如 8080)
sudo nft list chain inet firewalld_public IN_public_pre | grep "dport 8080"
# 查看规则计数器(确认规则是否被命中)
sudo nft list chain inet firewalld_public IN_public_pre -a | grep -A2 "dport 8080"
-a
参数显示规则的 handle ID 和 packet/byte 计数器。如果计数器为 0,说明流量根本没到达这条规则——问题可能在更早的链(如
IN_public_log
)或网络路由。如果计数器递增但连接失败,则问题在应用层(如应用未监听、监听了 127.0.0.1 而非 0.0.0.0)。
4.4 第四步:检查内核 conntrack 状态与辅助模块
Samba、FTP、H.323 等协议依赖 conntrack 辅助模块。若端口开放但协议异常,先查模块:
# 查看已加载的 conntrack helper 模块
lsmod | grep nf_conntrack
# 检查特定模块是否启用(如 nf_conntrack_ftp)
sudo firewall-cmd --list-modules | grep ftp
# 若缺失,手动加载(并持久化)
sudo firewall-cmd --permanent --add-module=nf_conntrack_ftp
sudo firewall-cmd --reload
FTP 是典型例子。被动模式(PASV)下,客户端需连接服务器随机端口(如 50000-51000)。firewalld 的
ftp
service 定义不仅开放 21/tcp,还加载
nf_conntrack_ftp
模块,该模块能解析 FTP 控制连接中的
PORT
或
PASV
命令,动态打开对应数据端口。若模块未加载,数据连接会被丢弃。
4.5 第五步:启用并分析 firewalld 日志
当以上步骤均无异常,需启用详细日志:
# 启用日志(默认 level=info,可设 debug 获取更多细节)
sudo firewall-cmd --set-log-denied=all
# 查看日志(实时跟踪)
sudo journalctl -u firewalld -f | grep -i "deny\|reject"
# 或查看 messages(firewalld 日志默认输出至此)
sudo tail -f /var/log/messages | grep -i "firewalld.*deny"
--set-log-denied=all
会让 firewalld 对所有被拒绝的包记录日志,包含源 IP、目标端口、协议、zone。日志格式示例:
Jan 15 10:22:33 server firewalld[1234]: WARNING: ICMP type 'host-unreachable' is not supported by the kernel for this address family.
Jan 15 10:22:34 server firewalld[1234]: INFO: '192.168.1.100' DROP IN=eth0 OUT= MAC=... SRC=192.168.1.100 DST=203.0.113.10 PROTO=TCP SPT=54321 DPT=8080
最后一行清晰显示:来自
192.168.1.100
的 TCP 包,目标
203.0.113.10:8080
,被
DROP
。结合
IN=eth0
,确认是入站流量被拒。若日志中无此记录,说明包在到达 firewalld 前已被其他机制(如云平台安全组、物理防火墙)拦截。
4.6 第六步:排除云平台与宿主机网络层干扰
CentOS 8 常部署于云环境(AWS/Azure/阿里云)。firewalld 是操作系统层防火墙,而云平台提供 网络层安全组(Security Group) ,二者是叠加关系:
| 层级 | 控制者 | 作用范围 | 典型配置项 |
|---|---|---|---|
| 云平台安全组 | 云服务商 | 实例网络边界 | 入站规则:允许 22/8080 from 0.0.0.0/0 |
| firewalld | 操作系统 | 实例内部网络栈 | zone: public, service: ssh,nodeapp |
必须确保两者策略一致。我见过最典型的错误:安全组开放了
0.0.0.0/0
,但 firewalld 的
public
zone 未添加
nodeapp
服务,导致“云平台放行,OS 拦截”。反之,若安全组未开放端口,即使 firewalld 全开,流量也无法抵达服务器网卡。
验证方法:在云平台控制台,临时将安全组入站规则设为
0.0.0.0/0
(所有端口),再测试连接。若此时通了,问题必在安全组配置。
4.7 第七步:终极验证——从服务器内部发起连接测试
所有外部测试都可能受中间网络影响。最可靠的验证,是从服务器自身发起连接:
# 测试本地回环(验证应用是否监听)
curl -v http://127.0.0.1:8080
# 测试本机 IP(验证 firewalld 是否放行本机访问)
curl -v http://203.0.113.10:8080
# 测试本机 IP 加端口(同上,但指定端口)
nc -zv 203.0.113.10 8080
若
127.0.0.1
通而
203.0.113.10
不通,100% 是 firewalld 或安全组问题。若两者都不通,则是应用未启动或监听地址错误(如只监听
127.0.0.1:8080
,而非
0.0.0.0:8080
)。
我将这套七步法整理成一张速查表,贴在团队 Wiki 首页。每次接到“连不上”工单,工程师必须按顺序执行,不得跳步。实践证明,95% 的问题能在前三步定位。
5. 进阶技巧与生产环境避坑指南
在 CentOS 8 的 firewalld 实战中,有一些超越基础文档的技巧和血泪教训,它们不常被提及,却直接影响系统的稳定性与可维护性。以下是我从三年生产运维中提炼的硬核经验。
5.1 动态 zone 切换:根据网络环境自动适配策略
企业员工常在办公室、家庭、咖啡馆等不同网络切换。手动
--change-interface-zone
效率低下。firewalld 支持基于网络连接名称(connection name)的自动 zone 分配,需配合 NetworkManager:
# 查看当前 NetworkManager 连接
nmcli connection show
# 为名为 "Office-WiFi" 的连接分配 internal zone
sudo nmcli connection modify "Office-WiFi" connection.zone internal
# 为名为 "Home-ISP" 的连接分配 public zone
sudo nmcli connection modify "Home-ISP" connection.zone public
# 重启 NetworkManager 生效
sudo systemctl restart NetworkManager
NetworkManager 会监听网络连接事件,当检测到连接名称匹配时,自动调用
firewall-cmd --change-interface-zone
将对应接口绑定到指定 zone。这实现了“到公司自动信任内网,回家自动启用公网策略”的无缝体验。注意:此功能依赖 NetworkManager,若服务器禁用 NM(如某些云服务器默认禁用),则不可用。
5.2 富规则(Rich Rule)的实战威力与陷阱
rich rule
是 firewalld 最强大的武器,但也最易误用。它支持基于源/目标 IP、端口、协议、ICMP 类型、连接状态(
state
)、甚至时间(
time
)的复杂条件。一个经典案例是:
仅允许工作日 9:00-18:00 访问管理端口
:
# 创建时间受限的 rich rule
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" port port="9090" protocol="tcp" time hour="09:00-18:00" day-of-week="mon-fri" accept'
# 为非工作时间添加拒绝规则(确保覆盖)
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" port port="9090" protocol="tcp" reject'
time
和
day-of-week
参数依赖系统时钟与
chronyd
时间同步。若服务器时间偏差超过 5 分钟,规则可能失效。因此,
必须确保
chronyd
服务启用并同步
:
sudo systemctl enable chronyd
sudo systemctl start chronyd
sudo chronyc tracking # 验证同步状态
另一个陷阱是 rich rule 的匹配顺序。firewalld 按添加顺序(handle ID 递增)执行规则。若你先添加了
accept
规则,后添加
reject
,则
reject
永远不会触发。因此,我坚持“先拒绝后接受”的原则,并用
--permanent
+
--reload
确保顺序固化。
5.3 Docker 与 firewalld 的共存之道
Docker 默认使用
iptables
后端管理容器网络,与 firewalld 的
nftables
后端冲突。CentOS 8 上,Docker 20.10+ 已原生支持
firewalld
驱动,但需显式配置:
# 编辑 Docker daemon 配置
sudo tee /etc/docker/daemon.json << 'EOF'
{
"iptables": false,
"ip-forward": true,
"default-runtime": "runc",
"exec-opts": ["native.cgroupdriver=systemd"],
"live-restore": true
}
EOF
# 重启 Docker
sudo systemctl restart docker
# 验证 firewalld 是否接管容器网络
sudo firewall-cmd --list-all | grep docker
"iptables": false
是关键。它禁止 Docker 直接操作 iptables,转而通过 firewalld 的 D-Bus 接口管理规则。此时,
docker run -p 8080:80 nginx
会自动在
public
zone 添加端口映射,无需手动
firewall-cmd --add-port
。若未设置此选项,Docker 会绕过 firewalld 直接写 iptables,导致策略混乱。
5.4 策略备份、版本化与灾难恢复
firewalld 配置分散在
/etc/firewalld/
下,手工备份易遗漏。我采用
firewall-offline-cmd
工具进行原子化导出:
# 导出当前所有配置为单个 XML 文件(含 zones, services, ipsets)
sudo firewall-offline-cmd --export-etc-firewalld > /backup/firewalld-backup-$(date +%Y%m%d).xml
# 导入备份(需先停 firewalld)
sudo systemctl stop firewalld
sudo firewall-offline-cmd --import-etc-firewalld < /backup/firewalld-backup-20231001.xml
sudo systemctl start firewalld
firewall-offline-cmd
是 firewalld 的离线管理工具,不依赖服务运行,确保备份/恢复过程绝对可靠。我将此命令加入每日 cron 任务,并将备份文件推送到 Git 仓库,实现配置版本化。某次误操作
--remove-all-services
后,5 分钟内即从 Git 恢复,零业务中断。
5.5 性能监控:确认 firewalld 未成为瓶颈
firewalld 本身不处理数据包,它只是 nftables 的配置代理。但复杂 rich rule 或大量 IPset 可能增加内核规则匹配开销。监控方法:
# 查看 nftables 规则总数(过多需优化)
sudo nft list ruleset | grep "chain" | wc -l
# 监控 conntrack 表使用率(高占用影响性能)
sudo conntrack -C # 输出类似 "32768/65536",表示

527

被折叠的 条评论
为什么被折叠?



