在CentOS运维中,firewalld规则迁移和批量管理是每个运维工程师都绕不开的实操难题。简单说,规则迁移就是把一台服务器上配置好的防火墙规则完整搬到另一台机器上,而批量管理则是在多台服务器上同时执行规则变更,避免一台台手动敲命令。核心解决方案有三条路:一是通过firewall-cmd导出配置文件再导入,二是利用XML区域文件直接拷贝,三是借助Ansible等自动化工具实现批量下发。下面我把每种方法的具体操作、注意事项和实战技巧全部讲透。

一、firewalld规则迁移的三种主流方式

第一种方式是最常用的,直接用firewall-cmd的导出功能。在源服务器上执行以下命令,把当前运行的规则导出为XML文件:

firewall-cmd --runtime-to-permanent
firewall-cmd --zone=public --list-all --xml > /tmp/firewall_public.xml

这里有个关键点,必须先执行runtime-to-permanent,把运行时规则写入永久配置,否则导出的只是临时规则,重启就丢了。导出后把XML文件scp到目标服务器,在目标机上用firewall-cmd加载:

scp /tmp/firewall_public.xml root@target_server:/tmp/
ssh root@target_server "firewall-cmd --permanent --zone=public --load-rich-rule-from-file=/tmp/firewall_public.xml"
ssh root@target_server "firewall-cmd --reload"

第二种方式是直接拷贝区域配置文件。firewalld的区域配置存放在/etc/firewalld/zones/目录下,每个区域对应一个.xml文件。你可以直接把源机器的区域文件打包拷过去:

tar czf /tmp/zones_backup.tar.gz -C /etc/firewalld/zones/ .
scp /tmp/zones_backup.tar.gz root@target_server:/tmp/
ssh root@target_server "tar xzf /tmp/zones_backup.tar.gz -C /etc/firewalld/zones/"
ssh root@target_server "firewall-cmd --reload"

这种方式适合整个区域配置完全一致的场景,比如同一个业务集群的机器。但要注意,如果目标机器的区域文件已经有自定义修改,直接覆盖会丢失原有配置,建议先备份目标机的/etc/firewalld/zones/目录。

第三种方式是通过rich rule和service的组合导出。如果你的规则比较零散,不是整个区域迁移,而是特定几条规则,那就用rich rule的方式单独提取:

firewall-cmd --zone=public --list-rich-rules > /tmp/rich_rules.txt
ssh root@target_server "while read rule; do firewall-cmd --permanent --zone=public --add-rich-rule=\"\$rule\"; done < /tmp/rich_rules.txt"
ssh root@target_server "firewall-cmd --reload"

这种方式精细度最高,适合只迁移部分关键规则的场景,比如只迁移数据库端口和Web端口的放行规则。

二、批量管理firewalld规则的实战方案

当你管着几十台甚至上百台CentOS服务器时,逐台登录改规则是不现实的。批量管理的核心思路就是"配置集中化+自动化下发"。

最直接的方案是写Shell脚本批量执行。假设你有一个服务器列表文件servers.txt,每行一个IP地址,规则文件是rules.sh:

#!/bin/bash
SERVER_LIST="servers.txt"

while read SERVER; do
  echo "Processing $SERVER ..."
  ssh root@$SERVER "
    firewall-cmd --permanent --add-port=8080/tcp
    firewall-cmd --permanent --add-port=3306/tcp
    firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.0.0.0/8 port protocol=tcp port=22 accept'
    firewall-cmd --reload
  "
done < $SERVER_LIST

这个脚本的问题在于没有错误处理和结果反馈。生产环境建议加上超时控制和执行结果记录:

#!/bin/bash
SERVER_LIST="servers.txt"
LOG_FILE="firewall_batch_$(date +%Y%m%d).log"

while read SERVER; do
  echo "[$(date)] Processing $SERVER" | tee -a $LOG_FILE
  ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@$SERVER "
    firewall-cmd --permanent --add-port=8080/tcp 2>&1
    firewall-cmd --permanent --add-port=3306/tcp 2>&1
    firewall-cmd --perload 2>&1
    echo 'RELOAD_DONE'
  " 2>&1 | tee -a $LOG_FILE
done < $SERVER_LIST

更专业的做法是用Ansible。Ansible的firewalld模块原生支持规则管理,幂等性好,不会重复添加。写一个playbook:

- name: Batch manage firewalld rules
  hosts: web_servers
  become: yes
  tasks:
    - name: Open HTTP port
      ansible.posix.firewalld:
        port: 80/tcp
        permanent: yes
        state: enabled
        zone: public

    - name: Open MySQL port
      ansible.posix.firewalld:
        port: 3306/tcp
        permanent: yes
        state: enabled
        zone: public

    - name: Add rich rule for SSH
      ansible.posix.firewalld:
        rich_rule: 'rule family=ipv4 source address=10.0.0.0/8 port protocol=tcp port=22 accept'
        permanent: yes
        state: enabled
        zone: public

    - name: Reload firewalld
      ansible.posix.firewalld:
        state: reloaded

Ansible的优势在于它会自动判断规则是否已存在,已存在就跳过,不会重复添加。执行命令是ansible-playbook -i inventory firewall_rules.yml,几百台机器几分钟搞定。

三、规则迁移中容易踩的坑和避坑指南

第一个坑是区域不匹配。源服务器用的是public区域,目标服务器可能默认是drop区域或者自定义区域。迁移前一定要确认目标机的区域配置,用firewall-cmd --get-active-zones检查。如果区域不对,要么在目标机上创建对应区域,要么修改导入的XML文件中的zone属性。

第二个坑是service和port混用导致冲突。比如源服务器用firewall-cmd --add-service=http开放80端口,目标服务器可能没有http这个service定义。解决办法是迁移时统一用port方式,或者确保目标机安装了相同的firewalld服务包版本。

第三个坑是忽略了ICMP规则。很多人迁移规则只关注TCP/UDP端口,忘了ICMP。如果你的监控系统依赖ping,迁移后ping不通就麻烦了。记得把icmp-block-inversion和icmp-block规则一并迁移:

firewall-cmd --zone=public --list-icmp-blocks > /tmp/icmp_rules.txt
ssh root@target_server "while read icmp; do firewall-cmd --permanent --zone=public --add-icmp-block=\$icmp; done < /tmp/icmp_rules.txt"

第四个坑是版本差异。CentOS 7用firewalld 0.4.x,CentOS 8/Stream用firewalld 0.8.x以上,CentOS 9又有变化。XML格式在大版本之间可能不兼容,跨大版本迁移时建议先在测试环境验证。

四、建立规则管理的标准化流程

长期运维不能靠临时脚本,要建立标准化流程。我的建议是这样做:

首先,所有规则变更必须有记录。建一个Git仓库,存放所有服务器的firewalld规则模板,每次变更提交到Git,有迹可查。目录结构可以这样组织:

firewall-rules/
├── templates/
│   ├── web_server.xml
│   ├── db_server.xml
│   └── bastion_server.xml
├── inventory/
│   └── hosts.ini
└── playbooks/
    └── deploy_rules.yml

其次,区分永久规则和临时规则。临时规则用firewall-cmd --add-xxx(不加--permanent),适合临时调试;永久规则必须加--permanent再reload。迁移时只迁移永久规则,临时规则本来就不该保留。

最后,做好回滚预案。每次批量下发规则前,先把目标机的当前配置备份:

ssh root@target_server "cp -r /etc/firewalld /etc/firewalld.bak.$(date +%s)"

出问题时一键回滚:

ssh root@target_server "rm -rf /etc/firewalld && mv /etc/firewalld.bak.* /etc/firewalld && firewall-cmd --reload"

五、进阶技巧:规则审计与合规检查

在安全要求高的环境中,还需要定期审计firewalld规则。可以写一个审计脚本,对比实际运行规则和标准模板的差异:

#!/bin/bash
EXPECTED_PORTS="80 443 8080 3306"
ACTUAL_PORTS=$(firewall-cmd --zone=public --list-ports | tr ' ' '\n')

for port in $EXPECTED_PORTS; do
  echo "$ACTUAL_PORTS" | grep -q "$port"
  if [ $? -ne 0 ]; then
    echo "WARNING: Port $port is missing!"
  fi
done

这个脚本可以配合cron定期执行,把结果发到运维群或者写入监控系统,确保规则没有被意外篡改。

总结一下,firewalld规则迁移的核心就是"导出-传输-导入-重载"四步走,批量管理的核心是"模板化+自动化+幂等性"。不管用Shell脚本还是Ansible,关键是要有标准、有记录、有回滚。把这些做到位,CentOS防火墙管理就不再是头疼的事,而是可控、可追溯、可审计的标准化运维流程。