引自:Solving the mystery of pods health checks failures in Kubernetes。原文中的某些描述并不清晰,本文作了调整。
很早以前,环境中的pod有时候会遇到健康检查失败的问题,但并没有什么明显表征,且几乎是立马就会恢复。由于这种情况很少发生,且不会对业务造成影响,因此起初并没有人关注该问题。
但后来发生的频率越来越高,导致开发人员频繁接收到deployment的健康告警。
第1步:查看日志
- Kubernetes worker的系统日志 -- 无异常
- kubelet 日志 -- 无异常
- Containerd 日志 -- 无异常
- CNI 日志 -- 无异常
- 检查最近失败的pod日志 -- 无异常
通过检查相关日志,并没有发现什么异常
第2步:tcpdump
在抓取的流量中发现,当kubelet给pod发送TCP SYN之后,pod会回复SYN-ACK,但kubelet并没有发送TCP ACK。在一段时间的重试之后,Kubelet会建立起一条TCP会话,因此该问题是随机发生的。
为以防万一,我们检查了TCP中的seq和ack序列号,并没有发现问题。
此时怀疑worker可能存在问题:是不是Kubelet没有处理接收到的报文?
第3步:ss
每秒调用一次"ss -natp"来查看kubelet进程连接,此时发现失败的连接卡在了SYN-SENT阶段,说明kubelet并没有接收到pod发来的SYN-ACK报文。
第4步:conntrack
使用conntrack查看TCP网络连接跟踪,发现有的连接卡在SYN-SENT状态(kubelet侧),有的连接卡在SYN-RECV(pod侧),但连接的源端口号看起来都类似。
在我们的环境中,设定了一个较大的源端口可选范围:
net.ipv4.ip_local_port_range=12000 65001
出现问题的源端口为30XXX或31XXX,非常类似。
第5步:ipvs
通过ipvsadm命令查看ipvs配置发现,所有卡住的连接都使用了Kubernetes的nodeport 保留端口
根因分析
至此,问题已经明了。当Kubelet初始化一条TCP连接时,会随机选择一个源端口号,例如31055。当TCP SYN到达pod之后,pod会向31055端口号回复一个TCP SYN-ACK报文。当该报文到达IPVS之后,由于已经存在一个端口号为31055的nodeport(Kubernetes loadbalance service),此时会将TCP SYN-ACK报文转发到对应的后端(其他pod),这样就导致Kubelet无法接收到回复的报文,无法建立连接。
解决办法
解决方式也很简单,设置如下内核参数即可,这样Kubelet在建立连接时就不会选择30000–32768的端口作为TCP源端口:
net.ipv4.ip_local_reserved_ports="30000–32768"
Kubernetes的nodeport保留端口为30000-32767,因此设置的
net.ipv4.ip_local_reserved_ports
为30000–32768
TIPs
net.ipv4.ip_local_port_range
的默认值为32768 60999
,正好和Kubernetes的nodeport保留端口错开,本文中描述的问题的源头也是因为修改了该内核参数,因此非必要不要修改内核参数!