前言
本文分析kubelet源码,包括:
-
kubelet心跳和Node信息上报;
-
syncLoop主循环;
-
worker循环;
-
同步Pod和删除Pod;
注:基于kubernetes 1.36.1。
一、kubelet 心跳和 Node 信息
kubelet 向控制面证明「节点还活着、还能调度」走两条独立路径:
| 路径 | 对象 | 默认周期 | 作用 |
|---|---|---|---|
| Node Lease | Lease |
续约 10s (40s * 0.25) |
心跳:node-lifecycle-controller 主要靠它判节点失联 |
| Node Status | Node.Status |
计算 10s ;无变化上报 5min | 节点信息:Ready/容量/地址/镜像/压力等 |
两条路径都在 Kubelet.Run 里起 goroutine,不经过 syncLoop。
pkg/kubelet/kubelet.go:Run 启动心跳与 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 |
false → NodeReady=False |
NetworkReady |
网络插件已起来,能接需要网络的容器 | runtimeState.networkError |
false → NodeReady=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.go:setNodeStatus 用一组 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 Ready 为 True,要求:
-
runtimeErrors为空:1)CRIRuntimeService.Status返回RuntimeReady=True;2)PLEG循环不超时(3分钟); -
networkErrors为空:CRIRuntimeService.Status返回NetworkReady=True; -
storageErrors为空:CSINode资源创建成功; -
nodeShutdownErrors为空:nodeShutdownManager.IsShuttingDown返回false,表示不在停机过程中; -
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.go:syncLoop 外层循环。
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.go:syncLoopIteration 一次 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 → configCh → HandlePod* → UpdatePod。
pkg/kubelet/kubelet.go:makePodSourceConfig 注册三类源。
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.go:HandlePodAdditions 写入 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)。 HandlePodRemoves:podManager.RemovePod + deletePod → UpdatePod(SyncPodKill)。
3.2. probe 探针
触发链 :SyncPod 里 probeManager.AddPod 给每个探针起一个 worker 协程 → 周期探测 → 结果变化写入 resultsManager.Set → 对应 Updates() channel → syncLoopIteration → HandlePodSyncs。
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)
PLEG 是 Pod Lifecycle Event Generator 的缩写,负责周期性对比 runtime 中 Pod/Container 状态并产出生命周期事件。
触发链 :GenericPLEG 每 1s Relist(CRI ListPodSandbox + ListContainers)→ 对比新旧状态生成 ContainerStarted/Died/... → eventChannel → plegCh → HandlePodSyncs。
pkg/kubelet/pleg/generic.go:
-
Relist调用CRI
ListPodSandbox+ListContainers获取当前Pod状态,比对内存中的Pod状态,生成事件; -
只要Pod中一个容器变化(计算出变化事件),就触发更新内存podCache(
GetPodStatus:PodSandboxStatus+ContainerStatus); -
发送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):
-
发事件前 :
reconcilePodRecord已调用updateCache→GetPodStatus,把 sandbox + 容器状态写入podCache; -
syncLoop 收到事件 (
pkg/kubelet/kubelet.go); -
SyncPod里的实际决策 :读 podCache + pod spec,computePodActions决定启动/停止容器;
PLEG 事件 = 「这个 Pod 的 runtime 状态变了,且 podCache 已更新,请立即 sync 一次」。真正决定启动/重启/收尾的是
SyncPod+computePodActions,不是事件类型本身。
3.4. syncCh:1 秒定时 + workQueue
触发链 :每次 podWorkerLoop 跑完 completeWork 把 UID 按 backoff / SyncFrequency(默认 1min )丢进 workQueue → syncTicker 每秒 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.go:getPodsToSync从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协程处理。
-
保证每个Pod只有一个worker协程
podWorkerLoop; -
podSyncStatus.pendingUpdate是本次期望状态; -
通过
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将实际往期望收敛。
-
generateAPIPodStatus:根据podStatus(CRI实际状态)和pod(Spec期望),实际Pod的Status,后面写入apiserver; -
statusManager.SetPodStatus:更新缓存的Pod的Status,异步更新到apiserver; -
probeManager.AddPod:启动探针协程; -
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,启停容器。
-
computePodActions:计算本轮调谐要做什么,比如:发现init容器启动完成,本轮可以启动业务容器;发现容器liveness探针失败,本轮需要重启容器; -
如果Pod刚创建,先要建Sandbox容器(用于为Pod内所有业务容器提供共享的网络、IPC等命名空间),调用CRI
RunPodSandbox+PodSandboxStatus; -
先启动init容器,再启动业务容器;(不是一轮SyncPod);
-
启动容器,调用CRI
ImageStatus→(必要时)PullImage→CreateContainer→StartContainer;
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
- 传输层 :Unix Domain Socket
pkg/kubelet/apis/config/v1beta1/defaults.go:默认UDS位置
Go
if obj.ContainerRuntimeEndpoint == "" {
obj.ContainerRuntimeEndpoint = "unix:///run/containerd/containerd.sock"
}
-
客户端封装 :
NewRemoteRuntimeService/NewRemoteImageService各维护一条 Grpc长连接 -
crictl 与 kubelet 使用相同的通讯方式
4.4.2. CRI / CNI 状态探测(RuntimeService.Status)
作用:探测 container runtime 与 CNI 网络是否就绪,决定节点能否调度 / syncLoop 是否退避。
| 方向 | 字段 | 含义 |
|---|---|---|
出参 StatusResponse |
status.conditions[] |
必须 包含 RuntimeReady、NetworkReady |
调用时机(见 1.1):
-
Kubelet.Run启动时先调一次updateRuntimeUp -
之后每 5s 周期性探测
-
syncNodeStatus写NodeReady时读runtimeState里缓存的结果
crictl :crictl 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
| 方向 | 字段 | 含义 |
|---|---|---|
| 入参 | config(PodSandboxConfig) |
name/ns/uid、DNS、端口、安全上下文等 |
| 入参 | runtime_handler |
RuntimeClass(空 = 默认) |
| 出参 | pod_sandbox_id |
新 sandbox ID |
-
时机:
computePodActions.CreateSandbox == true(首次创建、UID 变化重建等) -
crictl :
crictl runp <pod-config.json>
B. 创建并启动每个 Container
同一 startContainer 内顺序执行;init 与业务容器分多轮 SyncPod。
Step1、ImageStatus ------ 本地有没有镜像
| 方向 | 字段 | 含义 |
|---|---|---|
| 入参 | image.image |
镜像名或 ID |
| 出参 | image |
有则返回元数据;无则为 nil |
-
决定要不要 Pull
-
crictl :
crictl inspecti <image>
Step2、PullImage ------ 需要时拉取
| 方向 | 字段 | 含义 |
|---|---|---|
| 入参 | image.image / auth / sandbox_config |
镜像、认证、可选 Pod 上下文 |
| 出参 | image_ref |
本地镜像 ID,Create 时引用 |
-
本地无镜像,或
imagePullPolicy=Always -
crictl :
crictl pull <image>
Step3、CreateContainer ------ 创建容器对象
| 方向 | 字段 | 含义 |
|---|---|---|
| 入参 | pod_sandbox_id / config / sandbox_config |
归属 sandbox、容器配置、sandbox 配置 |
| 出参 | container_id |
新容器 ID |
- crictl :
crictl create <sandbox-id> <container-config.json> <pod-config.json>
Step4、StartContainer ------ 启动进程
| 方向 | 字段 | 含义 |
|---|---|---|
| 入参 | container_id |
上一步 ID |
| 出参 | (空) | 成功即 RUNNING |
- crictl :
crictl start <container-id>
Step5、ExecSync ------ hook / Exec 探针
| 方向 | 字段 | 含义 |
|---|---|---|
| 入参 | container_id / cmd[] / timeout |
容器、命令、超时(秒) |
| 出参 | stdout / stderr / exit_code |
输出与退出码 |
-
PostStart:Start 成功后立刻执行
-
PreStop:停容器前执行
-
Exec 探针 :
probe周期性调用 -
crictl :
crictl 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
-
更新Pod Status到apiserver;
-
调用CRI杀Pod;
-
调用CRI查sandbox和container状态;
-
更新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
-
优雅停机超时时间,默认 pod.Spec.TerminationGracePeriodSeconds = 30s;
-
执行PreStop钩子,优雅停机超时时间 -= PreStop耗时;
-
调用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的ConditionReady/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的Ready为False。(主要健康指标是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对应一个podWorker,podWorker会调用SyncPod同步pod。podWorker每轮会先计算新的Pod状态,异步Patch到apiserver,computePodActions计算需要执行的操作,调用CRI实际执行操作;