上一篇 下一篇 分享链接 返回 返回顶部

如何在香港服务器的CentOS 7环境中优化iptables与firewalld规则,避免高流量下的网络性能下降?

发布人:Minchunlin 发布时间:2025-08-18 10:01 阅读量:583


昨天凌晨 2:40,在香港葵涌机房,走廊尽头的空调像海风一样呼呼吹着,我盯着 NOC 大屏上那条红得发烫的 PPS 曲线,像心电图一样抖个不停。前一分钟,业务从 60 万 PPS 暴冲到 180 万 PPS,入口 LVS 没报警,应用也没明显 5xx——偏偏是边缘一台“看似平平无奇”的 CentOS 7 边界机开始丢包。

我把咖啡放在机架门上,手摸着 KVM 的键盘,深吸一口气:这次,八成又是 netfilter 的老毛病——规则多、顺序乱、conntrack 顶到天花板,再加上几条“好心办坏事”的 LOG。

下面这篇,是我那一夜从“止血”到“手术”的完整过程。无论你是刚接触 Linux 防火墙的新手,还是在高流量现场摸爬滚打多年的老兵,我尽量把步骤、参数、坑点和取舍都摊开讲清楚,能抄就抄,能复用就复用。

现场环境与流量画像

硬件/软件清单(当班那台边界机)

配置
机型 Dell R630(1U)
CPU 2× Intel Xeon E5-2680 v4(各 14C/28T,共 28C/56T)
内存 128 GB DDR4
网卡 2× Intel X520 10GbE(ixgbe,多队列)
系统 CentOS Linux release 7.9 (3.10.0-1160 内核)
组件 firewalld 0.6.x(iptables 后端)、iptables-services、ipset、conntrack-tools、irqbalance、ethtool

典型流量画像

  • 峰值:1.5–2.0 Mpps / 4–7 Gbps(以 TCP/80/443 为主,夹杂少量 UDP 探测/健康检查)
  • 连接特征:大量短连接 + 部分长连 keepalive
  • 连通角色:边界转发 + 少量本地终止(SSH/管理面)
  • 症状:PPS 一冲高,丢包率和 RTT 抖动上来;softirq/ksoftirqd 占用飙升;nf_conntrack_count 接近上限;iptables 计数显示多条 LOG/rich-rule 命中频繁。
  • 先止血:3 个 5 分钟内能做的动作
  • 目标:把“热路径”变冷,让 CPU 先喘口气,再做精细手术。

暂停热日志(别在热路径上 -j LOG)

# 临时禁用所有 LOG 规则(按需定位具体链)
iptables -S | grep " -j LOG" | sed 's/^-A/iptables -D/' | bash
# firewalld 若在用 rich-rule 做日志,先移除/降低频率(见后文的限速 LOG)

把已建立连接放到最前(把热流量早放行)

iptables -I INPUT 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -I FORWARD 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

单独的管理白名单(避免每条规则都去比对源)

# 先用 ipset 管起来(火墙与纯 iptables 都通用)
ipset create mgmt_allow hash:ip -exist
ipset add mgmt_allow 203.0.113.10 -exist   # 你的跳板机
ipset add mgmt_allow 203.0.113.11 -exist

iptables -I INPUT 2 -m set --match-set mgmt_allow src -p tcp --dport 22 -j ACCEPT

这三刀下去,一般 CPU 就能降一截;如果现场仍旧喘不过气,再看下面的系统级优化。

火力点一:把 conntrack 调到“够用且不浪费”

为什么卡? 高 PPS 下,每个新流要做状态跟踪;短连接多时,nf_conntrack_count 很容易顶到 nf_conntrack_max,随后开始“看不见新连接”→ 丢包/超时。

估算思路

每条 conntrack 条目内存占用(CentOS 7/3.10 内核)大致 350–500 B/条(协议不同略有差异)。

你能给 conntrack 的内存预算 ≈ 机器空闲内存的 10–20% 较稳妥。

我们的机器(128GB)给 2GB 预算 → 上限 ~4,000,000 条(按 512B 粗算)。
哈希桶(hashsize)建议 ≈ nf_conntrack_max / 4 → 1,000,000。

落地操作

# 1) 安装工具
yum install -y conntrack-tools ipset

# 2) 立刻把上限拉高(可热改)
sysctl -w net.netfilter.nf_conntrack_max=4000000
echo 4000000 > /proc/sys/net/netfilter/nf_conntrack_max

# 3) 调整典型超时(HTTP/短连多时,收紧 time_wait、fin_wait 等)
cat >/etc/sysctl.d/99-nf-conntrack.conf <<'EOF'
net.netfilter.nf_conntrack_max=4000000
net.netfilter.nf_conntrack_buckets=1000000     # 部分内核读作 hashsize(见下一条)
# 常见 TCP 状态超时(按业务调)
net.netfilter.nf_conntrack_tcp_timeout_established=300
net.netfilter.nf_conntrack_tcp_timeout_close_wait=60
net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30
net.netfilter.nf_conntrack_tcp_timeout_time_wait=15
# UDP 通常更短
net.netfilter.nf_conntrack_udp_timeout=15
net.netfilter.nf_conntrack_udp_timeout_stream=60
EOF
sysctl --system

关于 hashsize 的坑

hashsize 多数情况下需要在模块加载时指定,不能热改;CentOS 7 上对应 /sys/module/nf_conntrack/parameters/hashsize 通常只读。

解决:在 /etc/modprobe.d/nf_conntrack.conf 写入参数,并重启。

echo "options nf_conntrack hashsize=1000000" >/etc/modprobe.d/nf_conntrack.conf
# 确保开机加载(通常自动),必要时:
echo nf_conntrack >/etc/modules-load.d/nf_conntrack.conf

观测命令

# 当前条目/上限
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

# 概览统计
conntrack -S

火力点二:用 ipset + 直接规则,替换上千条“散弹枪式”匹配

大量 per-IP/网段/端口的规则会把 iptables 链拉得巨长,线性匹配的 CPU 成本可观。把“多对一”匹配折叠到 ipset,命中就是 O(1)。

规范做法

# 白名单/黑名单/探测源 分开建
ipset create mgmt_allow hash:ip -exist
ipset create blocklist  hash:ip -exist
ipset create probe_src  hash:ip -exist

# 例:把某个黑名单段整个塞进来
ipset add blocklist 198.51.100.0/24 -exist

iptables 热路径(示例顺序)

# ① 已建立连接优先
iptables -I INPUT 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# ② 管理面白名单(SSH)
iptables -I INPUT 2 -m set --match-set mgmt_allow src -p tcp --dport 22 -j ACCEPT

# ③ 早丢黑名单
iptables -I INPUT 3 -m set --match-set blocklist src -j DROP

# ④ 业务端口合并匹配(multiport)
iptables -I INPUT 4 -p tcp -m multiport --dports 80,443 -j ACCEPT

# ⑤ 兜底策略:DROP(确保 loopback 有 ACCEPT)
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -j DROP

要点:规则少、顺序稳、命中早。把“高命中规则”挪到链前面,避免“把 CPU 预算浪费在末尾的冷门匹配上”。

火力点三:在 raw 表对可控的 UDP 流量做 NOTRACK

某些场景(例如来自我方探测机的 UDP 健康检查、固定地址的 QUIC/UDP 隧道),你未必需要 conntrack。跳过状态跟踪,直接放行,能明显减压。

谨慎:只对可信源 + 固定端口这么做,且要在 filter 表配合显式 ACCEPT。

# 把探测源放进 ipset
ipset create probe_src hash:ip -exist
ipset add probe_src 192.0.2.10 -exist   # 你的探测器/健康检查机

# 1) raw 表里 NOTRACK(在 conntrack 之前)
iptables -t raw -I PREROUTING 1 -m set --match-set probe_src src -p udp --dport 443 -j CT --notrack

# 2) filter 表显式放行同一流量(注意没有 ctstate 可用)
iptables -I INPUT 5 -m set --match-set probe_src src -p udp --dport 443 -j ACCEPT

用 firewalld 的 direct 方式(永久生效)

# ipset 用 firewalld 管理(可持久化)
firewall-cmd --permanent --new-ipset=probe_src --type=hash:ip
firewall-cmd --permanent --ipset=probe_src --add-entry=192.0.2.10

# raw NOTRACK
firewall-cmd --permanent --direct --add-rule ipv4 raw PREROUTING 0 \
  -m set --match-set probe_src src -p udp --dport 443 -j CT --notrack

# filter 放行
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \
  -m set --match-set probe_src src -p udp --dport 443 -j ACCEPT

firewall-cmd --reload

火力点四:合理的链路与规则排序(参考基线)

INPUT 链推荐结构(终止本机)

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]

# 0. loopback
-A INPUT -i lo -j ACCEPT

# 1. 已建立连接
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 2. 早丢无效包(省后面的匹配)
-A INPUT -m conntrack --ctstate INVALID -j DROP

# 3. 黑名单(ipset)
-A INPUT -m set --match-set blocklist src -j DROP

# 4. 管理面(ipset)
-A INPUT -m set --match-set mgmt_allow src -p tcp --dport 22 -j ACCEPT

# 5. 业务端口(合并匹配)
-A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT

# 6. 控制频率的日志(非热路径)
-A INPUT -m limit --limit 10/second --limit-burst 50 -j NFLOG --nflog-group 10 --nflog-prefix "FW_DROP:"

# 7. 兜底 DROP
-A INPUT -j DROP
COMMIT

FORWARD 链(作为路由/边界)同理,先 ESTABLISHED,RELATED,再特殊放行,再 default DROP。

火力点五:网卡与中断——把软中断“摊平”

高 PPS 时,iptables 的 CPU 消耗往往叠加在 softirq 上。把网卡队列、RPS/XPS、ring 缓冲、中断亲和设好,能让核“吃得更均匀”。

打开多队列/调整通道数

ethtool -l eth0                      # 看当前/最大队列数
ethtool -L eth0 combined 8           # 例如设 8 组队列(按 CPU 和流量定)

增大 ring 缓冲(防突刺)

ethtool -g eth0
ethtool -G eth0 rx 4096 tx 4096

确认 offload(一般保留 GRO/TSO/GSO,LRO 视环境)

ethtool -k eth0
# 如需调整:ethtool -K eth0 gro on tso on gso on

中断绑核(可交给 irqbalance;极端情况下手动绑)

# 先看中断号
grep eth0 /proc/interrupts
# 设置亲和(示例把某中断绑到 CPU 2)
echo 4 > /proc/irq/XX/smp_affinity    # 4 是 1<<2 的十六进制(写 4、8、10...)

开启 RPS/XPS(让收发在多个核分担)

# 把 RX 队列分配到一组 CPU(示例:0-7 号核)
echo f0 > /sys/class/net/eth0/queues/rx-0/rps_cpus     # 按位图写入
# XPS 类似:
echo f0 > /sys/class/net/eth0/queues/tx-0/xps_cpus

提醒:每次改动后,用 nstat -az、sar -n DEV 1、top -H、/proc/softirqs 观察是否有效果。

火力点六:firewalld 的**“省心而高效”**用法

很多人卡在 rich-rule 又爱又恨:可读性好,但写多了 reload 变慢、规则易冗余。我的做法:

策略:能用 direct 就用 direct(直写到 iptables),批量用 ipset 管住;rich-rule 留给少数“读起来舒服”的规则。

Zone:边界机通常只要 public,不要“一个端口一个 zone”。

reload 原则:先 --runtime 调试确认,再 --permanent,最后 --reload 一次性落盘。

示例:

# 1) 基本面
firewall-cmd --set-default-zone=public
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https

# 2) ipset(持久化)
firewall-cmd --permanent --new-ipset=blocklist --type=hash:ip
firewall-cmd --permanent --ipset=blocklist --add-entry=198.51.100.0/24

# 3) direct 规则(优先级 0 越小越靠前)
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \
  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 \
  -m set --match-set blocklist src -j DROP

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 2 \
  -m set --match-set mgmt_allow src -p tcp --dport 22 -j ACCEPT

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 3 \
  -p tcp -m multiport --dports 80,443 -j ACCEPT

firewall-cmd --reload

纯 iptables 模式:当你决定关掉 firewalld

极简、稳定、可预测。在规则不常改、追求极致性能的边界机上,我常这么做:

# 1) 关 firewalld,启 iptables-services
systemctl disable --now firewalld
yum install -y iptables-services
systemctl enable iptables

# 2) 配置 /etc/sysconfig/iptables
cat >/etc/sysconfig/iptables <<'EOF'
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -m set --match-set blocklist src -j DROP
-A INPUT -m set --match-set mgmt_allow src -p tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
-A INPUT -m limit --limit 10/second --limit-burst 50 -j NFLOG --nflog-group 10 --nflog-prefix "FW_DROP:"
-A INPUT -j DROP
COMMIT
EOF

# 3) 保存并生效
service iptables save || true
systemctl restart iptables

注意:ipset 的持久化要么用 ipset save/restore + systemd 单元,要么写到开机脚本里。

验证与压测:用事实说话

命令小抄

# 流控/丢包/重传
nstat -az | egrep 'Tcp|Udp|Ip'
ss -s
sar -n DEV 1

# 规则命中
iptables -L -v -n
iptables -t raw -L -v -n

# 连接跟踪
cat /proc/sys/net/netfilter/nf_conntrack_count
conntrack -S

# 压测(外部节点)
iperf3 -c <边界机> -p 443 -u -b 2G -t 60
wrk -t16 -c2000 -d60s https://your_edge/

一次真实的前后对比

指标 优化前 优化后
峰值 PPS 1.8 Mpps 1.8 Mpps
丢包率(5 分钟 95P) 2.3% 0.2%
softirq CPU 占用 620%(多核总和) 330%
nf_conntrack_count / max 3.7M / 3.8M 1.9M / 4.0M
iptables LOG 命中/秒 12k ≤100(限速后)
RTT 抖动(p95) 42ms 12ms

影响最大的三件事:(1)ipset 折叠规则、(2)NOTRACK 探测 UDP、(3)conntrack 上限 + 合理超时。网卡队列和 ring 的优化则把峰值下的抖动“磨平了”。

我踩过的坑(你别再踩)

  • hashsize 不能热改:很多人改了 nf_conntrack_buckets 却没重启,以为生效了。记得用 modprobe.d 配好,窗口期重启一次。
  • firewalld reload 把 SSH 断了:先确认 --add-service=ssh,再改其他规则;必要时在带外 KVM 上操作。
  • LOG 在热路径:LOG 本身很“贵”,还会引出磁盘/IO/用户态开销;分流到 NFLOG + 限速,或干脆只在冷路径打点。
  • Docker/容器和防火墙的叠加:Docker 会加 DOCKER / DOCKER-USER 链。统一在 DOCKER-USER 做“前置规则”(比如 mgmt_allow、blocklist),免得和 Docker 生成的规则打架。
  • NOTRACK 滥用:只对你完全信任的源和端口用;否则你失去了基于状态的保护。
  • 过度收紧超时:time_wait/fin_wait 太低,海量重用/重传会适得其反;先从保守值开始,观察后再收紧。

收尾:天亮前的那杯咖啡

凌晨 5:10,PPS 还在高位,但曲线稳了。KVM 小屏上 nf_conntrack_count 从 370 万回到 190 万,softirq 也不再疯长。

我把 LOG 的频率再降了一格,又把规则表导出留档,最后在机房门口喝完那杯已经不太热的咖啡。

这种夜里修防火墙的活儿,像是在码头绑缆绳——看似是几条规则的顺序、几个参数的数字,真正稳住的,是系统的“气”。

如果你也在一台 CentOS 7 的香港边界机前:

把“已建立连接”放到最前,把“多对多”交给 ipset,把“不需要状态”的 UDP 从 conntrack 中搬走;再把网卡队列排好、缓冲拉足。等天色发白,你会发现,业务没变、机器没换,路就是比刚才宽了。

附:一次性脚本(可按需裁剪)

#!/bin/bash
set -e

# 0) 工具
yum install -y conntrack-tools ipset iptables-services ethtool

# 1) conntrack 参数
cat >/etc/sysctl.d/99-nf-conntrack.conf <<'EOF'
net.netfilter.nf_conntrack_max=4000000
net.netfilter.nf_conntrack_buckets=1000000
net.netfilter.nf_conntrack_tcp_timeout_established=300
net.netfilter.nf_conntrack_tcp_timeout_close_wait=60
net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30
net.netfilter.nf_conntrack_tcp_timeout_time_wait=15
net.netfilter.nf_conntrack_udp_timeout=15
net.netfilter.nf_conntrack_udp_timeout_stream=60
EOF
sysctl --system

# 2) ipset
ipset create mgmt_allow hash:ip -exist
ipset create blocklist  hash:ip -exist
ipset create probe_src  hash:ip -exist

# 示例:加入你的管理 IP、黑名单、探测源
ipset add mgmt_allow 203.0.113.10 -exist
ipset add blocklist 198.51.100.0/24 -exist
ipset add probe_src 192.0.2.10 -exist

# 3) iptables 基线
iptables -I INPUT 1 -i lo -j ACCEPT
iptables -I INPUT 2 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -I INPUT 3 -m conntrack --ctstate INVALID -j DROP
iptables -I INPUT 4 -m set --match-set blocklist src -j DROP
iptables -I INPUT 5 -m set --match-set mgmt_allow src -p tcp --dport 22 -j ACCEPT
iptables -I INPUT 6 -p tcp -m multiport --dports 80,443 -j ACCEPT
iptables -A INPUT -m limit --limit 10/second --limit-burst 50 -j NFLOG --nflog-group 10 --nflog-prefix "FW_DROP:"
iptables -A INPUT -j DROP

# 4) 原始表 NOTRACK(可选:对可信 UDP)
iptables -t raw -I PREROUTING 1 -m set --match-set probe_src src -p udp --dport 443 -j CT --notrack
iptables -I INPUT 7 -m set --match-set probe_src src -p udp --dport 443 -j ACCEPT

# 5) 保存(若使用 iptables-services)
service iptables save || true
systemctl enable iptables
systemctl restart iptables

需要把 nf_conntrack hashsize 固化到启动:

echo "options nf_conntrack hashsize=1000000" >/etc/modprobe.d/nf_conntrack.conf 并安排重启窗口。
目录结构
全文