记一次kubernetes集群异常:kubelet连接apiserver超时

  • 时间:
  • 浏览:0
  • 来源:大发彩神排列三_大发神彩排列三官方

[root@c4-jm-i1-k8stest03 ~]# netstat -antpl |grep kubelet

tcp 0 0 127.0.0.1:10248 0.0.0.0:* LISTEN 23665/./kubelet

tcp 0 928 10.162.1.26:63876 10.132.106.115:6443 ESTABLISHED 23665/./kubelet

连接被hang住了,重启kubelet时会,一切又恢复了。

无独有偶,那末 的事情偏偏被什儿 人碰到了,接到线上小量node not ready的报警后,立刻上线查看,发现所有的node kubelet都报如下错误:

参考链接

1、https://github.com/kubernetes/kubernetes/issues/41916

2、https://github.com/kubernetes/kubernetes/issues/48638

3、https://github.com/kubernetes-incubator/kube-aws/issues/598

4、https://github.com/kubernetes/client-go/issues/374

5、https://github.com/kubernetes/apimachinery/blob/b874eabb9a4eb99cef27db5c8d06f165425400cec/pkg/util/net/http.go#L109-L120

6、https://www.cncf.io/blog/2018/08/31/grpc-on-http-2-engineering-a-robust-high-performance-protocol/

7、https://github.com/kubernetes/kubernetes/pull/63492

8、https://github.com/kubernetes/kubernetes/pull/71174

9、https://github.com/golang/go/issues/31643

10、https://github.com/kubernetes/kubernetes/pull/740016

[root@c4-jm-i1-k8stest03 ~]# netstat -antpl |grep kubelet

tcp 0 0 127.0.0.1:10248 0.0.0.0:* LISTEN 23665/./kubelet

tcp 0 0 10.162.1.26:63876 10.132.106.115:6443 ESTABLISHED 23665/./kubelet

tcp6 0 0 :::4194 :::* LISTEN 23665/./kubelet

tcp6 0 0 :::102400 :::* LISTEN 23665/./kubelet

tcp6 0 0 :::10255 :::* LISTEN 23665/./kubelet

tcp6 0 0 10.162.1.26:102400 10.132.1.400:61218 ESTABLISHED 23665/./kubelet

此时执行

在h2中,为了提高网络性能,有有一个 主机只建立有有一个 连接,所有的请求都通过该连接进行,默认情况报告下,即使网络异常,他还是重用什儿 连接,直到操作系统将连接关闭,而操作系统关闭僵尸连接的时间默认是十几分钟,具体的时间都并能调整系统参数:

经过沟通后发现:LB会为其转发的每有有一个 connection维护什儿 数据特性,当新的一台LB server上线之时会均摊一每种那末 的流量,什儿 在其维护的数据特性中找并能该connection的记录就会认为什儿 请求非法,直接DROP掉。这类的事实在 还处在不少,在kubernetes的isuse里有不少那末 的案例,甚至并能公有云的的LB也会有那末 的问题图片。这类:kubernetes#41916,kubernetes#48638,kubernetes-incubator/kube-aws#598

集群恢复时会,发现有故障通报LB处在了故障,联系了相关同学发现时间点刚好相符,怀疑是肯能LB异常原因分析分析着kubelet无法连接apiserver。

iptables -I OUTPUT -p tcp --sport 63876 -j DROP

首先确保kubelet与apiserver连接正常,执行netstat -antpl | grep 6443都并能看到kubelet与apiserver 10.132.106.115:6443连接正常:

定 位 问 题

经过阅读代码,发现什儿 逻辑那末 被修复过,参考下方链接:

kubernetes是master-slave特性,master node是集群的大脑,当master node处在故障时整个集群都"out of control"。master node中最重要的当属apiserver组件,它负责防止所有请求,并持久化情况报告到etcd。一般什儿 人会部署多份apiserver实现高可用。官方建议在多个apiserver前面部署有有一个 LB进行负载均衡,当其中一台apiserver处在故障时会,LB自动将流量切换到什儿 实例后面 。那末 实在 简单,什儿 也引入了额外的依赖,肯能LB处在故障肯能原因分析分析着全版apiserver不可用。什儿 人知道在kubernetes中node节点上kubelet与apiserver心跳超时后,controller-manager会将该node情况报告置为notReady,随分时四驱逐其上的pod,使哪几种pod在什儿 地方重建。可是 当LB处在故障时,集群中所有的node一定会变为notReady情况报告,进而原因分析分析着大规模的pod驱逐。

h2主动探测连接故障是通过发送Ping frame来实现,这是有有一个 优先级比较高什儿 payload很少的包,网络正常时是都并能快速返回,该frame默认时会发送,并能显式设置才会发送。在什儿 gRPC等要求可靠性比较高的通信框架中都实现了Ping frame,在gRPC On HTTP/2: Engineering A Robust, High Performance Protocol中谈到:

相当于明白原因分析分析着时会,push LB的同学改进的同去,kubelet也应该做什儿 改进:当kubelet连接apiserver超时时会,应该reset掉连接,进行重试。简单做了有有一个 测试,使用iptables规则drop掉kubelet发出的流量来模拟网络异常。

赶紧用tcpdump抓包分析了一下,发现kubelet不断地给apiservre发送包却那末收到对端的ACK,登录master查看apiserver服务也一切正常。时会同事发现重启kubelet就好了,为了尽快防止问题图片并能把kubelet全版重启了,后面 再慢慢定位问题图片。

查阅文档发现这是http1.1与http2.0的差异:在http1.1中,默认采用keep-alive复用网络连接,发起新的请求时,肯能当前有闲置的连接就会复用该连接,肯能那末则新建有有一个 连接。当kubelet连接异常时,老的连接被占用,总爱hang等待时间时间对端响应,kubelet在下一次心跳周期,肯能那末可用连接就会新建有有一个 ,可是 新连接正常通信,心跳包就都并能正常发送。

艰 难 修 复

都并能通过设置环境变量DISABLE_HTTP2来禁用h2,简单验证了一下,显式设置该环境变量禁用h2后,让连接使用http1.1实在 那末什儿 问题图片了。

社区那个issue肯能开了很长时间好像并那末防止的痕迹,还得另一方想辦法 。什儿 人知道有有一个 http.Client某种实在 只做了什儿 http协议的防止,底层的通信是交给Transport来实现,Transport决定怎么可不可以根据有有一个 request返回对应的response。在kubernetes client-go中关于Transporth2的设置并能这有有一个 函数。

E0415 17:03:11.351872 16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?resourceVersion=0&timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)

E0415 17:03:16.352108 16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)

E0415 17:03:21.352335 16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)

E0415 17:03:26.352548 16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)

E0415 17:03:31.352790 16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)

E0415 17:03:31.352810 16624 kubelet_node_status.go:366] Unable to update node status: update node status exceeds retry count

日志中显示的10.13.10.12是LB的地址。通过什儿 日志判断是kubelet连接apiserver失败,初步怀疑是网络故障,手动telnet 10.13.10.12 6443后发现一切正常,这就比较奇怪了,明明网络通信正常,kubelet为哪几种连不上apiserver?

} else {

令人遗憾的是,当前golang对于有有一个 进程的抽象ClientConn肯能支持发送Ping frame,什儿 连接是交由连接池clientConnPool管理的,该特性是个内内外部的私有特性体,什儿 人那末直接操作,封装连接池的Transport也那末暴露任何的接口来实现设置连接池中的所有连接定期发送Ping frame。肯能什儿 人想实现什儿 功能就并能自定义有有一个 Transport并实现有有一个 连接池,要实现有有一个 稳定可靠的Transport似乎时会说容易。并能求助golang社区看有那末防止方案,提交了有有一个 issue后,放慢一定会人回复并提交了PR,查看到一下,实现还是比较简单的,于是基于什儿 PR实现了clinet-go的Ping frame的探测。

}

return t

}

什儿 调用了http2.ConfigureTransport来设置transport支持h2。什儿 句代码似乎太过简单,并那末任何Ping frame相关的防止逻辑。查了下golang标准库中Transport与Pingframe相关的辦法 。

开发完毕准备上线的时会,想趁这次修复升级一下kubernetes版本到v1.10.11,一般patch release是保证兼容的。在测试v1.10.11的时会惊奇的发现,即使不改任何代码,什儿 问题图片也没辦法 复现了。说明在v1.10.2中是有问题图片的,在v1.10.11中恢复了,接着在master中又引入了什儿 问题图片,看来还得并能仔细阅读一下这每种代码了,到底是处在了哪几种。

什儿 backport到1.10.3的代码中,当连接异常一定会会调用closeAllConns强制关闭掉所有的连接使其重建。

时会又引入了regression,将closeAllConns置为nil,原因分析分析着连接无法正常关闭。

故 障 发 生

接下来什儿 为什么会修复什儿 问题图片了。网上找了一下相关的issue,首先找到的是kubernetes/client-go#374什儿 issue,后面 描述的情况报告和什儿 人碰到的很这类,一群人说是肯能使用了HTTP/2.0协议(以下简称h2),查找了一下kubelet的源码,发现kubelet默认是使用h2协议,具体的代码实现在SetTransportDefaults什儿 函数中。

// SetTransportDefaults applies the defaults from http.DefaultTransport

// for the Proxy, Dial, and TLSHandshakeTimeout fields if unset

func SetTransportDefaults(t http.Transport) http.Transport {

t = SetOldTransportDefaults(t)

// Allow clients to disable http2 if needed.

if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 {

The less clean version is where the endpoint dies or hangs without informing the client. In this case,TCP might undergo retry for as long as 10 minutes before the connection is considered failed.Of course, failing to recognize that the connection is dead for 10 minutes is unacceptable.

gRPC solves this problem using HTTP/2 semantics:when configured using KeepAlive,gRPC will periodically send HTTP/2 PING frames.These frames bypass flow control and are used to establish whether the connection is alive.

If a PING response does not return within a timely fashion,gRPC will consider the connection failed,close the connection,and begin reconnecting (as described above)

都并能看到gRPC同样处在那末 的问题图片,为了快速识别故障连接并恢复采用了Ping frame。什儿 目前kubernetes所建立的连接中并那末实现Ping frame,原因分析分析着了无法及时发现连接异常并自愈。

峰 回 路 转

将kubelet发出的包丢掉,模拟网络故障,此时都并能看到netstat的输出中该连接的Send-Q正在逐步增加,什儿 kubelet也打印出日志显示无法连接:

https://github.com/kubernetes/kubernetes/pull/63492

什儿 问题图片和当时处在故障的情况报告一模一样:连接异常原因分析分析着kubelet心跳超时,重启kubelet一定会新建连接,恢复正常心跳。肯能什儿 人当前采用的kubernetes版本是v1.10.2,下载master分支的代码编译试了下,也是有什儿 问题图片的,感觉什儿 问题图片总爱处在。

net.ipv4.tcp_retries2, net.ipv4.tcp_keepalive_time, net.ipv4.tcp_keepalive_probes, net.ipv4.tcp_keepalive_intvl

通过调整操作系统断开异常连接的时间实现快速恢复。

明白了什儿 逻辑时会修改就简单了,将closeAllConns再置为正确的值即可,给官方提交了有有一个 pr,官方很乐意就接受了,并backport到了1.14版本中。至此什儿 就算全版修复了,当然都并能通过上文提到的给h2增加Ping frame的辦法 防止该问题图片,这是什儿 方案肯能复杂,修复时间比较长。

来源:小米云技术

ID:mi-cloud-tech

作者:高荣

背 景