背景
某日发现新搭的一套测试环境上业务系统告警, 绝大部分错误日志都是failed: Cannot assign requested address
分析
首先怀疑是网络问题, 登录POD内使用curl
、telnet
、ping
等检测工具排查均正常;
Google错误信息后发现failed: Cannot assign requested address
的出现主要是因为系统分配的客户端连接端口耗尽(建立一个连接需要四个部分:目标IP、目标端口、源IP、和源端口, 从同一个客户端发起的请求会主动变更的只有源端口), 无法建立socket连接导致;
已知Linux服务器上最大端口数量: 65535, 于是查看内核配置的可用范围, 实际最大可用只有28231
# sysctl -a | grep ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999
通过netstat -ae | grep -i time_wait | wc -l
查询tw数量基本打满; 且端口的释放默认是在60秒通过net.ipv4.tcp_fin_timeout = 60
控制
目前情况基本确认就是因为短时大量的请求, 导致本地端口来不及释放, 从而影响新的socket的创建
比较常见的有两种调整方式:
- 调低fin_timeout时间
net.ipv4.tcp_fin_timeout
- 调高可用端口范围
net.ipv4.ip_local_port_range
目前我们使用的是云厂商提供的K8S服务, 联想到在我们以前使用的集群中并没有这种问题出现且tw数量一直是被控制在5000, 那是什么导致tw数量被压制呢?
答案就是net.ipv4.tcp_max_tw_buckets
, 该参数设置最大同时保留的timewait socket数量, 一旦达到这个数值, 处于等待关闭的tw会被立即销毁, 一般是作为简单的ddos防御手段
在满足当前服务器主动关闭连接, 且TIME_WAIT数量等于或大于net.ipv4.tcp_max_tw_buckets预设的数值时, 可能出现两种异常情况
1. 对端服务器发完最后一个FIN包, 没有收到当前服务器发送的最后一个ACK, 又重发了一个FIN包, 因为之前的TW被销毁了无法响应, 本地服务器会收到一个Reset包; 这种情况没什么影响, 因为链接本来就要关闭;
2.TW连接立即释放, 刚刚释放的端口可能被立即使用, 如果此时对端服务器没有释放连接, 当前服务器就会收到对端发来的一个Reset包, 如果当前服务器是代理服务器, 会直接返回一个502;
调整
K8S内调整POD的内核参数, 建议使用initContainer避免修改docker和kubelet配置需要重启服务(initContainer配置与container平级)
apiVersion: apps/v1
kind: Deployment
metadata:
name: taqu-app
spec:
template:
spec:
initContainers:
- command:
- sysctl
- -w
- net.ipv4.tcp_max_tw_buckets=10000
image: centos:7
name: init-sysctl
securityContext:
privileged: true
其他修改方式: 在 Kubernetes 集群中使用 sysctl
小结
其实可以综合考虑增加可用端口范围、减少fin_timeout时间、适当增减max_tw_bucket数量
未解之谜
- 为什么
ss -s
输出的结果和netstat ae| grep -i time_wait
的结果不一样 - cgroup隔离网络空间具体隔离了哪些资源