记一次容器环境下出现 Address not available
发布网友
发布时间:2024-09-29 17:02
我来回答
共1个回答
热心网友
时间:2024-09-29 17:47
pod 创建后一段时间一直是正常运行,突然有一天发现没有新的连接创建了,业务上是通过 pod A 访问 svc B 的 svc name 的方式,进入 pod 手动去 wget 一下,发现报错了 Address not available。为何会报错这个呢?
错误代码参考连接:[errno.3]
通过 netstat -an 查看到连接 svc 的地址,其中 estab 状态的连接数,已经到达了可用的随机端口数量阈值,无法在新建连接了。
最后通过修改了内核参数随机端口 net.ipv4.ip_local_port_range 端口范围才得以解决的。
Linux 的内核定义的随机端口 32768 ~ 60999,可能在业务设计场景中,比较容易被忽略的,我们都知道,每一个 TCP 连接都是由四元组(源 IP,源端口,目的 IP,目的端口)构成的,只要四元组中其中一个元组发生了变化,就可以创建一个 TCP 连接的。当一个 POD 要访问一个固定的目的 IP + 目的端口的时候,那么每一个 TCP 连接的变量就只剩下源端口是随机的了,所以如果在需求就是需要创建大量长连接的话,要么就调大内核随机端口,要么就调整业务。
相关内核参考连接:[ip-sysctl.txt]
手动调小了 net.ipv4.ip_local_port_range,之后进行复现。
同样的问题,分别尝试了 curl,nc,wget 命令,报错都不一样,这就犯难了。
那么就通过 strace 命令进程分析一下看看,跟踪指定系统调用名称 它们都会创建 socket(), 然后发现 wget/curl 命令是通过 connect() 函数,而 nc 命令先是是通过 bind() 函数调用, 如果报错就不会继续调用 connect() 函数了。
通过 B/S 架构的分析如下,connect() 是在客户端创建 socket 后建立的。
为什么 wget/curl 同样调用的是 connect() 函数报错的,为何报错还是不一样的?为什么 connect() 函数和 bind() 函数报错不一样?是不是所有情况下都是这样输出呢?
直接找了一台 Centos7.9 的系统,安装 curl 、wget、 nc 等工具,同样改小端口范围的情况下会出现如下报错 Cannot assign requested address,某些镜像(alpine、busybox)里,使用相同命令工具对相同的情况下报错会不同。因为这些镜像里可能为了缩小整个镜像大小,对于一些基础命令都会选择 busybox 工具箱(上面的 wget 和 nc 就来自于 busybox 工具箱里的,参考 busybox 文档:Busybox Command Help)来使用,所以就造成在问题定位方面困扰了。
Linux 系统中用于包含与错误码相关的定义:/usr/include/asm-generic/errno.h
容器环境下,端口配置最佳实践可修改范围理论上是 0~65535,但是 0~1023 是特权端口,已经预留给一下标准服务,如 HTTP:80,SSH:22 等,只能特权用户使用,同时也避免未授权的用户通过流量特征攻击等所以建议端口调大的话可以将随机端口范围限制在 1024-65535 之间。
如何正确配置 Pod 源端口普通 Pod 源端口修改方法从 kubernetes 社区得知可以通过安全上下文修改 securityContext,还有可以通过 initContainers 容器给特权模式 mount -o remount rw /proc/sys 的方式修改,此修改方式只会在 pod 的网络命名空间中生效。
hostnetwork 模式 pod 修改注意事项1.22+ 集群以上就不建议修改 net.ipv4.ip_local_port_range,因为这会和 ServiceNodePortRange 产生冲突。
Kubernetes 的 ServiceNodePortRange 默认是 30000~32767,Kubernetes 1.22 及以后的版本,去除了 kube-proxy 监听 NodePort 的逻辑,如果有监听的话,应用程序在选用随机端口的时候,会避开这些监听中的端口。如果 net.ipv4.ip_local_port_range 的范围和 ServiceNodePortRange 存在重叠,由于去掉了监听 NodePort 的逻辑,应用程序在选用随机端口的时候就可能选中重叠部分,比如 30000~32767,在当 NodePort 与内核 net.ipv4.ip_local_port_range 范围有冲突的情况下,可能会导致偶发的 TCP 无法连接的情况,可能导致健康检查失败、业务访问异常等问题。
大量创建 svc 的时候减少创建监听的步骤只是提交 ipvs/iptables 规则,这样可以优化连接性能。另一个就解决某些场景下出现大量的 CLOSE_WAIT 占用 TCP 连接等问题。在 1.22 版本之后就去掉了 PortOpener 逻辑。
kubernetes/pkg/proxy/iptables/proxier.go Line 1304 in f98f27b 具体是如何冲突的呢?测试环境是 k8s 1.22.10,kube-proxy 网络模式 ipvs。以 kubelet 健康检查为例,调整了节点的内核参数 net.ipv4.ip_local_port_range 为 1 024~65535。
部署 tcpdump 抓包,抓到有健康检查失败的事件后,停止抓包。看到 kubelet 是用节点 IP(192.168.66.27)+随机端口 32582 向 pod 发起了 TCP 握手 podIP(192.168.66.65)+80,但是 pod 在 TCP 握手时回 SYN ACK 给 kubelet 的时候,目标端口是 32582,却一直在重传。因为这个随机端口刚好是某一个服务的 nodeport,所以优先被 IPVS 拦截给规则后端的服务,但这个后端服务 (192.168.66.9) 并没有发起和 podIP(192.168.66.65)TCP 建连,所以后端服务 (192.168.66.9) 直接是丢弃的。那么 kubelet 就不会收到 SYN ACK 回应,TCP 无法建联,所以导致健康检查失败。
增加前置判断,通过 initContainers 容器修改的时候,如果 podIP 和 hostIP 不相等才修改 net.ipv4.ip_local_port_range 参数,避免误操作导致修改节点的内核参数。
如何正确配置 NodePort 范围在 Kubernetes中,APIServer 提供了 ServiceNodePortRange 参数(命令行参数 --service-node-port-range),该参数是用于限制 NodePort 或 LoadBalancer 类型的 Service 在节点上所监听的 NodePort 端口范围,该参数默认值为 30000~32767。在 ACK Pro 集群中,您可以通过自定义 Pro 集群的管控面参数修改该端口范围。具体操作,请参见自定义 ACK Pro 集群的管控面参数。
相关链接:[1] errno.3
man7.org/linux/man-page...
[2] ip-sysctl.txt
kernel.org/doc/Document...
[3] Busybox Command Help
busybox.net/downloads/B...
[4] securityContext
kubernetes.io/docs/task...
[5] Kubernetes社区PR
github.com/kubernetes/k...
[6] f98f27b
github.com/kubernetes/k...
[7] 自定义ACK Pro集群的管控面参数
help.aliyun.com/zh/ack/...