ip_local_port_range 用尽后 bind 失败的 ephemeral port 监控与自动扩容脚本

10次阅读

bind失败主因常是ip_local_port_range耗尽而非端口被占;大量短连接使TIME_WaiT积,内核标记整个端口区间暂不可用,触发“Cannot assign requested address”错误。

ip_local_port_range 用尽后 bind 失败的 ephemeral port 监控与自动扩容脚本

为什么 bind 失败不一定是因为端口被占,而是 ip_local_port_range 耗尽

linux 的 ephemeral port 不是“用一个少一个”地永久占用,而是由 net.ipv4.ip_local_port_range 定义的区间(默认 32768 60999,共约 28K 端口)决定可用上限。当大量短连接(如 http client、gRPC outbound)密集建立又快速关闭时,TIME_WAIT 状态会卡住端口几秒,叠加高并发bind 就可能直接返回 Cannot assign requested address —— 这不是某个端口被占,而是整个范围已被内核标记为“暂不可用”。ss -s 显示的 tw 数量远超预期,或 /proc/sys/net/ipv4/ip_local_port_range 输出值过窄,就是典型信号。

怎么实时监控当前 ephemeral port 分配压力

不能只看 netstat -an | grep TIME_WAIT | wc -l,它漏掉未进入 TIME_WAIT 但已分配出去的端口。真正有效的指标是内核统计的已分配 ephemeral port 数:

  • cat /proc/net/snmp | awk '/^TcpExt/ && /EmbryonicRsts|SyncookiesFailed/ {print}' —— 看是否因端口不足导致 SYN 丢弃
  • awk '$1 ~ /InUse/ {print $2}' /proc/net/ip_vs_stats —— 不适用;改用:cat /proc/net/sockstat | grep "TCP: inuse" 中的 inuse 值,它反映当前所有已分配(含 ESTABLISHED/TIME_WAIT)的 TCP socket 数
  • 更准的是:awk '/^tcp/ {if($4=="01") c++} END{print c+0}' /proc/net/tcp* 统计所有处于 TIME_WAIT 的 socket(状态码 01),再结合 sysctl net.ipv4.ip_local_port_range 计算占用率

建议每 5 秒采样一次 inusetime_wait,当 inuse / (high-low) > 0.8 且持续 3 次,就触发扩容逻辑。

如何安全地动态扩容 ip_local_port_range

不能直接写死扩大到 1024 65535:低端口(

  • 推荐策略:从默认 32768 60999 扩至 16384 65535(共 ~49K),覆盖更多低频长连接场景,同时避开 0–1023 特权端口区
  • 执行命令:sysctl -w net.ipv4.ip_local_port_range="16384 65535",并写入 /etc/sysctl.conf 防重启丢失
  • 注意:该参数热生效,但**仅对新创建的 socket 生效**,已有连接不受影响;扩容后需观察 ss -stotal: 12345 是否缓慢回落,确认内核已开始使用新区间

自动扩容脚本的关键设计点

脚本不是“一发现高就扩”,而是要防抖、防误扩、可逆:

  • 必须加锁(如 flock -n /tmp/ip_local_port_range.lock),避免多实例并发修改
  • 记录上次扩容时间戳和原始值到 /var/run/ip_local_port_range.last,便于故障回滚
  • 检查是否已扩容过:sysctl net.ipv4.ip_local_port_range | grep -q "16384.*65535",避免重复操作
  • 拒绝在容器环境(/proc/1/cgroupdockerkubepods)中执行,因容器网络命名空间隔离,宿主机 sysctl 不生效
  • 示例判断逻辑片段:
    current=$(sysctl -n net.ipv4.ip_local_port_range) low=${current%% *}; high=${current##* } range=$((high - low)) inuse=$(grep "TCP: inuse" /proc/net/sockstat | awk '{print $3}') if (( $(echo "$inuse / $range > 0.85" | bc -l) )); then   # 执行扩容... fi

最易被忽略的是:扩容后不检查 net.ipv4.tcp_fin_timeoutnet.ipv4.tcp_tw_reuse 是否配合调整 —— 如果只扩 range 却放任 TIME_WAIT 堆积 60 秒,压力只会转移而非缓解。

text=ZqhQzanResources