kubelet源码阅读

前言

本文分析kubelet源码,包括:

  1. kubelet心跳和Node信息上报;

  2. syncLoop主循环;

  3. worker循环;

  4. 同步Pod和删除Pod;

注:基于kubernetes 1.36.1。

一、kubelet 心跳和 Node 信息

kubelet 向控制面证明「节点还活着、还能调度」走两条独立路径:

路径 对象 默认周期 作用
Node Lease Lease 续约 10s40s * 0.25 心跳:node-lifecycle-controller 主要靠它判节点失联
Node Status Node.Status 计算 10s ;无变化上报 5min 节点信息:Ready/容量/地址/镜像/压力等

两条路径都在 Kubelet.Run 里起 goroutine,不经过 syncLoop

pkg/kubelet/kubelet.goRun 启动心跳与 Node 状态上报。

Go 复制代码
func (kl *Kubelet) Run(ctx context.Context, updates <-chan kubetypes.PodUpdate) {
    // ...
    if kl.kubeClient != nil {
        go func() {
            // 1. 先探一次 runtime 启动情况
            kl.updateRuntimeUp(ctx)
            // 2. 每 10s 计算一次 Node.Status;有变化或超时才 PATCH
            wait.JitterUntil(func() { kl.syncNodeStatus(ctx) }, kl.nodeStatusUpdateFrequency, 0.04, true, wait.NeverStop)
        }()

        // 3. 启动阶段 100ms 一轮,尽快把 Node 同步上去,成功一次后退出
        go kl.fastStatusUpdateOnce()

        // 4. 独立续约 Node Lease
        go kl.nodeLeaseController.Run(context.Background())
        // ...
    }
    // 5. 之后每 5s 再探一次 runtime,结果写进 runtimeState,供 NodeReady 使用
    go wait.UntilWithContext(ctx, kl.updateRuntimeUp, 5*time.Second)
    // 6. 进入 Pod 主循环(与心跳无关)
    kl.syncLoop(ctx, updates, kl)
}

1.1. updateRuntimeUp:CRI Status 探 runtime

上报 Node 之前,kubelet 必须先知道 container runtime 和网络 是否可用。updateRuntimeUp 调 CRI RuntimeService.Status,从返回的 RuntimeStatus.Conditions 里取出两个 Condition:

Condition 含义 写到 对 Node 的影响
RuntimeReady runtime 已起来,能接基本容器 runtimeState.runtimeError falseNodeReady=False
NetworkReady 网络插件已起来,能接需要网络的容器 runtimeState.networkError falseNodeReady=False

staging/src/k8s.io/cri-client/pkg/remote_runtime.go:CRI 侧要求这两个 Condition 必须都在。

Go 复制代码
func (r *remoteRuntimeService) statusV1(ctx context.Context, verbose bool) (*runtimeapi.StatusResponse, error) {
    resp, err := r.runtimeClient.Status(ctx, &runtimeapi.StatusRequest{Verbose: verbose})
    // 1. CRI 规范:Status.Conditions 至少要有 RuntimeReady、NetworkReady 两个
    if resp.Status == nil || len(resp.Status.Conditions) < 2 {
        return nil, errors.New("RuntimeReady or NetworkReady condition are not set")
    }
    return resp, nil
}

pkg/kubelet/kubelet.go:取出两个 Condition,写入 runtimeState

Go 复制代码
func (kl *Kubelet) updateRuntimeUp(ctx context.Context) {
    // 1. CRI RuntimeService.Status
    s, err := kl.containerRuntime.Status(ctx)
    if err != nil || s == nil {
        return
    }
    // 2. Condition 1:NetworkReady。未就绪则记下错误;hostNetwork Pod 仍允许启动
    networkReady := s.GetRuntimeCondition(kubecontainer.NetworkReady)
    if networkReady == nil || !networkReady.Status {
        kl.runtimeState.setNetworkState(fmt.Errorf("container runtime network not ready: %v", networkReady))
    } else {
        kl.runtimeState.setNetworkState(nil)
    }
    // 3. Condition 2:RuntimeReady。会传播到 NodeReady;未就绪则本轮直接返回
    runtimeReady := s.GetRuntimeCondition(kubecontainer.RuntimeReady)
    if runtimeReady == nil || !runtimeReady.Status {
        kl.runtimeState.setRuntimeState(fmt.Errorf("container runtime not ready: %v", runtimeReady))
        return
    }
    kl.runtimeState.setRuntimeState(nil)
    // 4. runtime 首次 Ready 时,才启动 cadvisor / eviction / pluginManager 等依赖 runtime 的模块
    kl.oneTimeInitializer.Do(func() {
        kl.initializeRuntimeDependentModules(ctx)
    })
    kl.runtimeState.setRuntimeSync(kl.clock.Now())
}

1.2. Node Lease(真正的心跳)

kubelet 启动时,会启动一个 Lease controller,负责定时更新 Node Lease。

YAML 复制代码
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
  creationTimestamp: "2026-08-15T09:33:51Z"
  name: worker1
  namespace: kube-node-lease
  ownerReferences:
  - apiVersion: v1
    kind: Node
    name: worker1
    uid: 8c11a976-976d-4d71-886e-57ef6ac03d85
  resourceVersion: "90008"
  uid: e631131b-df7b-49f4-9658-8e2bcad08f67
spec:
  holderIdentity: worker1
  leaseDurationSeconds: 40
  renewTime: "2026-08-16T07:33:38.064587Z"

staging/src/k8s.io/component-helpers/apimachinery/lease/controller.go:每10s周期续约,创建或更新Lease的Spec.RenewTime=当前时间。

Go 复制代码
func (c *controller) Run(ctx context.Context) {
    wait.JitterUntilWithContext(ctx, c.sync, c.renewInterval, 0.04, true)
}
func (c *controller) sync(ctx context.Context) {
    if c.latestLease != nil {
        // 2. 乐观用上次版本号 Update,少一次 GET,降低 apiserver/etcd 压力
        if err := c.retryUpdateLease(ctx, c.latestLease); err == nil {
            return
        }
    }
    // 3. 不存在则 Create;已存在则再 Update
    lease, created := c.backoffEnsureLease(ctx)
    if !created && lease != nil {
        _ = c.retryUpdateLease(ctx, lease)
    }
}

node_lifecycle_controller.go:controller-manager发现kubelet超过50s没上报lease,会把 Node 的 Condition NodeReady/NodeMemoryPressure/NodeDiskPressure/NodePIDPressure 设置为 Unknown,最终会打上污点node.kubernetes.io/unreachable:NoSchedule+node.kubernetes.io/unreachable:NoExecute造成驱逐。

Go 复制代码
observedLease, _ := nc.leaseLister.Leases(v1.NamespaceNodeLease).Get(node.Name)
if observedLease != nil && (savedLease == nil || savedLease.Spec.RenewTime.Before(observedLease.Spec.RenewTime)) {
    nodeHealth.lease = observedLease
    // 记录 最近一次 kubelet 仍然活着的时间
    nodeHealth.probeTimestamp = nc.now()
}
// gracePeriod = 50s
if nc.now().After(nodeHealth.probeTimestamp.Add(gracePeriod)) {
    // 设置Condition Unknown
}

1.3. Node Status(节点信息)

pkg/kubelet/kubelet_node_status.go:每10s执行一次syncNodeStatus,启动后会先registerWithAPIServer注册一次Node(如果Node已经存在则忽略),后续只会updateNodeStatus更新Status。

Go 复制代码
func (kl *Kubelet) syncNodeStatus(ctx context.Context) {
    kl.syncNodeStatusMux.Lock()
    defer kl.syncNodeStatusMux.Unlock()
    if kl.registerNode {
        // 1. 首次:构造 v1.Node 并 Create;已存在则 Get 后调和 annotation / taint
        kl.registerWithAPIServer(ctx)
    }
    // 2. 带重试的 Status PATCH
    _ = kl.updateNodeStatus(ctx)
}

func (kl *Kubelet) tryUpdateNodeStatus(ctx context.Context, tryNumber int) error {
    // 3. 第一次从 nodeLister(本地 informer)读,减轻 apiserver 压力;冲突后改走 etcd
    if tryNumber == 0 {
        originalNode, err = kl.nodeLister.Get(string(kl.nodeName))
    } else {
        originalNode, err = kl.heartbeatClient.CoreV1().Nodes().Get(ctx, string(kl.nodeName), opts)
    }
    // 判断是否需要更新
    node, changed := kl.updateNode(ctx, originalNode)
    // 4. 有变化,或距上次上报超过 nodeStatusReportFrequency(默认 5min)才 PATCH
    shouldPatchNodeStatus := changed || kl.isUpdateStatusPeriodExpired()
    if !shouldPatchNodeStatus {
        return nil
    }
    _, err = kl.patchNodeStatus(originalNode, node)
    return err
}

pkg/kubelet/kubelet_node_status.gosetNodeStatus 用一组 setter 填 Node.Status

Go 复制代码
func (kl *Kubelet) defaultNodeStatusFuncs() []func(context.Context, *v1.Node) error {
    return []func(context.Context, *v1.Node) error{
        nodestatus.NodeAddress(...),          // 1. 节点 IP / Hostname
        nodestatus.MachineInfo(...),          // 2. Capacity / Allocatable(cpu/mem/pods/ephemeral-storage + device plugin)
        nodestatus.VersionInfo(...),          // 3. kubelet / kube-proxy / runtime 版本
        nodestatus.DaemonEndpoints(...),      // 4. kubelet 端口
        nodestatus.Images(...),               // 5. 节点镜像列表(截断到 nodeStatusMaxImages)
        nodestatus.GoRuntime(),               // 6. GOOS / GOARCH
        nodestatus.RuntimeHandlers(...),      // 7. RuntimeClass handler
        nodestatus.NodeFeatures(...),         // 8. runtime features
        nodestatus.MemoryPressureCondition(...),
        nodestatus.DiskPressureCondition(...),
        nodestatus.PIDPressureCondition(...),
        nodestatus.ReadyCondition(...),       // 9. NodeReady:runtime / network / storage / cgroup / shutdown
        nodestatus.VolumesInUse(...),         // 10. 已挂载卷,供 attach-detach controller 对账
        kl.recordNodeSchedulableEvent,        // 11. Unschedulable 变化打 Event
    }
}

controller-manager 如果发现 Node 的 Condition Ready=False,会打上污点node.kubernetes.io/not-ready:NoSchedule+node.kubernetes.io/not-ready:NoExecute造成驱逐。

pkg/kubelet/nodestatus/setters.go:判定 Node 的 Condition ReadyTrue,要求:

  1. runtimeErrors 为空:1)CRIRuntimeService.Status返回RuntimeReady=True;2)PLEG循环不超时(3分钟);

  2. networkErrors 为空:CRIRuntimeService.Status返回NetworkReady=True

  3. storageErrors 为空:CSINode资源创建成功;

  4. nodeShutdownErrors 为空:nodeShutdownManager.IsShuttingDown返回false,表示不在停机过程中;

  5. node.Status.Capacity 必须包含必需资源;

Go 复制代码
//1. 先把 ReadyCondition 默认设成 True(KubeletReady)
newNodeReadyCondition := v1.NodeCondition{
	Type:              v1.NodeReady,
	Status:            v1.ConditionTrue,
	Reason:            "KubeletReady",
	Message:           "kubelet is posting ready status",
	LastHeartbeatTime: currentTime,
}
//2. 汇总四类错误:runtime / network / storage / shutdown
errs := []error{runtimeErrorsFunc(), networkErrorsFunc(), storageErrorsFunc(), nodeShutdownManagerErrorsFunc()}
//3. 额外检查 Capacity 是否缺必需资源(cpu/memory/pods,按开关可加 ephemeral-storage)
requiredCapacities := []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory, v1.ResourcePods}
if localStorageCapacityIsolation {
	requiredCapacities = append(requiredCapacities, v1.ResourceEphemeralStorage)
}
missingCapacities := []string{}
for _, resource := range requiredCapacities {
	if _, found := node.Status.Capacity[resource]; !found {
		missingCapacities = append(missingCapacities, string(resource))
	}
}
if len(missingCapacities) > 0 {
	errs = append(errs, fmt.Errorf("missing node capacity for resources: %s", strings.Join(missingCapacities, ", ")))
}
//4. 只要聚合错误不为空,就把 NodeReady 置为 False
if aggregatedErr := errors.NewAggregate(errs); aggregatedErr != nil {
	newNodeReadyCondition = v1.NodeCondition{
		Type:              v1.NodeReady,
		Status:            v1.ConditionFalse,
		Reason:            "KubeletNotReady",
		Message:           aggregatedErr.Error(),
		LastHeartbeatTime: currentTime,
	}
}

二、syncLoop 主流程

syncLoop 是 kubelet 处理 期望 Pod 配置 vs 实际 runtime 的事件循环,从多个 channel 取事件,转成 HandlePod*,最终投递到每个 Pod 自己的 worker 协程。

cmd/kubelet/app/server.go:把 PodConfig.Updates() 交给 Run

Go 复制代码
func startKubelet(...) {
    // 1. updates = PodConfig 合并 file/http/apiserver 后的增量通道
    go k.Run(ctx, podCfg.Updates())
}

pkg/kubelet/kubelet.gosyncLoop 外层循环。

Go 复制代码
func (kl *Kubelet) syncLoop(ctx context.Context, updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
    // 1. 1s 唤醒,扫 workQueue
    syncTicker := time.NewTicker(time.Second) 
    // 2. 2s housekeeping
    housekeepingTicker := time.NewTicker(housekeepingPeriod)
    // 3. PLEG 容器生命周期事件
    plegCh := kl.pleg.Watch() 
    for {
        if err := kl.runtimeState.runtimeErrors(); err != nil {
            // 4. runtime(CRI) 未就绪则退避,不跑 Pod 同步
            time.Sleep(duration)
            continue
        }
        kl.syncLoopMonitor.Store(kl.clock.Now())
        // 5. 循环
        if !kl.syncLoopIteration(ctx, updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {
            break
        }
    }
}

pkg/kubelet/kubelet.gosyncLoopIteration 一次 select 只处理 一个 就绪 channel。

Go 复制代码
func (kl *Kubelet) syncLoopIteration(...) bool {
    select {
     // 1. 期望配置:apiserver / 静态 pod / http
    case u, open := <-configCh:
        switch u.Op {
        case kubetypes.ADD:      handler.HandlePodAdditions(ctx, u.Pods)
        case kubetypes.UPDATE:   handler.HandlePodUpdates(ctx, u.Pods)
        case kubetypes.REMOVE:   handler.HandlePodRemoves(ctx, u.Pods)
        case kubetypes.RECONCILE: handler.HandlePodReconcile(ctx, u.Pods)
         // 2. DELETE 当 UPDATE:走优雅删除
        case kubetypes.DELETE:   handler.HandlePodUpdates(ctx, u.Pods)
        }
        kl.sourcesReady.AddSource(u.Source)
    // 3. runtime 实际状态变化
    case e := <-plegCh:
        if isSyncPodWorthy(e) { handler.HandlePodSyncs(ctx, []*v1.Pod{pod}) }
    // 4. 1s 定时:取出 workQueue 到期的 Pod
    case <-syncCh:
        handler.HandlePodSyncs(ctx, kl.getPodsToSync())
    // 5. 探针 * 3
    case update := <-kl.livenessManager.Updates():
        handleProbeSync(...)
    case update := <-kl.readinessManager.Updates():
        handleProbeSync(...)
    case update := <-kl.startupManager.Updates():
        handleProbeSync(...)
    // 6. DRA 设备就绪
    case update := <-kl.containerManager.Updates():
        handler.HandlePodSyncs(ctx, pods)
    // 7. 2s 清理孤儿容器 / cgroup / 目录
    case <-housekeepingCh:
        handler.HandlePodCleanups(ctx)
    }
    return true
}

三、触发 SyncLoop 的 N 个 通道

syncLoopIteration 的 select 一共 8 路(probe 拆成 3 个 manager)。汇聚关系:

Java 复制代码
file / http / apiserver ──► PodConfig.updates ──► configCh
GenericPLEG Relist ────────► pleg.eventChannel ─► plegCh
time.Ticker(1s) + workQueue ────────────────────► syncCh
liveness/readiness/startup.Set ─────────────────► probe Updates()
containerManager (DRA) ─────────────────────────► containerManager.Updates()
time.Ticker(2s) ────────────────────────────────► housekeepingCh

3.1. apiserver / 静态 Pod(configCh)

触发链 :配置源全量快照 → PodConfig.Merge 算出 ADD/UPDATE/DELETE/REMOVE/RECONCILE → configChHandlePod*UpdatePod

pkg/kubelet/kubelet.gomakePodSourceConfig 注册三类源。

Go 复制代码
func makePodSourceConfig(...) (*config.PodConfig, error) {
    cfg := config.NewPodConfig(...)
    if kubeCfg.StaticPodPath != "" {
        // 1. 静态 Pod:读 --pod-manifest-path(默认 20s 再 list 一次,linux 另有 inotify)
        config.NewSourceFile(..., cfg.Channel(ctx, kubetypes.FileSource))
    }
    if kubeCfg.StaticPodURL != "" {
        // 2. HTTP 静态 Pod:轮询 StaticPodURL (忽略)
        config.NewSourceURL(..., cfg.Channel(ctx, kubetypes.HTTPSource))
    }
    if kubeDeps.KubeClient != nil {
        // 3. apiserver:Watch spec.nodeName=<本节点>
        config.NewSourceApiserver(..., cfg.Channel(ctx, kubetypes.ApiserverSource))
    }
    return cfg, nil
}

pkg/kubelet/config/apiserver.go:等 Node informer sync 完再 Watch Pod。

Go 复制代码
func NewSourceApiserver(..., updates chan<- sourceUpdate) {
    // 1. 只看调度到本节点的 Pod
    lw := cache.NewListWatchFromClient(c.CoreV1().RESTClient(), "pods", metav1.NamespaceAll,
        fields.OneTermEqualSelector("spec.nodeName", string(nodeName)))
    go func() {
         // 2. 先等 Node 同步
        for !nodeHasSynced() { time.Sleep(1 * time.Second) }
        newSourceApiserverFromLW(lw, updates)
    }()
}

func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- sourceUpdate) {
    send := func(objs []interface{}) {
        // 3. Reflector 每次变更把当前全量 Pod 列表推给 PodConfig
        updates <- sourceUpdate{Pods: pods}
    }
    r := cache.NewReflector(lw, &v1.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0)
    go r.Run(wait.NeverStop)
}

pkg/kubelet/config/file.go:静态 Pod 立即读一次,后续 20s 一次list。

Go 复制代码
func (s *sourceFile) run(logger klog.Logger) {
    listTicker := time.NewTicker(s.period)
    go func() {
        _ = s.listConfig(logger) // 1. 启动立刻读目录/文件,加快静态 Pod 启动
        for {
            select {
            case <-listTicker.C:           // 2. 周期全量 list(兜底 inotify 漏事件)
                _ = s.listConfig(logger)
            case e := <-s.watchEvents:     // 3. linux inotify:add/modify/delete 单文件
                _ = s.consumeWatchEvent(logger, e)
            }
        }
    }()
    s.startWatch(logger)
}

pkg/kubelet/config/config.go:各源全量对比,拆成增量 Op。

Go 复制代码
func (s *podStorage) Merge(ctx context.Context, source string, update sourceUpdate) error {
    adds, updates, deletes, removes, reconciles := s.merge(ctx, source, update)
    // 1. REMOVE:源里消失(apiserver 真正删掉 / 静态文件没了)
    if len(removes.Pods) > 0 { s.updates <- *removes }
    // 2. ADD:该源第一次见到这个 UID
    if len(adds.Pods) > 0 { s.updates <- *adds }
    // 3. UPDATE:Spec/关键 annotation 变了
    if len(updates.Pods) > 0 { s.updates <- *updates }
    // 4. DELETE:DeletionTimestamp 已设,走优雅删除
    if len(deletes.Pods) > 0 { s.updates <- *deletes }
    // 5. RECONCILE:只 Status 对不上,多数情况不启容器
    if len(reconciles.Pods) > 0 { s.updates <- *reconciles }
    return nil
}

pkg/kubelet/kubelet.goHandlePodAdditions 写入 podManager(期望态),再投递 worker。

Go 复制代码
func (kl *Kubelet) HandlePodAdditions(ctx context.Context, pods []*v1.Pod) {
    for _, pod := range pods {
        // 1. 期望 Spec 的权威缓存;不在这里就等于已被删除
        kl.podManager.AddPod(pod)
        // 2. kubelet 侧准入:驱逐压力 / predicate / AppArmor 等失败则 reject 成 Failed
        if !kl.podWorkers.IsPodTerminationRequested(pod.UID) && !podutil.IsPodPhaseTerminal(pod.Status.Phase) {
            if ok, reason, message := kl.allocationManager.AddPod(kl.GetActivePods(), pod); !ok {
                kl.rejectPod(ctx, pod, reason, message)
                continue
            }
        }
        // 3. 投递到该 Pod 专属 worker,UpdateType=SyncPodCreate
        kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{
            Pod: pod, MirrorPod: mirrorPod, UpdateType: kubetypes.SyncPodCreate, StartTime: start,
        })
    }
}

HandlePodUpdates 同理:podManager.UpdatePod + UpdatePod(SyncPodUpdate)HandlePodRemovespodManager.RemovePod + deletePodUpdatePod(SyncPodKill)

3.2. probe 探针

触发链SyncPodprobeManager.AddPod 给每个探针起一个 worker 协程 → 周期探测 → 结果变化写入 resultsManager.Set → 对应 Updates() channel → syncLoopIterationHandlePodSyncs

pkg/kubelet/kubelet.go:创建 Pod 时注册探针。

Go 复制代码
func (kl *Kubelet) SyncPod(...) (isTerminal bool, postSync func(), err error) {
    // ...
    // 1. 按容器上的 Startup/Readiness/LivenessProbe 各起一个 worker 协程
    kl.probeManager.AddPod(ctx, pod)
    result := kl.containerRuntime.SyncPod(...)
    // ...
}

pkg/kubelet/prober/prober_manager.go:每种探针一个 goroutine。

Go 复制代码
func (m *manager) AddPod(ctx context.Context, pod *v1.Pod) {
    for _, c := range append(pod.Spec.Containers, getRestartableInitContainers(pod)...) {
         // 1. startup:未成功前卡住 liveness/readiness
        if c.StartupProbe != nil {
            w := newWorker(m, startup, pod, c)
            go w.run(ctx)
        }
        // 2. readiness:决定 Endpoints / Ready
        if c.ReadinessProbe != nil {
            go newWorker(m, readiness, pod, c).run(ctx)
        }
        // 3. liveness:失败则杀容器重启
        if c.LivenessProbe != nil {
            go newWorker(m, liveness, pod, c).run(ctx) 
        }
    }
}

pkg/kubelet/prober/worker.go:按 PeriodSeconds 探测,连续失败/成功达到 threshold 才写结果。

Go 复制代码
func (w *worker) run(ctx context.Context) {
    probeTicker := time.NewTicker(time.Duration(w.spec.PeriodSeconds) * time.Second)
    for w.doProbe(ctx) {
        select {
        case <-w.stopCh:
            return
        case <-probeTicker.C: // 1. 周期探测
        case <-w.manualTriggerCh:
        }
    }
}

func (w *worker) doProbe(ctx context.Context) bool {
    result, err := w.probeManager.prober.probe(ctx, w.probeType, w.pod, status, w.container, w.containerID)
    // 2. 未达 SuccessThreshold / FailureThreshold 不改状态
    if (result == results.Failure && w.resultRun < int(w.spec.FailureThreshold)) ||
        (result == results.Success && w.resultRun < int(w.spec.SuccessThreshold)) {
        return true
    }
    // 3. 结果相对上次变化才 Set,从而写入 Updates()
    w.resultsManager.Set(w.containerID, result, w.pod)
    return true
}

pkg/kubelet/prober/results/results_manager.go:结果变化才通知 syncLoop。

Go 复制代码
type manager struct {
	// map of container ID -> probe Result
	cache map[kubecontainer.ContainerID]Result
	// channel of updates
	updates chan Update
}
func (m *manager) Set(id kubecontainer.ContainerID, result Result, pod *v1.Pod) {
    // cache 里结果变了才发
    if m.setInternal(id, result) {
        m.updates <- Update{id, result, pod.UID}
    }
}
func (m *manager) setInternal(...) bool {
	prev, exists := m.cache[id]
	if !exists || prev != result {
		m.cache[id] = result
		return true
	}
	return false
}

pkg/kubelet/kubelet.go:syncLoop 消费探针更新。

Go 复制代码
case update := <-kl.livenessManager.Updates():
    // 1. liveness 失败 → HandlePodSyncs → 
    // 反查manager发现probe失败 → 杀容器
    if update.Result == proberesults.Failure {
        handleProbeSync(...)
    }
case update := <-kl.readinessManager.Updates():
    // 2. 先改本地 Ready 状态(statusManager 后续异步写回 apiserver),再 sync
    kl.statusManager.SetContainerReadiness(...)
    handleProbeSync(...)
case update := <-kl.startupManager.Updates():
    // 3. 先改本地 startup 状态(statusManager 后续异步写回 apiserver),再 sync
    kl.statusManager.SetContainerStartup(...)
    handleProbeSync(...)

func handleProbeSync(...) {
    pod, ok := kl.podManager.GetPodByUID(update.PodUID)
     // UpdateType=SyncPodSync
    handler.HandlePodSyncs(ctx, []*v1.Pod{pod})
}

3.3. 容器运行时事件(PLEG)

PLEGPod Lifecycle Event Generator 的缩写,负责周期性对比 runtime 中 Pod/Container 状态并产出生命周期事件。

触发链 :GenericPLEG 每 1s Relist(CRI ListPodSandbox + ListContainers)→ 对比新旧状态生成 ContainerStarted/Died/...eventChannelplegChHandlePodSyncs

pkg/kubelet/pleg/generic.go

  1. Relist调用CRIListPodSandbox+ListContainers获取当前Pod状态,比对内存中的Pod状态,生成事件;

  2. 只要Pod中一个容器变化(计算出变化事件),就触发更新内存podCache(GetPodStatusPodSandboxStatus + ContainerStatus);

  3. 发送plegCh,通知syncLoop调谐;

重点:syncLoop里的Pod实际状态,来源于podCache,podCache来源于 GetPodStatus(默认 PodSandboxStatus + 逐个 ContainerStatus),依赖于PLEG刷新。

Go 复制代码
func (g *GenericPLEG) Relist() {
    // 1. CRI ListPodSandbox + ListContainers
    podList, err := g.runtime.GetPods(ctx, true)
    // 更新最近一次relist时间,如果3min内没有更新,会触发Node的Ready=False
    g.updateRelistTime(timestamp)
    g.podRecords.setCurrent(pods)
    for pid := range g.podRecords {
        g.reconcilePodRecord(ctx, pid)
    }
}
func (g *GenericPLEG) reconcilePodRecord(ctx context.Context, pid types.UID) {
	oldPod := g.podRecords.getOld(pid)
	pod := g.podRecords.getCurrent(pid)
	allContainers := getContainersFromPods(oldPod, pod)
	var events []*PodLifecycleEvent
	// 对比容器在新老pod中的状态,生成事件
	for _, container := range allContainers {
		containerEvents := computeEvents(g.logger, oldPod, pod, &container.ID)
		events = append(events, containerEvents...)
	}
	_, reinspect := g.podsToReinspect.LoadAndDelete(pid)
	if len(events) == 0 && !reinspect {
		return
	}
	// GetPodStatus:PodSandboxStatus(sandbox/IP)+ ContainerStatus(各容器)
	// 更新cache
	status, updated, err := g.updateCache(ctx, pod, pid)
	// 循环events,通知kubelet.syncLoopIteration处理
	for i := range events {
		select {
		case g.eventChannel <- events[i]:
		}
	}
}

事件生成规则pkg/kubelet/pleg/generic.go):generateEvents 主要看 newState

Go 复制代码
func computeEvents(...) []*PodLifecycleEvent {
	var pid types.UID
	if oldPod != nil {
		pid = oldPod.ID
	} else if newPod != nil {
		pid = newPod.ID
	}
	oldState := getContainerState(oldPod, cid)
	newState := getContainerState(newPod, cid)
	return generateEvents(logger, pid, cid.ID, oldState, newState)
}
func generateEvents(...) []*PodLifecycleEvent {
	if newState == oldState {
		return nil
	}
	switch newState {
	case plegContainerRunning:
		return []*PodLifecycleEvent{{ID: podID, Type: ContainerStarted, Data: cid}}
	case plegContainerExited:
		return []*PodLifecycleEvent{{ID: podID, Type: ContainerDied, Data: cid}}
	case plegContainerUnknown:
		return []*PodLifecycleEvent{{ID: podID, Type: ContainerChanged, Data: cid}}
	case plegContainerNonExistent:
		switch oldState {
		case plegContainerExited:
			return []*PodLifecycleEvent{{ID: podID, Type: ContainerRemoved, Data: cid}}
		default:
			return []*PodLifecycleEvent{{ID: podID, Type: ContainerDied, Data: cid}, {ID: podID, Type: ContainerRemoved, Data: cid}}
		}
	}
}

事件的具体影响(事件本身不携带「重启/启动」指令,而是触发 pod sync):

  1. 发事件前reconcilePodRecord 已调用 updateCacheGetPodStatus,把 sandbox + 容器状态写入 podCache

  2. syncLoop 收到事件pkg/kubelet/kubelet.go);

  3. SyncPod 里的实际决策 :读 podCache + pod spec,computePodActions决定启动/停止容器;

PLEG 事件 = 「这个 Pod 的 runtime 状态变了,且 podCache 已更新,请立即 sync 一次」。真正决定启动/重启/收尾的是 SyncPod + computePodActions,不是事件类型本身。

3.4. syncCh:1 秒定时 + workQueue

触发链 :每次 podWorkerLoop 跑完 completeWork 把 UID 按 backoff / SyncFrequency(默认 1min )丢进 workQueuesyncTicker 每秒 getPodsToSync 取出到期项 → HandlePodSyncs。这是「没人推事件也要收敛」的兜底。

pkg/kubelet/pod_workers.go:同步结束重新入队。

Go 复制代码
func (p *podWorkers) completeWork(..., syncErr error) {
    switch {
    case phaseTransition:
        // 1. 进入 terminating 立刻再跑
        p.workQueue.Enqueue(podUID, 0)
    case syncErr == nil:
        // 2. 即使Pod同步成功,也要重新入队:约 1min 后再 sync
        p.workQueue.Enqueue(podUID, wait.Jitter(p.resyncInterval, ...))
    default:
        // 3. 失败:默认 10s backoff
        p.workQueue.Enqueue(podUID, wait.Jitter(p.backOffPeriod, ...)) 
    }
}

pkg/kubelet/kubelet.gogetPodsToSync从workqueue取出到期Pod,重新同步。

Go 复制代码
func (kl *Kubelet) syncLoopIteration(...) bool {
	logger := klog.FromContext(ctx)
	select {
	case <-syncCh: // syncCh=1秒定时器
		podsToSync := kl.getPodsToSync()
		handler.HandlePodSyncs(ctx, podsToSync)
    }
}
func (kl *Kubelet) getPodsToSync() []*v1.Pod {
	allPods := kl.podManager.GetPods()
	podUIDs := kl.workQueue.GetWork()
	podUIDSet := sets.New[string]()
	for _, podUID := range podUIDs {
		podUIDSet.Insert(string(podUID))
	}
	var podsToSync []*v1.Pod
	for _, pod := range allPods {
		if podUIDSet.Has(string(pod.UID)) {
			podsToSync = append(podsToSync, pod)
			continue
		}
	}
	return podsToSync
}

3.5. housekeepingCh:2 秒清理

触发链housekeepingTicker(2s)→ HandlePodCleanups

pkg/kubelet/kubelet_pods.go:主要动作。

Go 复制代码
func (kl *Kubelet) HandlePodCleanups(ctx context.Context) error {
     // 1. 裁掉配置源已无、且已 terminated 的 worker
    workingPods := kl.podWorkers.SyncKnownPods(logger, allPods)
    // 2. 期望有、worker 不知(static pod UID 复用)→ UpdatePod(SyncPodCreate)
    // 3. runtime 有、worker 不知 → UpdatePod(SyncPodKill, RunningPod=...)
    // 4. 停已终止 Pod 的 probe;清孤儿 status / 目录 / mirror pod / cgroup
    return nil
}

孤儿 Pod 投递:

Go 复制代码
kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{
    UpdateType: kubetypes.SyncPodKill,
    RunningPod: runningPod, // 1. 没有 API Spec,只能按 runtime 里看到的容器杀
    KillPodOptions: killPodOptions,
})

3.6. containerManager.Updates():DRA 设备

触发链 :Dynamic Resource Allocation 设备准备好 → containerManager.Updates()HandlePodSyncs

Go 复制代码
case update := <-kl.containerManager.Updates():
    for _, p := range update.PodUIDs {
        if pod, ok := kl.podManager.GetPodByUID(types.UID(p)); ok {
            pods = append(pods, pod)
        }
    }
    handler.HandlePodSyncs(ctx, pods)

四、Pod 同步

从 syncLoop 到 CRI 的三段:

Java 复制代码
HandlePod*  →  UpdatePod(投递,每 UID 一个 goroutine)
            →  podWorkerLoop(驱动状态机:Sync / Terminating / Terminated)
            →  SyncPod(把实际 runtime 往期望 Spec 收敛)

4.1. UpdatePod

pkg/kubelet/pod_workers.go:投递Pod期望给worker协程处理。

  1. 保证每个Pod只有一个worker协程podWorkerLoop

  2. podSyncStatus.pendingUpdate是本次期望状态;

  3. 通过podUpdates通知worker协程处理本次期望状态;

要点:worker 串行处理同一 Pod;pendingUpdate 合并意味着「只收敛到最新 Spec」

Go 复制代码
func (p *podWorkers) UpdatePod(ctx context.Context, options UpdatePodOptions) {
    uid := options.Pod.UID
    status, ok := p.podSyncStatuses[uid]
    if !ok {
        firstTime = true
        // 1. 首次见到该 UID,创建同步状态podSyncStatus
        p.podSyncStatuses[uid] = &podSyncStatus{syncedAt: now, ...}
    }
    podUpdates, exists := p.podUpdates[uid]
    if !exists {
        // 2. pod第一次进入,每 Pod 一个常驻协程
        podUpdates = make(chan struct{}, 1) 
        p.podUpdates[uid] = podUpdates
        go func() {
            p.podWorkerLoop(ctx, uid, podUpdates)
        }()
    }
    // 3. podSyncStatus.pendingUpdate=期望
    status.pendingUpdate = &options
    status.working = true
    select {
    // 4. podUpdates通知worker
    case podUpdates <- struct{}{}: 
    default:
    }
}

4.2. podWorkerLoop

pkg/kubelet/pod_workers.go:worker,从podSyncStatus.pendingUpdate消费期望(多来源),从podCache(PLEG每秒通过CRI发现容器变化,更新写入)获取实际状态,执行SyncPod/SyncTerminatingPod

Go 复制代码
func (p *podWorkers) podWorkerLoop(parentCtx context.Context, podUID types.UID, podUpdates <-chan struct{}) {
    // 1. Worker 被 UpdatePod 的信号唤醒
    for range podUpdates {
        // 2. 从pendingUpdate消费本轮期望 -> update.Options
        ctx, update, canStart, canEverStart, ok := p.startPodSync(parentCtx, podUID)
        // 3. 等 PLEG 刷新 PodCache 后的实际状态 -> status
        status, err := p.podCache.GetNewerThan(update.Options.Pod.UID, lastSyncTime)
        switch {
        case update.WorkType == TerminatedPod:
            // 4. 清 volume / cgroup / 目录
            err = p.podSyncer.SyncTerminatedPod(...)
        case update.WorkType == TerminatingPod:
            // 5. 杀全部容器
            err = p.podSyncer.SyncTerminatingPod(...)
        default:
            // 6. 创建/重启/原地 resize
            isTerminal, postSync, err = p.podSyncer.SyncPod(
                ctx, update.Options.UpdateType, update.Options.Pod, update.Options.MirrorPod, status)
        }
        // 7. 无论成功/失败,入workQueue,持续调谐
        p.completeWork(...)
    }
}
func (p *podWorkers) startPodSync(...) (...) {
    // 1. SyncPod / TerminatingPod / TerminatedPod
    update.WorkType = status.WorkType()
    // 2. 消费掉这次期望
    update.Options = *status.pendingUpdate 
    status.pendingUpdate = nil
}

4.3. SyncPod

pkg/kubelet/kubelet.go:Pod同步会先更新apiserver,再调用CRI将实际往期望收敛。

  1. generateAPIPodStatus:根据podStatus(CRI实际状态)和pod(Spec期望),实际Pod的Status,后面写入apiserver;

  2. statusManager.SetPodStatus:更新缓存的Pod的Status,异步更新到apiserver;

  3. probeManager.AddPod:启动探针协程;

  4. containerRuntime.SyncPod:调 CRI 把实际状态拉齐;

Go 复制代码
func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (isTerminal bool, postSync func(), err error) {
    // 1. 用 runtime 状态生成 v1.PodStatus
    apiPodStatus := kl.generateAPIPodStatus(ctx, pod, podStatus, false)
    // 2. 已终态:不再创建容器,下一轮走 Terminatin
    if apiPodStatus.Phase == v1.PodSucceeded || apiPodStatus.Phase == v1.PodFailed {
        kl.statusManager.SetPodStatus(logger, pod, apiPodStatus)
        return true, nil, nilg
    }
     // 3. 本地缓存,异步 PATCH apiserver
    kl.statusManager.SetPodStatus(logger, pod, apiPodStatus)
    // 4. Pod 级 cgroup
    pcm.EnsureExists(logger, pod)
    // 5. 静态 Pod 保证 mirror 存在且与 spec 一致
    kl.tryReconcileMirrorPods(ctx, pod, mirrorPod)
    // 6. /var/lib/kubelet/pods/<uid>/
    kl.makePodDataDirs(pod)
    // 7. 等卷 attach/mount
    kl.volumeManager.WaitForAttachAndMount(ctx, pod)
    // 8. 启动探针 worker
    kl.probeManager.AddPod(ctx, pod)              
    // 9. 核心:对比 Spec vs podStatus,调 CRI 把实际状态拉齐
    result := kl.containerRuntime.SyncPod(sctx, pod, podStatus, pullSecrets, kl.crashLoopBackOff, restartingAllContainers)
    return false, postSync, result.Error()
}

pkg/kubelet/kuberuntime/kuberuntime_manager.go:runtime 侧 SyncPod,算期望和实际的diff,启停容器。

  1. computePodActions:计算本轮调谐要做什么,比如:发现init容器启动完成,本轮可以启动业务容器;发现容器liveness探针失败,本轮需要重启容器;

  2. 如果Pod刚创建,先要建Sandbox容器(用于为Pod内所有业务容器提供共享的网络、IPC等命名空间),调用CRI RunPodSandbox + PodSandboxStatus

  3. 先启动init容器,再启动业务容器;(不是一轮SyncPod);

  4. 启动容器,调用CRI ImageStatus →(必要时)PullImageCreateContainerStartContainer

Go 复制代码
func (m *kubeGenericRuntimeManager) SyncPod(...) (result kubecontainer.PodSyncResult) {
    // 1. 算本轮要杀/建什么
    podContainerChanges := m.computePodActions(ctx, pod, podStatus, restartAllContainers)
    // 2. 需要整 pod 重建时,先 kill pod sandbox
    if podContainerChanges.KillPod {
        m.killPodWithSyncResult(...)
    } else {
        // 3. 否则只杀不该继续运行的容器(如 liveness/startup 失败、spec 变化等)
        for containerID, containerInfo := range podContainerChanges.ContainersToKill {
            m.killContainer(ctx, pod, containerID, containerInfo.name, containerInfo.message, ...)
        }
    }
    if podContainerChanges.CreateSandbox {
         // 4. CRI RunPodSandbox
        podSandboxID, msg, err = m.createPodSandbox(ctx, pod, attempt)
         // 5. CRI PodSandboxStatus 这里能拿到PodIP
        resp, err := m.runtimeService.PodSandboxStatus(ctx, podSandboxID, false)
        m.runtimeHelper.OnPodSandboxReady(ctx, pod)
    }
    // 单个容器启动 CRI ImageStatus → PullImage → CreateContainer → StartContainer
    start := func(..., spec *startSpec) error {
        m.startContainer(ctx, podSandboxID, podSandboxConfig, spec, ...)
        return nil
    }
    // 6. ephemeral container(debug容器)
    for _, idx := range podContainerChanges.EphemeralContainersToStart { start(..., ephemeral) }
    // 7. init container
    for _, idx := range podContainerChanges.InitContainersToStart { start(..., init) } 
    // 8. 业务容器
    for _, idx := range podContainerChanges.ContainersToStart { start(..., container) }
    return result
}

4.4. CRI 调用细节

CRI 接口定义见 staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto

4.4.1. kubelet 与 CRI 的通讯方式

Java 复制代码
kubelet (kuberuntimeManager)
    │
    ├─ cri-client/remote_runtime.go ──gRPC──► RuntimeService
    │
    └─ cri-client/remote_image.go   ──gRPC──► ImageService
  1. 传输层Unix Domain Socket

pkg/kubelet/apis/config/v1beta1/defaults.go:默认UDS位置

Go 复制代码
if obj.ContainerRuntimeEndpoint == "" {
    obj.ContainerRuntimeEndpoint = "unix:///run/containerd/containerd.sock"
}
  1. 客户端封装NewRemoteRuntimeService / NewRemoteImageService 各维护一条 Grpc长连接

  2. crictl 与 kubelet 使用相同的通讯方式

4.4.2. CRI / CNI 状态探测(RuntimeService.Status

作用:探测 container runtime 与 CNI 网络是否就绪,决定节点能否调度 / syncLoop 是否退避。

方向 字段 含义
出参 StatusResponse status.conditions[] 必须 包含 RuntimeReadyNetworkReady

调用时机(见 1.1):

  • Kubelet.Run 启动时先调一次 updateRuntimeUp

  • 之后每 5s 周期性探测

  • syncNodeStatusNodeReady 时读 runtimeState 里缓存的结果

crictlcrictl info

Shell 复制代码
$ crictl info
{
  "status": {
    "conditions": [
      { "status": true, "type": "RuntimeReady" },
      { "status": true, "type": "NetworkReady" }
    ]
  }
}

4.4.3. PLEG:List 粗扫 + Status 精读

作用 :GenericPLEG 用两套 CRI 读「节点上实际有什么」,再 diff / 刷新 podCache,驱动 syncLoop。

阶段 CRI 粒度 用途
Relist 粗扫 ListPodSandbox + ListContainers 全节点列表 + 粗状态 1s diff,生成 ContainerStarted/Died/...
有变化时精读 PodSandboxStatus + ContainerStatus 单 sandbox / 单容器详情 GetPodStatus → 写入 podCache(Pod 实际态)

ListPodSandbox + ListContainers: 粗状态

Shell 复制代码
$ crictl pods
POD ID        NAME        STATE
7c906fcb0f308 nginx-123   Ready

$ crictl ps
CONTAINER     NAME         POD ID
20384828e8074 log-sidecar  7c906fcb0f308
e5d25a9e9213b nginx        7c906fcb0f308

PodSandboxStatus + ContainerStatus: 详情

Shell 复制代码
crictl inspectp <sandbox-id>     # = PodSandboxStatus(只有 sandbox,不含业务容器)
crictl inspect <container-id>    # = ContainerStatus

4.4.4. 创建 Pod:1 个 Sandbox + N 个 Container

SyncPod 把期望 Pod 落到 runtime------先建 1 个 sandbox ,再按需启动 N 个 init / 业务 / ephemeral 容器。

A. 创建 Sandbox ------ RunPodSandbox
方向 字段 含义
入参 configPodSandboxConfig name/ns/uid、DNS、端口、安全上下文等
入参 runtime_handler RuntimeClass(空 = 默认)
出参 pod_sandbox_id 新 sandbox ID
  • 时机:computePodActions.CreateSandbox == true(首次创建、UID 变化重建等)

  • crictlcrictl runp <pod-config.json>

B. 创建并启动每个 Container

同一 startContainer 内顺序执行;init 与业务容器分多轮 SyncPod。

Step1、ImageStatus ------ 本地有没有镜像
方向 字段 含义
入参 image.image 镜像名或 ID
出参 image 有则返回元数据;无则为 nil
  • 决定要不要 Pull

  • crictlcrictl inspecti <image>

Step2、PullImage ------ 需要时拉取
方向 字段 含义
入参 image.image / auth / sandbox_config 镜像、认证、可选 Pod 上下文
出参 image_ref 本地镜像 ID,Create 时引用
  • 本地无镜像,或 imagePullPolicy=Always

  • crictlcrictl pull <image>

Step3、CreateContainer ------ 创建容器对象
方向 字段 含义
入参 pod_sandbox_id / config / sandbox_config 归属 sandbox、容器配置、sandbox 配置
出参 container_id 新容器 ID
  • crictlcrictl create <sandbox-id> <container-config.json> <pod-config.json>
Step4、StartContainer ------ 启动进程
方向 字段 含义
入参 container_id 上一步 ID
出参 (空) 成功即 RUNNING
  • crictlcrictl start <container-id>
Step5、ExecSync ------ hook / Exec 探针
方向 字段 含义
入参 container_id / cmd[] / timeout 容器、命令、超时(秒)
出参 stdout / stderr / exit_code 输出与退出码
  • PostStart:Start 成功后立刻执行

  • PreStop:停容器前执行

  • Exec 探针probe 周期性调用

  • crictlcrictl exec <container-id> <cmd...>

五、Pod 删除

pkg/kubelet/pod_workers.go:apiserver删除Pod,会标记DeletionTimestamp删除时间。kubelet侧syncLoop发现DeletionTimestamp非空,标记terminatingAt=now,通知Worker处理。

Go 复制代码
func (p *podWorkers) UpdatePod(ctx context.Context, options UpdatePodOptions) {
  p.podLock.Lock()
  defer p.podLock.Unlock()
  status, ok := p.podSyncStatuses[uid]
  if !ok {
    // 首次发现该pod,创建同步状态podSyncStatus
    p.podSyncStatuses[uid] = status
  }
  // 期望Pod
  pod := options.Pod
  var becameTerminating bool
  if !status.IsTerminationRequested() {
     // terminatingAt == 0
    switch {
     // apiserver标记删除
    case pod.DeletionTimestamp != nil:
      status.deleted = true
      status.terminatingAt = now
      becameTerminating = true
    }
  }

  // 每个pod启动一个worker协程处理
  podUpdates, exists := p.podUpdates[uid]
  if !exists {
    // 创建worker协程
  }
  // 把Pod期望状态更新到pendingUpdate
  status.pendingUpdate = &options
  // 写入podUpdates channel,通知worker处理
  select {
  case podUpdates <- struct{}{}:
  default:
  }
  // 如果要停止Pod,这里可以通知worker取消,但是不一定能立即取消,需要等下一轮SyncPod
  if (becameTerminating || wasGracePeriodShortened) && status.cancelFn != nil {
    status.cancelFn()
    return
  }
}

pkg/kubelet/pod_workers.go:Pod的Worker协程,取出SyncLoop的Pod期望时,区分是杀Pod还是同步Pod。

Go 复制代码
func (p *podWorkers) startPodSync(...) {
	p.podLock.Lock()
	defer p.podLock.Unlock()
	status, ok := p.podSyncStatuses[podUID]
    // 计算同步类型
	update.WorkType = status.WorkType()
	// 取出pendingUpdate
	update.Options = *status.pendingUpdate
}

func (s *podSyncStatus) WorkType() PodWorkerState {
	// !s.terminatedAt.IsZero()
	if s.IsTerminated() {
		return TerminatedPod
	}
	// !s.terminatingAt.IsZero() --- DeletionTimestamp!=nil
    // 杀Pod
	if s.IsTerminationRequested() {
		return TerminatingPod
	}
	return SyncPod
}

pkg/kubelet/kubelet.go: SyncTerminatingPod,worker停止Pod

  1. 更新Pod Status到apiserver;

  2. 调用CRI杀Pod;

  3. 调用CRI查sandbox和container状态;

  4. 更新Pod Status到apiserver;

Go 复制代码
func (kl *Kubelet) SyncTerminatingPod(...) (err error) {
  // 1. 生成Pod的Status
  apiPodStatus := kl.generateAPIPodStatus(ctx, pod, podStatus, false)
  // 2. 更新缓存Pod的Status 异步Patch到apiserver
  kl.statusManager.SetPodStatus(logger, pod, apiPodStatus)
  // 停止探针
  kl.probeManager.StopLivenessAndStartup(pod)
  // 模型转换
  p := kubecontainer.ConvertPodStatusToRunningPod(kl.getRuntime().Type(), podStatus)
  // 3. 调用CRI杀死Pod
  if err := kl.killPod(ctx, pod, p, gracePeriod); err != nil {
  }
  // 4. ListPodSandbox + ListContainers
  runtimePod, err := kl.containerRuntime.GetPod(ctx, pod.UID)
  // 5. PodSandboxStatus + ContainerStatus
  stoppedPodStatus, err := kl.containerRuntime.GetPodStatus(ctx, runtimePod)
  // 6. 生成Pod的Status
  apiPodStatus = kl.generateAPIPodStatus(ctx, pod, stoppedPodStatus, true)
  // 7. 更新缓存Pod的Status 异步Patch到apiserver
  kl.statusManager.SetPodStatus(logger, pod, apiPodStatus)
  return nil
}

注意,这里并没有删除sandbox和container,这个操作由kubelet的后台GC协程完成。

pkg/kubelet/kubelet.go: 每分钟执行container gc,调用CRI真实删除sandbox和container。

Go 复制代码
func (kl *Kubelet) StartGarbageCollection(ctx context.Context) {
  go wait.Until(func() {
    if err := kl.containerGC.GarbageCollect(ctx); err != nil {
    }
  }, ContainerGCPeriod, wait.NeverStop)
}

pkg/kubelet/kuberuntime/kuberuntime_container.go:先并行停container,最后停sandbox。

Go 复制代码
func (m *kubeGenericRuntimeManager) killPodWithSyncResult(...) (result kubecontainer.PodSyncResult) {
  // 1. 停container
  killContainerResults := m.killContainersWithSyncResult(ctx, pod, runningPod, gracePeriodOverride)
  for _, containerResult := range killContainerResults {
    result.AddSyncResult(containerResult)
  }
  // 2. CRI StopPodSandbox 停sandbox
  for _, podSandbox := range runningPod.Sandboxes {
    if err := m.runtimeService.StopPodSandbox(ctx, podSandbox.ID.ID);
  }
}
func (m *kubeGenericRuntimeManager) killContainersWithSyncResult(...) (syncResults []*kubecontainer.SyncResult) {
  logger := klog.FromContext(ctx)
  containerResults := make(chan *kubecontainer.SyncResult, len(runningPod.Containers))
  wg := sync.WaitGroup{}
  wg.Add(len(runningPod.Containers))
  for _, container := range runningPod.Containers {
    // 每个container一个协程
    go func(container *kubecontainer.Container) {
      defer utilruntime.HandleCrashWithContext(ctx)
      defer wg.Done()
      killContainerResult := kubecontainer.NewSyncResult(kubecontainer.KillContainer, container.Name)
      // 停container
      if err := m.killContainer(...); err != nil {
        killContainerResult.Fail(kubecontainer.ErrKillContainer, err.Error())
      }
      containerResults <- killContainerResult
    }(container)
  }
  wg.Wait()
  close(containerResults)
  for containerResult := range containerResults {
    syncResults = append(syncResults, containerResult)
  }
  return
}

pkg/kubelet/kuberuntime/kuberuntime_container.go:停container

  1. 优雅停机超时时间,默认 pod.Spec.TerminationGracePeriodSeconds = 30s;

  2. 执行PreStop钩子,优雅停机超时时间 -= PreStop耗时;

  3. 调用CRI StopContainer,传入剩余优雅停机超时时间;

所以容器优雅停机,先发送SIGTERM,等超时时间,发送SIGKILL是由CRI实现的,不是kubelet。

Go 复制代码
func (m *kubeGenericRuntimeManager) killContainer(...) error {
  // 1. 获取停机时间,默认gracePeriod = pod.Spec.TerminationGracePeriodSeconds = 30s
  gracePeriod := setTerminationGracePeriod(ctx, pod, containerSpec, containerName, containerID, reason)
  // 2. 执行 PreStop,停机时间 -= PreStop的执行时间
  if containerSpec.Lifecycle != nil 
            && containerSpec.Lifecycle.PreStop != nil 
            && gracePeriod > 0 {
    gracePeriod = gracePeriod - m.executePreStopHook(ctx, pod, containerID, containerSpec, gracePeriod)
  }
  // 3. CRI StopContainer 停container,传入优雅停机时间超时gracePeriod
  err := m.runtimeService.StopContainer(ctx, containerID.ID, gracePeriod)
}

总结

kubelet自身健康与否取决于两点:

1)Lease:kubelet的心跳

  • kubelet:每10s创建或更新Lease资源,Spec.RenewTime=当前时间。

  • controller-manager:根据RenewTime发现kubelet超过50s没上报Lease,会设置Node的Condition Ready/MemoryPressure/DiskPressure/PIDPressure=Unknown,Node最终会打上污点node.kubernetes.io/unreachable:NoSchedule+node.kubernetes.io/unreachable:NoExecute造成驱逐。

2)Node Status:节点状态

  • kubelet:每10s统计一次Node状态,有变化或超过5min,则更新到apiserver。如果kubelet发现自己不健康,会主动设置Node的Condition的ReadyFalse。(主要健康指标是CRI Status正常返回,PLEG循环3min不超时)

  • controller-manager:发现Node的Condition Ready=False,会打上污点node.kubernetes.io/not-ready:NoSchedule+node.kubernetes.io/not-ready:NoExecute造成驱逐。

kubelet主要是将pod的状态往期望收敛:

1)spec 期望:多来源,其中包含:list&watch apiserver、list&watch 静态pod(/etc/kubernetes/manifests);

2)status 状态:主要来源于 PLEG 每秒 relist 调用cri,更新podCache

syncLoop是kubelet的主循环,接收多路channel变更,分发给podWorker

1)变更来源:

  • config:apiserver/静态pod变更;

  • probe:探针,比如liveness probe失败到达阈值,需要根据restartPolicy重启容器;

  • PLEG:PLEG 每秒 relist 调用cri,发现容器状态变更,比如initContainer启动完成,需要触发主容器启动;

  • syncCh:每秒扫描 workQueue,处理需要重新同步的Pod;(即使Pod同步成功,每分钟也会从工作队列里捞出来重新调谐一次);

2)每个Pod对应一个podWorkerpodWorker会调用SyncPod同步pod。podWorker每轮会先计算新的Pod状态,异步Patch到apiserver,computePodActions计算需要执行的操作,调用CRI实际执行操作;

相关推荐
烂蜻蜓1 小时前
Flask入门教程(九):模板渲染——用Jinja2构建动态页面
后端·python·flask
QQ_21696290961 小时前
【源码编号:project93375】SpringBoot汽车维修管理信息系统:客户车辆、维修预约、工单派发、配件结算全流程实战
java·spring boot·后端·汽车·springboot·需求分析
gis开发之家2 小时前
Spring Boot 4 深度解析,JdbcTemplate 实战——轻量级数据库操作方案
java·数据库·spring boot·后端
名字还没想好☜3 小时前
kubectl 排障实战:jsonpath 精准取值、custom-columns、events 排序与 top 速查
运维·前端·chrome·docker·kubernetes
2601_962063974 小时前
Spring Boot拦截器(Interceptor)详解
java·spring boot·后端
明月_清风4 小时前
GitHub Actions 从入门到实战:一文搞懂 CI/CD 自动化
后端·ci/cd·github
foggyprojects5 小时前
AI 问数也要做行级权限:让 Codex 给销售查询加上权限
后端
大明二代5 小时前
使用 Traefik、cert-manager 和 DNS-01 为内网 Kubernetes 服务配置 HTTPS
git·kubernetes·flux