【k8s kubelet 源代码阅读(二)】-节点状态上报
主处理流程
在kubelet的Run中会调用相应的代码进行节点状态更新,代码如下:
1 | |
updateRuntimeUp
它在最开始初始化时会被调用,后面也会被周期性地调用(每 5 秒一次),主要职责是:检查底层容器运行时的健康状况,并根据检查结果更新 Kubelet 内部的状态。当容器运行时首次准备就绪时,它还会触发一系列依赖于运行时的模块的初始化。其主要流程为:
通过 CRI (Container Runtime Interface) 调用底层容器运行时(如 containerd 或 CRI-O)的 Status 接口,获取一个包含运行时当前状态信息的结构体 s。
通过s中的NetworkReady来检查网络状态是否ready
通过s中的RuntimeReady来检查运行时是否ready
当 RuntimeReady 为 true 时才会去更新内部缓存,并初始化那些依赖容器运行时的模块(只会被初始调用一次),比如 cAdvisor(用于监控)、ContainerManager(用于资源管理)、EvictionManager(驱逐管理器)、pluginManager、shutdownManager等。这个设计确保了这些模块只在容器运行时真正可用后才被启动。
1 | |
syncNodeStatus
其首先会去registerWithAPIServer,然后再updateNodeStatus。
1 | |
registerWithAPIServer
该函数的核心作用是:尝试将 Kubelet 所在的节点(Node)注册到 Kubernetes API Server。如果节点已经存在,则进行一次状态协调(reconcile)。
函数返回一个布尔值,true 表示注册/协调成功,false 表示失败。
下面是该函数的详细执行流程:
尝试创建新节点
kl.kubeClient.CoreV1().Nodes().Create(...): 函数首先会乐观地尝试直接创建一个新的 Node 对象。如果创建成功 (
err == nil),说明这是一个全新的节点加入集群。函数会记录相关延迟指标,然后直接返回true,表示注册成功。
处理创建失败的情况
如果创建失败,函数会通过一个
switch语句来分析错误类型:apierrors.IsAlreadyExists(err): 这是最常见的情况。错误表明 API Server 中已经存在一个同名的 Node 对象。这意味着 Kubelet 可能是在重启,或者之前的注册请求因为某些原因中断了。在这种情况下,程序会继续往下执行,进入“协调”逻辑。apierrors.IsForbidden(err): 这个错误表示 Kubelet 没有创建 Node 对象的权限。这通常是由于 RBAC 配置不正确导致的。如果KubeletRegistrationGetOnExistsOnly这个特性门控被启用,它会直接报错并返回false。否则,它会继续尝试获取节点信息,因为节点可能已经由其他方式创建好了。default: 对于其他类型的错误(如网络问题),函数会记录错误日志并直接返回false,表示本次尝试失败。
获取已存在的节点对象
如果是因为
AlreadyExists或Forbidden(且特性门控未开启)进入后续流程,函数会调用kl.kubeClient.CoreV1().Nodes().Get(...)来获取 API Server 中已存在的 Node 对象。如果获取失败或返回的
existingNode为nil,说明发生了异常,函数会记录错误并返回false。
协调(Reconcile)节点状态
如果成功获取到了
existingNode,说明节点确实已经注册过了。这时 Kubelet 的任务就变成了确保当前 Kubelet 的配置和状态与 API Server 中的记录保持一致。originalNode := existingNode.DeepCopy(): 先深度拷贝一份原始的节点对象,用于后续的Patch操作,以计算出差异。接下来会调用一系列的
reconcile和update函数,来检查并更新existingNode对象中的字段,包括:reconcileCMADAnnotationWithExistingNode: 协调与存储卷挂载相关的注解。updateDefaultLabels: 更新节点的默认标签(如操作系统、架构等)。reconcileExtendedResource: 协调扩展资源(如 GPU 等)。reconcileHugePageResource: 协调大页内存资源。
requiresUpdate标志位会记录在这些检查过程中是否有任何字段需要被更新。
发起 Patch 更新
如果
requiresUpdate为true,说明节点的某些状态需要更新。nodeutil.PatchNodeStatus(...): 函数会调用这个帮助函数,向 API Server 发送一个PATCH请求。PATCH请求只会更新发生变化的字段,比UPDATE请求更高效。如果
PATCH操作失败,记录错误并返回false。
成功返回
- 如果
PATCH成功,或者根本不需要更新(requiresUpdate为false),函数最终会返回true,表示 Kubelet 已经成功地将自己与 API Server 中的节点状态同步了。
- 如果
总结一下:
tryRegisterWithAPIServer 函数封装了 Kubelet 节点注册的核心逻辑。它不仅处理了首次注册的场景,更重要的是处理了 Kubelet 重启后与现有节点对象进行状态同步的场景,通过一系列的协调和 PATCH 操作,确保了节点信息的准确性和一致性。
1 | |
updateNodeStatus
该函数每10s会被调用一次,但是有4%的抖动以避免多个节点同时上报状态,使得APIServer压力过大。
具体在update时会最多尝试5次的tryUpdateNodeStatus。每次update的流程如下:
获取当前节点对象(
originalNode)这个函数的设计考虑到了大规模集群下对 API Server 的性能影响。
if tryNumber == 0: 如果是第一次尝试(tryNumber为 0),它会优先从本地的nodeLister(一个本地缓存)中获取 Node 对象。这样做可以极大地减少对 API Server 的GET请求,降低控制平面的负载。本地缓存的数据可能会有轻微延迟,但通常是可以接受的。else: 如果不是第一次尝试(意味着前一次尝试可能因为数据冲突而失败),它会直接通过heartbeatClient向 API Server 发送GET请求,以获取最新、最准确的 Node 对象数据,避免再次发生冲突。如果获取 Node 对象失败,或者获取到的对象为
nil,则直接返回错误。
计算更新后的节点状态
node, changed := kl.updateNode(ctx, originalNode): 调用updateNode函数。这个函数会:创建一个
originalNode的深拷贝(DeepCopy)。基于 Kubelet 当前的内部状态(如容器运行时状态、资源容量、Pod CIDR 等)来更新这个拷贝的
Status字段。返回更新后的
node对象和一个布尔值changed,该值表示node.Status与originalNode.Status相比是否发生了实质性的变化。
判断是否需要发送更新
shouldPatchNodeStatus := changed || kl.clock.Since(kl.lastStatusReportTime) >= kl.nodeStatusReportFrequency: 这里决定了是否真的需要向 API Server 发送更新请求。满足以下任一条件即可:changed: 节点状态发生了变化(比如NodeReady条件从False变成了True)。kl.clock.Since(kl.lastStatusReportTime) >= kl.nodeStatusReportFrequency: 距离上次成功上报状态的时间已经超过了预设的nodeStatusReportFrequency(例如 5 分钟)。这是一个强制上报机制,确保即使节点状态一直没有变化,API Server 也能定期收到该节点的“心跳”,确认它还活着。
执行更新或跳过
if !shouldPatchNodeStatus: 如果不需要更新,函数会调用kl.markVolumesFromNode(node)来同步一下卷(Volume)的使用状态,然后直接返回nil,表示本次操作成功完成(虽然没有发送网络请求)。updatedNode, err := kl.patchNodeStatus(originalNode, node): 如果需要更新,则调用patchNodeStatus函数,向 API Server 发送一个PATCH请求,只更新发生变化的字段。if err == nil: 如果PATCH请求成功,函数会用 API Server 返回的最新updatedNode对象来调用kl.markVolumesFromNode(updatedNode),确保 Volume Manager 的状态与刚上报到 API Server 的状态一致。最后,返回
patchNodeStatus的结果(err)。如果err不为nil,外层的updateNodeStatus就会进行重试。
总结一下:
tryUpdateNodeStatus 是一个既高效又健壮的函数。它通过优先使用本地缓存来降低 API Server 负载,同时通过强制上报周期和冲突后直连 API Server 的机制来保证状态同步的最终一致性和可靠性。它是 Kubelet 节点状态管理的核心实现。
1 | |