iptables端口转发与负载均衡实战:DNAT/SNAT及statistic模块详解
2026/9/11 13:32:20 网站建设 项目流程

1. 案例引入:为什么端口转发和负载均衡要放在一起讲

iptables 在 Linux 服务器上的地位,说白了就是一道保安闸口。日常工作中大家用得最多的是封 IP、限端口、挡扫描,也就是所谓的安全策略。但 iptables 的能力远不止“拦”这一项,它还能做流量调度,把进到本机的数据包按规则扔给别的机器去处理。端口转发和负载均衡,就属于这种“放”和“导”的操作。

我接手过不少业务场景,最典型的一个:后端用三台 Web 服务器跑同样的服务,前端一台 Linux 网关做统一入口。外部用户只知道网关的 IP,所有请求先落到这台网关,再由网关把流量分发给后端三台机器。这里既要实现端口转发,也要让每次请求能比较均匀地分摊到不同后端,避免某一台被压垮。这就是标题里“端口转发 + 负载均衡”同时出现的意义。

本案例面向的是有一定 iptables 基础、想深入理解 NAT 和流量调度的朋友。看完之后,你不仅能手动敲出端口转发规则,还能用 statistic 模块做简单的轮询分发,搞清楚 DNAT、SNAT、PREROUTING、POSTROUTING 各自的职责边界。对于刚接触防火墙的人来说,这篇文章也会把一些容易踩坑的概念一次性讲透。


2. 动手之前:先把 NAT 流程和链的关系理清楚

2.1 数据包进入 Linux 主机的完整路径

iptables 规则不是随便往哪个链里一塞就完事的,写规则之前,得先在脑子里把数据包的行走路线画出来。一个从外部网络进入网关的 TCP 包,会依次经历这几个关键检查点:

  • 网卡接收数据包后,进入 PREROUTING 链。此时包的目标地址还是原始的公网地址或虚拟 IP。
  • 如果 PREROUTING 里有 DNAT 规则,包的 dest IP 会被改写,变成内网后端服务器的地址。
  • 路由决策发生在这个改写之后。内核会根据新的目标地址决定把包从哪张网卡送出去。
  • 如果是发往本机某个端口的,会经过 INPUT 链进入本地进程;如果是要转发的,则经过 FORWARD 链。
  • 包离开本机之前,还会经过 POSTROUTING 链。在这里做 SNAT(源地址转换),把包的源 IP 从网关内网口地址改成网关自身,这样后端服务器回包时才知道往哪里回。

这个流程是理解所有端口转发规则的基础。很多人在本机写了 DNAT 规则却不生效,就是因为漏了 FORWARD 链放行,或者忘了做 SNAT。

2.2 DNAT 和 SNAT 的分工,很多人搞反了

DNAT 全称 Destination Network Address Translation,作用是把目标地址改写。比如外部访问 203.0.113.10:8080,DNAT 把它改成 192.168.1.10:80。改写动作发生在 PREROUTING 链,因为这个阶段还没做路由决策,改完目标之后内核才能正确选择合适的出口。

SNAT 全称 Source Network Address Translation,作用是把来源地址改写。在多机转发的场景下,后端服务器的回包目标是 203.0.113.10 这个外网 IP,但它拿不到路由,所以必须在网关发出数据包时把源地址改成网关自己的内网 IP。这样后端回包会先回到网关,网关再根据之前的连接跟踪记录把回包转给外网客户端。

一句话记忆:进包改目标,出包改来源。前者帮你知道“要去哪”,后者帮对方知道“从哪来”。

2.3 为什么开启转发还需要额外放行

Linux 内核出于安全考虑,默认是关闭 IP 转发的。如果 net.ipv4.ip_forward 不是 1,就算你写了一大堆 DNAT、SNAT 规则,数据包到了主机后也会被直接丢弃,根本不会走 FORWARD 链。

有的朋友已经开启了 ip_forward,但规则还是不生效,这时候要检查 FORWARD 链的默认策略。如果默认策略是 DROP,你需要显式放行从内网口到外网口的数据流。常见的情形是:外部客户端 —— 网关 eth0 —— 内网后端服务器。这种情况下,FORWARD 链至少要有两条规则配合,一条放行外部进来的方向,一条放行后端回包的方向,或者直接用状态匹配放行已建立的连接。

提示:检查 ip_forward 是否开启,可以执行sysctl net.ipv4.ip_forward。如果输出的是 0,运行sysctl -w net.ipv4.ip_forward=1临时开启。如果要永久生效,在/etc/sysctl.conf里加上net.ipv4.ip_forward = 1


3. 端口转发的标准落地步骤:场景与命令逐条解析

3.1 一个最简单的转发场景:外部端口映射到内网 Web 服务

先从一个最常见的需求说起:服务器只有一个公网 IPv4 地址,但你内网有两台服务,一个跑在 80 端口,一个跑在 8080 端口,你需要让外部用户通过不同端口访问不同内网服务。

假设网关公网网卡是 eth0,IP 为 203.0.113.10,内网网卡是 eth1,IP 为 192.168.1.254。内网有一台 Web 服务器 192.168.1.100,监听 80 端口。你的目标是让外部用户访问 203.0.113.10:80 时,实际访问到 192.168.1.100:80。

需要配置的规则其实只有三条核心步骤:

# 1. 开启内核转发 sysctl -w net.ipv4.ip_forward=1 # 2. PREROUTING 链上做 DNAT,改写目标地址 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:80 # 3. POSTROUTING 链上做 SNAT,改写源地址 iptables -t nat -A POSTROUTING -d 192.168.1.100 -p tcp --dport 80 -j SNAT --to-source 192.168.1.254

这里先解释一下第三条规则为什么要限制目标地址和端口。如果不加-d 192.168.1.100 -p tcp --dport 80,说明所有从本机出去的数据包,只要源地址是内网网段的,都会把源地址改成 192.168.1.254。这在本例中问题不大,因为网关一般只服务这些内网主机。但更严谨的写法是只在去往特定后端服务器的流量上做 SNAT,这样能避免影响其他正常的 NAT 流量。

很多教程到这里就结束了,但实战中还有个容易漏掉的关键点:FORWARD 链放行。如果 FORWARD 默认策略是 ACCEPT,那么上述三条规则就够了。但如果你的 FORWARD 链是被收紧过的,还需要增加放行规则。

# 放行外部访问内网 Web 的数据包 iptables -A FORWARD -i eth0 -o eth1 -d 192.168.1.100 -p tcp --dport 80 -j ACCEPT # 放行内网 Web 回包数据包 iptables -A FORWARD -i eth1 -o eth0 -s 192.168.1.100 -p tcp --sport 80 -m state --state ESTABLISHED,RELATED -j ACCEPT

有些环境里,回包方向的状态匹配并不好写,因为涉及连接跟踪状态下 SNAT 的处理。更稳妥的写法是把回包方向也做成无条件放行,只限制源地址为内网 Web 服务器、目标地址为任意地址的数据包:

iptables -A FORWARD -i eth1 -o eth0 -s 192.168.1.100 -p tcp --sport 80 -j ACCEPT

实测下来,对于安全要求不高的内网环境,后一种写法故障率低,排查起来也简单。

3.2 多端口批量转发:用 REDIRECT 还是多写几条 DNAT

如果内网有多个服务都需要映射出来,比如 80、443、3306 都要对外开放,有些人的第一反应是复制好几条 DNAT 规则。如果你的公网 IP 和端口分配明确,这样做是没问题的。但如果服务数量多,写起来烦,维护也累。

还有一种场景是透明代理式的端口跳转。比如网关本机开了某个代理端口,想强制把所有 HTTP 流量导入到代理里,这就是 REDIRECT 的应用场景。REDIRECT 可以理解为 DNAT 的一个特例:目标地址固定为本机地址,不需要指定外部目标 IP,内核会自动处理。

# 把所有进入 eth0 的 TCP 80 端口的流量重定向到本机 8080 端口 iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 8080

REDIRECT 适合的场景是“端口替换”,常用于透明代理、透明网关、调试环境。而 DNAT 适合的场景是“跨主机转发”,目标机器是另一台服务器。两者不要混用。比如你把 DNAT 目标写成 127.0.0.1,虽然内核也能处理,但这等于绕了一大圈,直接用 REDIRECT 反而更干净。

3.3 用 conntrack 状态匹配提升转发效率

iptables 的连接跟踪机制会在内核里记录每个连接的状态,最常用的四个状态是 NEW、ESTABLISHED、RELATED、INVALID。在 FORWARD 链中,合理的利用状态匹配可以减少规则数量,也能避免不必要的二次判断。

比如你放行了外部到内网 Web 的访问,后续回包方向如果还要做一堆严格限制,不仅啰嗦,而且容易出错。更聪明的做法是放行所有目标为本机的外部访问连接,然后在反向方向放行 ESTABLISHED 状态。

# 放行新建连接(外部到内网 Web) iptables -A FORWARD -i eth0 -o eth1 -d 192.168.1.100 -p tcp --dport 80 -m state --state NEW -j ACCEPT # 放行所有已建立连接的双向流量 iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

这里有个细节需要留意:SNAT/DNAT 对连接跟踪是有额外影响的。因为 NAT 会改写数据包内容,conntrack 条目里不仅记录五元组,还会记录 NAT 转换前后各是什么。用conntrack -L命令可以看到src=203.0.113.10 dst=192.168.1.100 sport=xxxx dport=80这样的转换信息。当你不确定某条连接是否被正确转换时,用这个命令排查非常直观。


4. 基于 iptables 的负载均衡:statistic 模块的使用思路

4.1 为什么选 iptables 做负载均衡而不是 Nginx、LVS

看到“负载均衡”四个字,很多人的第一反应是 Nginx、HAProxy、LVS。确实,这些专业组件在应用层和四层负载均衡上有很强的优势,支持健康检查、会话保持、动态权重调整。但你也要明白,它们都要求额外安装服务,并且有一定的学习成本。

在某些最小化部署的环境里,你不能或者不想装额外的软件包。比如一个只跑基础系统的网关,正好承担了防火墙职责,再用 iptables 的 statistic 模块做数据包层级的负载均衡,是最轻量、侵入性最小的方案。它不需要用户态进程参与,完全在内核里完成流量分发,性能开销极低。

不过,iptables 做负载均衡也有明显短板:它不会检查后端服务的健康状态。如果某一台后端服务挂了,流量还是会被硬性分发过去,客户端就会看到连接超时或拒绝。所以这个方案适合“后端服务本身有限制重试机制”的场景,或者搭配外部的健康检查脚本定时清理不可用节点。

4.2 statistic 模块的三种分发策略

iptables 的 statistic 模块提供了三种基本策略:nth、random、every。

nth 策略可以理解为轮询模式。你可以控制每 N 个包中取第几个包做某种动作。例如:

# 每 3 个包取 1 个。 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 192.168.1.100:80

这里的--packet 0表示从第 0 个包开始命中。如果你有三台后端,通常写法是:第 0 个包给后端 A,第 1 个包给后端 B,第 2 个包给后端 C。

random 策略按概率分发,适合后端机器性能不一致、想按权重分配流量的场景。例如一台机器性能好,承担 50% 流量,另一台承担 30%,第三台承担 20%,可以这样写:先按 50% 概率转发到后端 A,剩下的 50% 里再按 60% 概率转发到后端 B,最后剩下的全给后端 C。

every 策略也常被理解为均匀分发,但它的计数粒度是按“匹配机会”来的,和 nth 有些微差别。在实际使用中,nth 更容易理解成轮询,every 更适合做周期性的抽取。

4.3 nth 轮询实现三台后端机器分发

假设场景如下:外部访问网关的 80 端口,网关将流量尽量均匀地分发给三台后端 Web 服务器,分别是 192.168.1.101、192.168.1.102、192.168.1.103。三台后端都监听 80 端口。

规则如下:

iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 192.168.1.101:80 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -m statistic --mode nth --every 3 --packet 1 -j DNAT --to-destination 192.168.1.102:80 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -m statistic --mode nth --every 3 --packet 2 -j DNAT --to-destination 192.168.1.103:80

注意,这三条规则之间是有顺序依赖的。第一条规则命中第 0 个包,第二条规则命中第 1 个包,第三条规则命中第 2 个包。因为 iptables 规则是按顺序匹配的,如果把第二条放到第一条前面,实际效果就会错乱。你最好严格维持这个顺序,或者干脆写到一个脚本里固化下来。

另外一个容易忽略的点:--every 3里的 3 代表计数周期,而不是包的总数。只要计数器达到 3 就会重置,所以三条规则必须使用相同的--every 3,否则轮询会失去意义。

4.4 random 分发实现权重比例

以三台后端为例,假设 192.168.1.101 性能最好,给它 50% 流量;192.168.1.102 次之,给它 30%;192.168.1.103 最弱,给它 20%。

# 50% 流量给 192.168.1.101 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -m statistic --mode random --probability 0.50 -j DNAT --to-destination 192.168.1.101:80 # 剩余流量中 60% 给 192.168.1.102,即 30% 总流量 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -m statistic --mode random --probability 0.60 -j DNAT --to-destination 192.168.1.102:80 # 剩余流量给 192.168.1.103 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.103:80

这里的关键是概率的计算链:第一条规则命中后,后面规则不再处理;第一条没命中,继续执行第二条。所以第二条写的是在剩余流量中概率 60%,换算成总流量就是 (1-0.5) * 0.6 = 0.3,正好是 30%。第三条不设概率,承接所有剩余流量。

还有一种做法是给每条规则单独设置绝对概率,但因为 iptables 是顺序匹配,后一条规则只能在“前一条没命中”的前提下运行,所以必须用条件概率去换算。我见过不少朋友在这里写错,直接用 0.5、0.3、0.2 三个值,结果第三条永远没有流量进入。这是 random 模式最常见的坑。


5. 动手实操:从环境准备到规则验证

5.1 环境准备清单

如果你要完整复现这个案例,建议准备三台机器,或者用虚拟化平台开三个虚拟机。网络拓扑如下:

  • 网关机(Linux):两张网卡,eth0 连接模拟外网,IP 203.0.113.10;eth1 连接内网,IP 192.168.1.254。
  • 后端服务器 1:192.168.1.101,Web 服务监听 80 端口。
  • 后端服务器 2:192.168.1.102,Web 服务监听 80 端口。
  • 后端服务器 3:192.168.1.103,Web 服务监听 80 端口。

在开始配置 iptables 之前,先把后端服务器的 Web 服务跑起来。最简单的验证方式是用 Python 起一个临时 HTTP 服务:

# 在后端 1 上执行 python3 -m http.server 80 --bind 0.0.0.0

为了让结果可区分,你可以把每台后端服务的默认页面内容改成不同文字,比如“Server 1 response”。这样通过网关访问时,能看到每次返回的服务器标识,方便验证负载均衡是否生效。

5.2 一次性脚本:完整规则下发与解释

以下脚本适合直接部署在网关上。注意,脚本内会先清理旧的 NAT 规则,避免重复追加导致规则膨胀。

#!/bin/bash # 网关内网 IP LAN_IP=192.168.1.254 # 网关公网 IP WAN_IP=203.0.113.10 # 后端服务器 BACKEND_1=192.168.1.101 BACKEND_2=192.168.1.102 BACKEND_3=192.168.1.103 # 1. 开启 IP 转发 sysctl -w net.ipv4.ip_forward=1 # 2. 清空 nat 表规则,避免历史规则影响 iptables -t nat -F iptables -t nat -X # 3. 清空 filter 表 FORWARD 链规则(谨慎:如果你还有其他过滤规则,不要直接 F,建议逐条删除) # iptables -F FORWARD # 4. 设置 filter 表 FORWARD 链默认策略为 ACCEPT,方便测试 iptables -P FORWARD ACCEPT # 5. 在 nat 表 PREROUTING 中配置 DNAT 负载均衡 iptables -t nat -A PREROUTING -d $WAN_IP -p tcp --dport 80 -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination $BACKEND_1:80 iptables -t nat -A PREROUTING -d $WAN_IP -p tcp --dport 80 -m statistic --mode nth --every 3 --packet 1 -j DNAT --to-destination $BACKEND_2:80 iptables -t nat -A PREROUTING -d $WAN_IP -p tcp --dport 80 -m statistic --mode nth --every 3 --packet 2 -j DNAT --to-destination $BACKEND_3:80 # 6. 在 nat 表 POSTROUTING 中配置 SNAT,使后端服务器能从网关回包 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

这里用 MASQUERADE 而不是 SNAT,是因为 MASQUERADE 能自动获取 eth0 的当前 IP,适合动态 IP 的场景。如果你确定公网 IP 永远不会变,用 SNAT 性能略好一点。两者本质区别在于 MASQUERADE 每次发送数据包时要查询出口网卡的 IP 地址,开销略高一点点。

5.3 从外网客户端验证轮询效果

模拟外网客户端可以直接用网关的网络命名空间、虚拟机或者其他机器。验证步骤:

  1. 在外网客户端执行curl http://203.0.113.10连续多次。
  2. 查看每次返回内容中的服务器标识。
  3. 正常情况下,三次请求会分别落到 Server 1、Server 2、Server 3。

如果每次都落在同一台服务器,优先检查 PREROUTING 链规则顺序是否正确。如果某个后端从来收不到流量,可以检查 DNAT 规则里--packet值是否写错。

还有一种情况:curl 本身会复用连接,如果服务端开启了 HTTP keep-alive,连续多次 curl 时可能使用同一条 TCP 连接,导致 nth 计数器只在连接建立初期生效。换句话说,负载均衡是按连接粒度分流,不是按 HTTP 请求粒度分流。如果你想要“每个请求都均匀分发”,那就需要在应用层做调度,Nginx 的 upstream 更适合这个需求。iptables 的 nth 模式只能保证 TCP 连接建立时尽量均匀地分散到不同后端。

5.4 查看连接跟踪表,确认 NAT 是否生效

当规则配置完成且客户端发起访问后,可以在网关上执行:

conntrack -L | grep 203.0.113.10

如果系统没有安装 conntrack 命令,可以用cat /proc/net/nf_conntrack查看原始内容,或者先加载模块:

modprobe nf_conntrack modprobe nf_conntrack_ipv4

输出里能看到一行类似这样的内容:

tcp 6 431999 ESTABLISHED src=192.168.1.100 dst=203.0.113.10 sport=34567 dport=80 src=203.0.113.10 dst=192.168.1.100 sport=80 dport=34567 [ASSURED] mark=0 use=1

重点看后半段的 NAT 转换信息:src=203.0.113.10 dst=192.168.1.100表示原始的数据包从外部客户端发往网关,经过 DNAT 后目标是 192.168.1.100。这说明 PREROUTING 链的 DNAT 规则已经生效。如果只看到前半段而没有后半段,说明 NAT 没有做成功,大概率是因为 PREROUTING 链里没有匹配到对应规则。


6. 踩坑记录:端口转发和负载均衡最容易翻车的地方

6.1 本机访问 DNAT 不生效的真正原因

许多人在配置完 PREROUTING 链的 DNAT 后,直接在网关本机上执行curl http://203.0.113.10:80来测试,发现根本不通。这不是规则写错了,而是因为本机访问本机 IP 时,数据包并不走 PREROUTING 链。它走的是路由决策之后的 OUTPUT 链。

Linux 里有一个专门用于本机发出流量改写的链,叫 OUTPUT(位于 nat 表)。如果确实需要让网关本机也通过公网 IP 访问后端服务,可以额外在 OUTPUT 链加一条类似的 DNAT 规则:

iptables -t nat -A OUTPUT -d 203.0.113.10 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.101:80

但这会破坏负载均衡的轮询逻辑,因为只有一条规则,流量会全部打到这一台后端。所以更推荐的做法是:测试时从外部客户端发起请求,而不是在网关上自测。

6.2 连接跟踪模块未加载导致规则忽好忽坏

iptables 的 NAT 功能依赖连接跟踪机制。如果内核没有加载nf_conntrack相关模块,执行 DNAT 匹配时可能会报错,或者规则虽然写进去了但流量不转换。

可以用以下命令确认模块是否加载:

lsmod | grep nf_conntrack

如果发现没有加载,手动加载:

modprobe nf_conntrack modprobe nf_nat

在 Ubuntu/Debian 上通常不需要手动干预,因为 iptables 服务启动时会自动加载。但在精简内核或 Docker 容器场景下,这个问题容易暴露。如果conntrack -L输出为空或命令不存在,先排查这一步。

6.3 回程路由不对称,导致响应数据包丢失

端口转发的本质是双向的:外网发进来的包要正确改写目标,后端返回的包也要正确改写源。有一个常见的工程场景是“多网关”环境,即后端服务器的默认网关不是这台 Linux 网关。

举个例子,后端服务器 192.168.1.100 的默认网关被设为另一台路由器,而不是 192.168.1.254。当网关把外部请求转发给 192.168.1.100 后,后端处理完直接找自己的默认网关把响应发出去了。此时响应包会绕过 Linux 网关,直接回到外部客户端,但这个包的源 IP 是 192.168.1.100,外部客户端根本不认识它,连接直接失败。

解决办法有二:

  • 修改后端服务器默认路由,让下一跳指向 Linux 网关。
  • 如果后端服务器不能改路由,可以在后端加策略路由,确保发往外部客户端网段的流量走 Linux 网关。

这个问题的排查方式也简单:在后端服务器上tcpdump -i eth0 port 80,如果能看到请求进来但响应发出去的下一跳不对,立刻就能发现抓包里的源 IP 和目标 IP 异常。

6.4 某些协议(如 FTP)的 NAT 特殊处理

常见的 HTTP、HTTPS、SSH 在 NAT 场景下都没有问题。但 FTP 比较特殊,它使用两个连接:一个控制连接(21 端口),一个数据连接(通常是动态端口)。在 DNAT 转发 FTP 时,如果没有加载nf_conntrack_ftp模块,数据连接将无法正确建立。

如果你在内网跑 FTP 服务,且需要通过网关转发,建议提前加载:

modprobe nf_conntrack_ftp

然后在规则放行时开放足够的数据端口范围,或者在 FORWARD 链使用状态 RELATED 放行。这个坑我踩过不止一次,如果不留意,FTP 会表现得时好时坏:登录正常,但列目录或传文件卡死。

6.5 规则的持久化,重启后一切归零

很多人手动敲完规则后觉得大功告成,结果一重启服务器,规则全部没了,因为 iptables 规则默认是不持久化的。解决方式取决于你的发行版。

在 CentOS/RHEL 系列上,可以用iptables-save > /etc/sysconfig/iptables保存,系统启动时由 iptables 服务自动加载。在 Ubuntu/Debian 上,可以安装iptables-persistent包,它会读取/etc/iptables/rules.v4/etc/iptables/rules.v6

如果你的环境是 Docker 或 Kubernetes 节点,建议把规则写到系统启动脚本里,或者用 systemd unit 文件统一管理。单纯依赖iptables-save在某些容器场景下会因为 Docker 自己维护链而出现冲突。


7. 进阶技巧:负载均衡 + 端口转发的组合玩法

7.1 把本机多个端口分别转到不同后端集群

有时候你只有一台公网服务器,但内网有多个不同业务。比如 80 端口给 Web 集群,443 端口给另一个 HTTPS 集群,3306 端口给数据库集群。你可以在同一台网关的 PREROUTING 链上分别写多组 DNAT 规则,互不干扰。

iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 192.168.1.11:80 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -m statistic --mode nth --every 3 --packet 1 -j DNAT --to-destination 192.168.1.12:80 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 443 -m statistic --mode nth --every 2 --packet 0 -j DNAT --to-destination 192.168.1.21:443 iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 443 -m statistic --mode nth --every 2 --packet 1 -j DNAT --to-destination 192.168.1.22:443

这里的关键是每一组规则使用自己独立的计数周期。因为 iptables 的 statistic 模块是分别在每条规则自己的计数器上工作的,不会跨端口造成干扰。不过要注意规则顺序:--dport 80的规则和--dport 443的规则不要在字段上写反,否则计数会串线。

7.2 源地址哈希:让同一个客户端始终访问同一台后端

nth 轮询的代价是同一个客户端的多个连接可能被分发到不同后端。如果后端之间有共享 session 的依赖,比如用户登录状态存在单机内存里,就会出问题。这时可以给同一来源 IP 做哈希或固定映射。

iptables 本身没有内置源地址哈希的 NAT 模块,但可以使用--match hashlimit做流量限制,严格说它并不适合做源地址一致的负载均衡。更实用的做法是借助 Linux 策略路由或 ip rule 实现。不过这属于另一个话题了。用 iptables 实现最简单的会话保持,可以尝试 random 模式并提高粒度,但也不能完全保证。

如果业务确实需要 sticky session,建议考虑 LVS 的 SH 调度算法(Source Hashing),或者 Nginx upstream 的 ip_hash 参数。iptables 方案在这个场景下并不优雅。

7.3 健康检查不存在的替代方案:用 conntrack 快速隔离故障节点

前面提到过,iptables 自身的 load balancing 不感知后端健康状态。但我们可以利用 conntrack 数据做一定程度的故障隔离。思路是写一个定时检查脚本,探测后端端口是否连通,不通时动态删除或插入规则。

#!/bin/bash # 简单健康检查:每 10 秒检查一次后端 80 端口,失败则临时移除 DNAT 规则 BACKEND=("192.168.1.101" "192.168.1.102" "192.168.1.103") while true; do for ip in "${BACKEND[@]}"; do if ! timeout 2 nc -zv "$ip" 80 >/dev/null 2>&1; then echo "$ip down" fi done sleep 10 done

实际生产环境里,我见过不少运维直接用 keepalived + LVS 替代这套轻量方案,因为健康检查的要求越加越多,脚本越来越复杂,最终不如用成熟方案。如果你只是临时过渡,或者后端就三五台机器,也不追求自动故障转移,那么 iptables 方案完全够用。

注意:动态删除规则时要尽量避免误删其他规则。建议为每条 DNAT 规则增加注释,例如-m comment --comment "web-backend-101"。删除时先iptables -t nat -L PREROUTING -n --line-numbers找到规则编号,再按编号删除,这样最稳妥。


8. 写在最后:这套方案的边界与取舍

iptables 端口转发和基于 statistic 模块的负载均衡,很适合小规模、低成本、不想额外引入组件的场景。它最大的优势是零依赖、性能高、规则透明,出了问题看一眼规则就能明白。但它也不是万能的:没有健康检查,没有精细的会话保持,没有动态权重调整。做演示环境、做实验、做小型办公网络,完全够用;做大规模高并发生产系统,我建议还是老老实实上 LVS 或 Nginx。

我个人在实际操作中的体会是:写 iptables 规则千万不能只动手不动脑,每一行命令都要想清楚它挂在哪个链、作用在哪个方向、影响哪些流量。把这个案例完整做一遍,你不仅会明白端口转发和负载均衡怎么配,更重要的是能建立一套“数据包走到哪一步会被怎么处理”的心智模型。后续不管碰到 TUN 模式代理、Docker 端口映射,还是 Kubernetes 的 Service 转发,排查思路都大同小异。

最后分享一个调试小技巧:在所有 iptables 规则之前不要加-j DROP这种终止动作,先用-j LOG记录到内核日志,再用tail -f /var/log/kern.log观察匹配情况。等确认流量方向和规则逻辑都正确之后,再把 LOG 规则移除,换成 ACCEPT 或 DNAT。这样调试效率会高很多,也能避免因为规则顺序问题把自己锁在外面。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询