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

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
网络型负载均衡 NLB,每月750个小时 15LCU
简介: 来源:小米云技术ID:mi-cloud-tech作者:高荣背 景kubernetes是master-slave结构,master node是集群的大脑,当master node发生故障时整个集群都"out of control"。

来源:小米云技术
ID:mi-cloud-tech
作者:高荣
背 景

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驱逐。

故 障 发 生

无独有偶,这样的事情偏偏被我们碰到了,接到线上大量node not ready的报警后,立刻上线查看,发现所有的node kubelet都报如下错误:

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?

赶紧用tcpdump抓包分析了一下,发现kubelet不断地给apiservre发送包却没有收到对端的ACK,登录master查看apiserver服务也一切正常。后来同事发现重启kubelet就好了,为了尽快解决问题只能把kubelet全部重启了,后面再慢慢定位问题。

定 位 问 题

集群恢复之后,发现有故障通报LB发生了故障,联系了相关同学发现时间点刚好相符,怀疑是因为LB异常导致kubelet无法连接apiserver。

经过沟通后发现:LB会为其转发的每一个connection维护一些数据结构,当新的一台LB server上线之后会均摊一部分原来的流量,但是在其维护的数据结构中找不到该connection的记录就会认为这个请求非法,直接DROP掉。类似的事确实还发生不少,在kubernetes的isuse里有不少这样的案例,甚至需要公有云的的LB也会有这样的问题。例如:kubernetes#41916,kubernetes#48638,kubernetes-incubator/kube-aws#598

大概明白原因之后,push LB的同学改进的同时,kubelet也应该做一些改进:当kubelet连接apiserver超时之后,应该reset掉连接,进行重试。简单做了一个测试,使用iptables规则drop掉kubelet发出的流量来模拟网络异常。

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

[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 :::10250 :::* LISTEN 23665/./kubelet
tcp6 0 0 :::10255 :::* LISTEN 23665/./kubelet
tcp6 0 0 10.162.1.26:10250 10.132.1.30:61218 ESTABLISHED 23665/./kubelet
此时执行

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

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

[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之后,一切又恢复了。

这个现象和当时发生故障的情况一模一样:连接异常导致kubelet心跳超时,重启kubelet后会新建连接,恢复正常心跳。因为我们当前采用的kubernetes版本是v1.10.2,下载master分支的代码编译试了下,也是有这个问题的,感觉这个问题一直存在。

艰 难 修 复

接下来就是怎么修复这个问题了。网上找了一下相关的issue,首先找到的是kubernetes/client-go#374这个issue,上面描述的情况和我们碰到的很相似,有人说是因为使用了HTTP/2.0协议(以下简称h2),查找了一下kubelet的源码,发现kubelet默认是使用h2协议,具体的代码实现在SetTransportDefaults这个函数中。

可以通过设置环境变量DISABLE_HTTP2来禁用h2,简单验证了一下,显式设置该环境变量禁用h2后,让连接使用http1.1确实没有这个问题了。

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

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

net.ipv4.tcp_retries2, net.ipv4.tcp_keepalive_time, net.ipv4.tcp_keepalive_probes, net.ipv4.tcp_keepalive_intvl
通过调整操作系统断开异常连接的时间实现快速恢复。

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

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,导致了无法及时发现连接异常并自愈。

社区那个issue已经开了很长时间好像并没有解决的痕迹,还得自己想办法。我们知道一个http.Client本身其实只做了一些http协议的处理,底层的通信是交给Transport来实现,Transport决定如何根据一个request返回对应的response。在kubernetes client-go中关于Transporth2的设置只有这一个函数。

// 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 {

klog.Infof("HTTP2 has been explicitly disabled")

} else {

if err := http2.ConfigureTransport(t); err != nil {
  klog.Warningf("Transport failed http2 configuration: %v", err)
}

}
return t
}
只是调用了http2.ConfigureTransport来设置transport支持h2。这一句代码似乎太过简单,并没有任何Ping frame相关的处理逻辑。查了下golang标准库中Transport与Pingframe相关的方法。

令人遗憾的是,当前golang对于一个tcp连接的抽象ClientConn已经支持发送Ping frame,但是连接是交由连接池clientConnPool管理的,该结构是个内部的私有结构体,我们没法直接操作,封装连接池的Transport也没有暴露任何的接口来实现设置连接池中的所有连接定期发送Ping frame。如果我们想实现这个功能就必须自定义一个Transport并实现一个连接池,要实现一个稳定可靠的Transport似乎并不容易。只能求助golang社区看有没有解决方案,提交了一个issue后,很快就有人回复并提交了PR,查看了一下,实现还是比较简单的,于是基于这个PR实现了clinet-go的Ping frame的探测。

峰 回 路 转

开发完毕准备上线的时候,想趁这次修复升级一下kubernetes版本到v1.10.11,一般patch release是保证兼容的。在测试v1.10.11的时候惊奇的发现,即使不改任何代码,这个问题也没办法复现了。说明在v1.10.2中是有问题的,在v1.10.11中恢复了,接着在master中又引入了这个问题,看来还得需要仔细阅读一下这部分代码了,到底是发生了什么。

经过阅读代码,发现这个逻辑曾经被修复过,参考下方链接:

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

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

随后又引入了regression,将closeAllConns置为nil,导致连接无法正常关闭。

明白了这个逻辑之后修改就简单了,将closeAllConns再置为正确的值即可,给官方提交了一个pr,官方很乐意就接受了,并backport到了1.14版本中。至此这个就算完全修复了,当然可以通过上文提到的给h2增加Ping frame的方式解决该问题,这是这种方案可能比较复杂,修复时间比较长。

参考链接
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/b874eabb9a4eb99cef27db5c8d06f16542580cec/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/78016

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
15天前
|
存储 Kubernetes 负载均衡
CentOS 7.9二进制部署K8S 1.28.3+集群实战
本文详细介绍了在CentOS 7.9上通过二进制方式部署Kubernetes 1.28.3+集群的全过程,包括环境准备、组件安装、证书生成、高可用配置以及网络插件部署等关键步骤。
100 3
CentOS 7.9二进制部署K8S 1.28.3+集群实战
|
15天前
|
Kubernetes 负载均衡 前端开发
二进制部署Kubernetes 1.23.15版本高可用集群实战
使用二进制文件部署Kubernetes 1.23.15版本高可用集群的详细教程,涵盖了从环境准备到网络插件部署的完整流程。
32 2
二进制部署Kubernetes 1.23.15版本高可用集群实战
|
15天前
|
存储 Kubernetes 测试技术
k8s使用pvc,pv,sc关联ceph集群
文章介绍了如何在Kubernetes中使用PersistentVolumeClaim (PVC)、PersistentVolume (PV) 和StorageClass (SC) 来关联Ceph集群,包括创建Ceph镜像、配置访问密钥、删除默认存储类、编写和应用资源清单、创建资源以及进行访问测试的步骤。同时,还提供了如何使用RBD动态存储类来关联Ceph集群的指南。
32 7
|
15天前
|
存储 Kubernetes 数据安全/隐私保护
k8s对接ceph集群的分布式文件系统CephFS
文章介绍了如何在Kubernetes集群中使用CephFS作为持久化存储,包括通过secretFile和secretRef两种方式进行认证和配置。
22 5
|
15天前
|
Kubernetes 负载均衡 应用服务中间件
kubeadm快速构建K8S1.28.1高可用集群
关于如何使用kubeadm快速构建Kubernetes 1.28.1高可用集群的详细教程。
34 2
|
16天前
|
Kubernetes Linux API
CentOS 7.6使用kubeadm部署k8s 1.17.2测试集群实战篇
该博客文章详细介绍了在CentOS 7.6操作系统上使用kubeadm工具部署kubernetes 1.17.2版本的测试集群的过程,包括主机环境准备、安装Docker、配置kubelet、初始化集群、添加节点、部署网络插件以及配置k8s node节点管理api server服务器。
48 0
CentOS 7.6使用kubeadm部署k8s 1.17.2测试集群实战篇
|
18天前
|
API UED 开发者
超实用技巧大放送:彻底革新你的WinForms应用,从流畅动画到丝滑交互设计,全面解析如何在保证性能的同时大幅提升用户体验,让软件操作变得赏心悦目不再是梦!
【8月更文挑战第31天】在Windows平台上,使用WinForms框架开发应用程序时,如何在保持性能的同时提升用户界面的吸引力和响应性是一个常见挑战。本文探讨了在不牺牲性能的前提下实现流畅动画与交互设计的最佳实践,包括使用BackgroundWorker处理耗时任务、利用Timer控件创建简单动画,以及使用Graphics类绘制自定义图形。通过具体示例代码展示了这些技术的应用,帮助开发者显著改善用户体验,使应用程序更加吸引人和易于使用。
45 0
|
JSON Kubernetes 数据格式
【Kubernetes】开发中与 APIServer 常见的几种认证方式
介绍了几种在开发过程中与 APIServer 交互的认证方式
889 0
【Kubernetes】开发中与 APIServer 常见的几种认证方式
|
Kubernetes 容器
kubernetes apiserver认证
kubernetes认证 Kubernetes集群的操作可以通过apiserver来进行操作,kubectl命令最终也是调用的apiserver,如果想要获取对apiserver进行操作,需要先通过其认证 api-server的认证方式: 基本认证:basic-auth --basic-auth-file=/path/to/basic-auth.
1289 0
|
25天前
|
Kubernetes Ubuntu Windows
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)