zl程序教程

您现在的位置是:首页 >  Java

当前栏目

图解K8s源码 - kubelet 篇

2023-02-18 16:23:54 时间

我们在之前的文章中介绍了 Master 控制平面中的三大组件:kube-apiserver、kube-controller-manager、kube-scheduler,它们分别负责 k8s 集群的资源访问入口、集群状态管理、集群调度。

本篇将介绍Node 节点上的重要组件之一的 kubelet。kubelet 是在每个 Node 节点上运行的主要 “节点代理”。它接受通过各种机制(主要是kube-apiserver)提供的一组 PodSpec(描述pod 的 YAML 或 JSON对象) 并保证这些 PodSpec 中描述的 pod 正常健康运行。

Kubelet 的工作原理

kubelet的核心是一个控制循环 SyncLoop, 驱动该控制运行的事件包括4种:

  • pod 更新事件;
  • pod 生命周期变化;
  • kubelet 本身设置的执行周期;
  • 定时的清理事件。

说回图中,kubelet 启动时首先是设置 Listers,即注册它关心的各种事件的 Informer,这些 Informer 就是 SyncLoop 需要处理的数据的来源。

另外,在SyncLoop循环上还有许多子控制循环,就是图中右侧的一些Manager。比如 Node Status Manager 就负责响应 Node 的状态变化,然后将 Node 的状态收集起来,通过 Heartbeat 的方式上报给 APIServer。

kubelet 采用 watch 机制监听与自己相关的 pod 的变化,即过滤条件是该 pod 的 nodeName 字段与自己相同。然后将这些pod 信息缓存在自己的内存中。

当一个 pod 完成调度与节点绑定后,kubelet 检查该 pod 在内存中的状态,确定这个是新调度过来的 pod,从而触发Handler中的ADD事件对应的处理逻辑,就是图中 HandlerPods 部分。

在处理过程中,kubelet 单独起一个 名为 Pod Update Worker 的 goroutine 来完成对 pod 的处理工作。如果是 ADD 事件的话,kubelet 就会为这个新的 Pod 生成对应的 Pod Status,检查 Pod 所声明使用的 Volume 是不是已经准备好。然后,调用下层的容器运行时(比如 Docker),开始创建这个 Pod 所定义的容器。而如果是 UPDATE 事件,kubelet 就会根据 Pod 对象具体的变更情况,调用下层容器运行时进行容器的重建工作。

由于 k8s 需要屏蔽下层容器运行时的差异,也是为了解耦 kubelet 与容器运行时,所以 kubelet 调用下层容器运行时的执行过程,并不会直接调用 Docker 的 API,而是通过一组叫作 CRI(Container Runtime Interface,容器运行时接口)的 gRPC 接口来间接执行的。

kubelet 通过 CRI 接口来和第三方容器运行时进行通信,操作容器与镜像。实现了 CRI 接口的容器运行时通常称为 CRI shim, 这是一个 gRPC Server,监听在本地的 unix socket 上。它用于将 CRI 请求,转换成对 containerd 的调用,然后创建出 runC 容器,runC 项目,负责执行设置容器 Namespace、Cgroups 和 chroot 等基础操作的组件的操作。

一般来说CRI接口可以分为两组:

  • ImageService:主要是容器镜像相关操作,比如拉取镜像、删除镜像等。
  • RuntimeService:主要是跟容器相关操作,比如创建、启动、删除Container、Exec等。

Kubelet 中事件处理机制

在之前的文章中提到过,k8s 中的各个组件会将运行时产生的各种事件汇报到 apiserver,使用 kubectl describe 可以看到其相关的 events。

events 在 vendor/k8s.io/api/core/v1/types.go 中进行定义:

type Event struct {
 metav1.TypeMeta `json:",inline"`
 metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
 InvolvedObject ObjectReference `json:"involvedObject" protobuf:"bytes,2,opt,name=involvedObject"`
 Reason string `json:"reason,omitempty" protobuf:"bytes,3,opt,name=reason"`
 Message string `json:"message,omitempty" protobuf:"bytes,4,opt,name=message"`
 Source EventSource `json:"source,omitempty" protobuf:"bytes,5,opt,name=source"`
 FirstTimestamp metav1.Time `json:"firstTimestamp,omitempty" protobuf:"bytes,6,opt,name=firstTimestamp"`
 LastTimestamp metav1.Time `json:"lastTimestamp,omitempty" protobuf:"bytes,7,opt,name=lastTimestamp"`
 Count int32 `json:"count,omitempty" protobuf:"varint,8,opt,name=count"`
 Type string `json:"type,omitempty" protobuf:"bytes,9,opt,name=type"`
 EventTime metav1.MicroTime `json:"eventTime,omitempty" protobuf:"bytes,10,opt,name=eventTime"`
 Series *EventSeries `json:"series,omitempty" protobuf:"bytes,11,opt,name=series"`
 Action string `json:"action,omitempty" protobuf:"bytes,12,opt,name=action"`
 Related *ObjectReference `json:"related,omitempty" protobuf:"bytes,13,opt,name=related"`
 ReportingController string `json:"reportingComponent" protobuf:"bytes,14,opt,name=reportingComponent"`
 ReportingInstance string `json:"reportingInstance" protobuf:"bytes,15,opt,name=reportingInstance"`
}

InvolvedObject 表示和事件关联的对象,Source 表示事件源。使用 kubectl 看到的事件一般包含 Type、Reason、Age、From、Message 几个字段。另外,k8s 中 events 目前只有两种类型:"Normal" 和 "Warning"。

我将event的生成、广播和处理涉及的主要函数做了解析标注并绘制成矢量图,流程如下:

这部分的代码逻辑在 github 上有个精简版的实现 demo,值得一看。

代码请参考:https://github.com/gosoon/k8s-learning-notes/tree/master/k8s-package/events

参考:

《深入剖析 Kubernetes》

https://kubernetes.io/zh-cn/docs/reference/command-line-tools-reference/kubelet/

https://blog.tianfeiyu.com/source-code-reading-notes/kubernetes/k8s_events.html

k8s系列往期文章列表:

Kubernetes微服务常见概念及应用

图解K8s源码 - 序章 - K8s组件架构

图解K8s源码 - k8s核心数据结构

图解K8s源码 - kube-apiserver篇

图解K8s源码 - kube-apiserver下的RBAC鉴权机制

图解K8s源码 - kube-controller-manager篇

图解K8s源码 - kube-scheduler篇

持续更新中……

END