在 Kubernetes 集群中,如果同一个节点上的大量 Pod 频繁访问同一个外部地址(比如一个数据库、一个第三方 API 网关),一段时间后这些 Pod 会突然集体出现连接超时,报错信息通常是 connection timed out 或者 cannot assign requested address。这种故障有一个非常典型的特征:重启应用只能缓解几分钟,随后问题再次出现,而且节点级别的重启才能彻底恢复。这大概率就是 SNAT 端口耗尽问题。本文从原理、排查和缓解三个层面来完整拆解这个问题。
一、SNAT 端口耗尽的底层原理
要理解端口耗尽,先要理解 Kubernetes 中 Pod 出节点流量的路径。在默认的 iptables 模式下,如果 Pod 的 IP 不参与集群外部路由,那么 Pod 发往集群外部(非 Pod CIDR、非 Service CIDR)的流量会经过 MASQUERADE 规则做源地址转换,也就是把源 IP 从 Pod IP 改写成节点 IP。这个动作由 netfilter 的连接跟踪模块(conntrack)记录和维护。
SNAT 转换要保证三元组唯一:源 IP、目标 IP、目标端口。转换后源 IP 固定为节点 IP,如果目标 IP 和目标端口也固定,那么可变的只剩源端口。Linux 的临时端口范围默认通常是 32768 到 60999,共约 28232 个端口。也就是说,一个节点访问同一个目标 IP:Port 的并发连接数上限就是这个数字。
更关键的是,conntrack 表项在 TCP 连接关闭后不会立即删除,处于 TIME_WAIT 状态的连接会继续占用表项,默认超时时间约 120 秒。如果应用是短连接模式,比如每秒发起 500 个新连接访问同一个目标,那么稳态下同时存在的表项数量约为 500 × 120 = 60000,远超 28232 的上限,端口耗尽就是必然结果。这也是为什么短连接高频访问比长连接更容易触发这个问题。
可以通过下面的命令查看和调整临时端口范围:
# 查看当前 ephemeral port 范围 cat /proc/sys/net/ipv4/ip_local_port_range # 输出示例:32768 60999 # 计算可用端口数 echo $((60999 - 32768 + 1)) # 临时扩大范围(重启失效) sysctl -w net.ipv4.ip_local_port_range="10240 65535" # 持久化配置 echo "net.ipv4.ip_local_port_range = 10240 65535" >> /etc/sysctl.conf sysctl -p
二、如何确认端口耗尽正在发生
端口耗尽的排查需要节点级别的操作权限。第一步是看内核日志,当 conntrack 表插不进去或者端口分配失败时,dmesg 中通常会有明确记录。
# 查看内核报错 dmesg | grep -iE "conntrack|nf_conntrack" # 典型输出: # nf_conntrack: nf_conntrack: table full, dropping packet # 查看 conntrack 表使用量和上限 sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max # 列出与某个目标 IP 相关的 SNAT 表项 conntrack -L -d 203.0.113.10 --reply-src any -p tcp # 或者直接统计 conntrack -L | grep "203.0.113.10" | wc -l
如果 nf_conntrack_count 接近 nf_conntrack_max,说明表满了;如果表没满,但指向同一个目标 IP 和端口的表项数量接近临时端口总数,那就是典型的 SNAT 端口耗尽。另外,还可以通过节点上的抓包确认:SYN 包发出去了但没有收到任何回应,说明源端口分配阶段就失败了,流量根本没出得了节点。
在 Kubernetes 层面也有辅助手段。如果使用的是 flannel 或者 kubenet 网络,Pod 出网必然走 masquerade,耗尽概率高;如果配置了某些 CNI 的直连模式,Pod IP 直接可达外部,则不存在 SNAT 问题。可以查看 kube-proxy 的 iptables 规则中 MASQUERADE 相关的条目,确认哪些流量在做源地址转换:
iptables-save | grep -i masquerade | head -20 # 关注形如这样的规则: # -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE # -A CNI-xxxx -d ! 10.0.0.0/8 -j MASQUERADE
三、缓解与根治方案
缓解手段要结合架构现状来选,下面按投入成本从低到高排列。
方案一:应用层连接复用。这是最根本的手段。把短连接改成连接池加长连接,例如 HTTP 客户端启用 keep-alive,数据库驱动启用连接池。连接复用后,单位时间新建连接数大幅下降,conntrack 表项的创建速度随之下降,端口耗尽自然消失。需要注意的是长连接要配置合理的心跳和最大存活时间,避免服务端主动断连造成连接失效。
方案二:调大端口范围和 conntrack 上限。这是快速止血方案。把 ip_local_port_range 扩大到 10240 65535 可以把可用端口提升到 5.5 万以上,同时调大 nf_conntrack_max 并缩短 TIME_WAIT 相关超时。开销小,见效快,但只是把上限抬高,高流量场景下仍可能再次耗尽。
# 调大 conntrack 表上限 sysctl -w net.netfilter.nf_conntrack_max=1048576 # 缩短 TIME_WAIT 超时(默认 120 秒) sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
方案三:增加出口维度。端口耗尽的本质是“单一源 IP + 单一目标”的组合瓶颈。可以通过多个出口来稀释:一是使用 egress gateway 方案(比如多个固定出口 IP 的代理池,按 Pod 或 namespace 分流);二是在云环境使用 NAT 网关的多个 EIP 做地址池;三是让不同节点分担流量,配合反亲和性把访问同一外部服务的 Pod 打散到不同节点。每个新增的源 IP 或目标出口都能线性扩展可用的连接组合数。
方案四:消除 SNAT 本身。如果网络环境允许,可以配置 CNI 的直连模式,让 Pod IP 直接对外可达。例如 Calico 里设置 natOutgoing: false(前提是 Pod CIDR 被外部路由感知),或者使用 Egress IP、Egress Gateway 等原生化方案,把源地址转换从节点维度迁移到专用出口,实现更精细的控制。
实际生产中通常是组合拳:先用方案一优化应用连接行为,同时用方案二抬高安全水位,最后视规模决定是否引入方案三或方案四。此外建议对 conntrack 使用率做监控告警,当 count 达到 max 的 80% 时提前介入,避免故障发生时才被动排查。
KubernetesSNAT端口耗尽修改时间:2026-08-31 08:12:51