首页 yabo88狗亚app 服务器 大yabo88狗亚app--任意三数字加yabo.com直达官网 存储 IT 安全 物联网 软件

云计算

云趋势频道旗下栏目: 亚搏娱乐 亚博电子游戏app 云开发 云趋势

K8S 从懵圈到熟练:读懂此文,集群节点不下线!

来源:云技术 ??发布时间:2019-05-18
摘要:导读:排查完全陌生的问题、不熟悉的系统组件,对许多工程师来说是无与伦比的工作乐趣,当然也是一大挑战。今天,阿里巴巴售后技术专家声东跟大家分享一例 Kubernetes 集群上的问题。这个问题影响范围较广,或许某天你也会遇到。更重要的是,作者在问题排

导读:排查完全陌生的问题、不熟悉的系统组件,对许多工程师来说是无与伦比的工作乐趣,当然也是一大挑战。今天,阿里巴巴售后技术专家声东跟大家分享一例 Kubernetes 集群上的问题。这个问题影响范围较广,或许某天你也会遇到。更重要的是,作者在问题排查过程中的思路和方法,也会让你有所启发。

关于问题

I am Not Ready

阿里云有自己的 Kubernetes 容器集群产品。随着 Kubernetes 集群出货量剧增,线上用户零星地发现,集群会非常低概率地出现节点 NotReady 情况。据我们观察,这个问题差不多每个月,都会有一两个用户遇到。在节点 NotReady 之后,集群 Master 没有办法对这个节点做任何控制,比如下发新的 Pod,再比如抓取节点上正在运行 Pod 的实时信息。

需要知道的Kubernetes知识

这里我稍微补充一点 Kubernetes 集群的基本知识。Kubernetes 集群的“硬件基础”,是以单机形态存在的集群节点。这些节点可以是物理机,也可以是虚拟机。集群节点分为 Master 节点和 Worker 节点。Master 节点主要用来承载集群管控组件,比如调度器和控制器。而 Worker 节点主要用来跑业务。Kubelet 是跑在各个节点上的代理,它负责与管控组件沟通,并按照管控组件的指示,直接管理 Worker节点。

当集群节点进入 NotReady 状态的时候,我们需要做的第一件事情,是检查运行在节点上的 kubelet 是否正常。在这个问题出现的时候,使用 systemctl 命令查看的kubelet 状态(kubelet 是 systemd 管理的一个 daemon )发现它是正常运行的。当我们用 journalctl 查看 kubelet 日志的时候,发现以下错误。

什么是PLEG?

这个报错清楚地告诉我们,容器 runtime 是不工作的,且 PLEG 是不健康的。这里容器 runtime 指的就是 docker daemon 。Kubelet 通过操作 docker daemon 来控制容器的生命周期。而这里的 PLEG,指的是 pod lifecycle event generator。PLEG 是 kubelet 用来检查 runtime 的健康检查机制。这件事情本来可以由 kubelet 使用 polling 的方式来做。但是 polling 有其高成本的缺陷,所以 PLEG 应用而生。PLEG 尝试以一种“中断”的形式,来实现对容器 runtime 的健康检查,虽然实际上,它同时用了 polling 和”中断”这样折中的方案。

基本上,根据上边的报错,我们可以确认容器 runtime 出了问题。在有问题的节点上,通过 docker 命令尝试运行新的容器,命令会没有响应,这说明上边的报错是准确的。

Docker Stack

Docker Daemon调用栈分析

Docker 作为阿里云 Kubernetes 集群使用的容器 runtime ,在1.11之后,被拆分成了多个组件以适应 OCI 标准。拆分之后,其包括 docker daemon,containerd,containerd-shim 以及 runC。组件 containerd 负责集群节点上容器的生命周期管理,并向上为 docker daemon 提供 gRPC 接口。

在这个问题中,既然 PLEG 认为容器 runtime 出了问题,我们需要从 docker daemon 进程看起。我们可以使用 kill -USR1 命令发送 USR1 信号给docker daemon,而 docker daemon 收到信号之后,会把所有线程调用栈输出到 /var/run/docker 文件夹里。

Docker daemon 进程的调用栈是比较容易分析的。稍加留意,我们会发现大多数的调用栈都长成下图中的样子。通过观察栈上每个函数的名字,以及函数所在的文件(模块)名称,我们可以了解到,这个调用栈的下半部分,是进程接到 http 请求,做请求路由的过程;而上半部分则是具体的处理函数。最终处理函数进入等待状态,等待一个mutex实例。

到这里,我们需要稍微看一下 ContainerInspectCurrent 这个函数的实现。从实现可以看到,这个函数的第一个参数,就是这个线程正在操作的容器名指针。使用这个指针搜索整个调用栈文件,我们会找出所有等在这个容器上的线程。同时,我们可以看到下边这个线程。

这个线程调用栈上的函数 ContainerExecStart 也是在处理相同容器。但不同的是,ContainerExecStart 并没有在等这个容器,而是已经拿到了这个容器的操作权(mutex),并把执行逻辑转向了 containerd 调用。关于这一点,我们也可以使用代码来验证。前边我提到过,containerd 通过 gRPC 向上对 docker daemon 提供接口。此调用栈上半部分内容,正是 docker daemon 在通过 gRPC 请求来呼叫containerd。

Containerd调用栈分析

与 docker daemon 类似,我们可以通过 kill -SIGUSR1 命令来输出containerd 的调用栈。不同的是,这次调用栈会直接输出到 messages 日志。

Containerd 作为一个 gRPC 的服务器,会在接到 docker daemon 的远程调用之后,新建一个线程去处理这次请求。关于 gRPC 的细节,我们这里其实不用太多关注。在这次请求的客户端调用栈上,可以看到这次调用的核心函数在 Start 一个Process 。我们在 containerd 的调用栈里搜索 Start,Process 以及 process.go 等字段,很容易发现下边这个线程。

相关阅读

最火资讯