简介
从官方的架构图中很容易就能找到 kubelet
执行 kubelet -h
看到 kubelet 的功能介绍:
-
kubelet 是每个 Node 节点上都运行的主要“节点代理”。使用如下的一个向 apiserver 注册 Node 节点:主机的
hostname
;覆盖host
的参数;或者云提供商指定的逻辑。 -
kubelet 基于
PodSpec
工作。PodSpec
是用YAML
或者JSON
对象来描述 Pod。Kubelet 接受通过各种机制(主要是 apiserver)提供的一组PodSpec
,并确保里面描述的容器良好运行。
除了由 apiserver 提供 PodSpec
,还可以通过以下方式提供:
-
文件
-
HTTP 端点
-
HTTP 服务器
kubelet 功能归纳一下就是上报 Node 节点信息,和管理(创建、销毁)Pod。 功能看似简单,实际不然。每一个点拿出来都需要很大的篇幅来讲,比如 Node 节点的计算资源,除了传统的 CPU、内存、硬盘,还提供扩展来支持类似 GPU 等资源;Pod 不仅仅有容器,还有相关的网络、安全策略等。
架构
kubelet 的架构由 N 多的组件组成,下面简单介绍下比较重要的几个:
PLEG
即 Pod Lifecycle Event Generator,字面意思 Pod 生命周期事件(ContainerStarted
、ContainerDied
、ContainerRemoved
、ContainerChanged
)生成器。
其维护着 Pod 缓存;定期通过 ContainerRuntime
获取 Pod 的信息,与缓存中的信息比较,生成如上的事件;将事件写入其维护的通道(channel)中。
PodWorkers
处理事件中 Pod 的同步。核心方法 managePodLoop()
间接调用 kubelet.syncPod()
完成 Pod 的同步:
-
如果 Pod 正在被创建,记录其延迟
-
生成 Pod 的 API Status,即
v1.PodStatus
:从运行时的 status 转换成 api status -
记录 Pod 从
pending
到running
的耗时 -
在
StatusManager
中更新 pod 的状态 -
杀掉不应该运行的 Pod
-
如果网络插件未就绪,只启动使用了主机网络(host network)的 Pod
-
如果 static pod 不存在,为其创建镜像(Mirror)Pod
-
为 Pod 创建文件系统目录:Pod 目录、卷目录、插件目录
-
使用
VolumeManager
为 Pod 挂载卷 -
获取 image pull secrets
-
调用容器运行时(container runtime)的
#SyncPod()
方法
PodManager
存储 Pod 的期望状态,kubelet 服务的不同渠道的 Pod
StatusProvider
提供节点和容器的统计信息,有 cAdvisor
和 CRI
两种实现。
ContainerRuntime
顾名思义,容器运行时。与遵循 CRI 规范的高级容器运行时进行交互。
生命周期事件生成器PLEG
对于 Pod来说,Kubelet 会从多个数据来源(api、file以及http) watch Pod spec 中的变化。对于容器来说,Kubelet 会定期轮询容器运行时,以获取所有容器的最新状态。随着 Pod 和容器数量的增加,轮询会产生较大开销,带来的周期性大量并发请求会导致较高的 CPU 使用率峰值,降低节点性能,从而降低节点的可靠性。为了降低 Pod 的管理开销,提升 Kubelet 的性能和可扩展性,引入了 PLEG(Pod Lifecycle Event Generator)。改进了之前的工作方式:
-
减少空闲期间的不必要工作(例如 Pod 的定义和容器的状态没有发生更改)。
-
减少获取容器状态的并发请求数量。
为了进一步降低损耗,社区推出了基于event实现的PLEG,当然也需要CRI运行时支持。
它定期检查节点上 Pod 运行情况,如果发现感兴趣的变化,PLEG 就会把这种变化包 装成 Event 发送给 Kubelet 的主同步机制 syncLoop 去处理。
syncLoop
Kubelet启动后通过syncLoop进入到主循环处理Node上Pod Changes事件,监听来自file,apiserver,http三类的事件并汇聚到kubetypes.PodUpdate Channel(Config Channel)中,由syncLoopIteration不断从kubetypes.PodUpdate Channel中消费。
源码分析
源码版本
origin/release-1.30
启动流程
入口函数
入口位于 cmd/kubelet/app/server.go 。
其中 RunKubelet() 接口下的 startKubelet 即是启动 kubelet 的启动函数。
startKubelet
func startKubelet(k kubelet.Bootstrap, podCfg *config.PodConfig, kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *kubelet.Dependencies, enableServer bool) {
// start the kubelet
go k.Run(podCfg.Updates())
// start the kubelet server
if enableServer {
go k.ListenAndServe(kubeCfg, kubeDeps.TLSOptions, kubeDeps.Auth, kubeDeps.TracerProvider)
}
if kubeCfg.ReadOnlyPort > 0 {
go k.ListenAndServeReadOnly(netutils.ParseIPSloppy(kubeCfg.Address), uint(kubeCfg.ReadOnlyPort))
}
go k.ListenAndServePodResources()
}
跳转到 pkg/kubelet/kubelet.go ,这里就是 kubelet 的启动接口代码所在。
kubelet.Run
// Run starts the kubelet reacting to config updates
func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
// ...
// Start the cloud provider sync manager
if kl.cloudResourceSyncManager != nil {
go kl.cloudResourceSyncManager.Run(wait.NeverStop)
}
// 加载部分模块,如镜像管理, 观察oom,
if err := kl.initializeModules(); err != nil {
kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.KubeletSetupFailed, err.Error())
klog.ErrorS(err, "Failed to initialize internal modules")
os.Exit(1)
}
// Start volume manager
// 开启volume 管理
go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)
//...
// 开启pleg, 即watch方式获取 runtime的event
// Start the pod lifecycle event generator.
kl.pleg.Start()
// Start eventedPLEG only if EventedPLEG feature gate is enabled.
if utilfeature.DefaultFeatureGate.Enabled(features.EventedPLEG) {
kl.eventedPleg.Start()
}
// 启动主进程,接口 kl.syncLoopIteration()
// 监听来自 file/apiserver/http 事件源发送过来的事件,并对事件做出对应的同步处理。
kl.syncLoop(ctx, updates, kl)
}
事件watch
syncLoop
// syncLoop is the main loop for processing changes. It watches for changes from
// three channels (file, apiserver, and http) and creates a union of them. For
// any new change seen, will run a sync against desired state and running state. If
// no changes are seen to the configuration, will synchronize the last known desired
// state every sync-frequency seconds. Never returns.
func (kl *Kubelet) syncLoop(ctx context.Context, updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
//...
for {
if err := kl.runtimeState.runtimeErrors(); err != nil {
klog.ErrorS(err, "Skipping pod synchronizatio