Part 2: 容器运行时与CRI——超深度逐行分析


一、模块定位

1.1 CRI(Container Runtime Interface)业务职责

CRI是Kubernetes容器运行时接口,是Kubelet与底层容器运行时之间的标准通信协议。其核心业务职责包括:

  • 容器生命周期管理:容器的创建、启动、停止、删除、查询
  • Pod Sandbox管理:Pod沙箱的创建、停止、删除、状态查询
  • 镜像管理:镜像的拉取、查询、列表、删除、文件系统信息
  • 流式操作:Exec、Attach、PortForward等交互式操作
  • 运行时状态查询:版本信息、运行时状态、容器统计
  • 容器日志管理:日志读取、日志轮转、日志清理
  • 容器GC:死亡容器回收、沙箱回收、日志目录回收
  • 安全上下文:Seccomp、AppArmor、SELinux、Capabilities等安全策略应用
  • RuntimeClass动态选择:根据Pod Spec的runtimeClassName选择不同运行时

1.2 在Kubelet中的位置

CRI模块位于Kubelet架构的核心层,是Kubelet与容器运行时之间的桥梁:

┌─────────────────────────────────────────────┐
│                Kubelet                       │
│  ┌─────────┐  ┌──────────┐  ┌────────────┐ │
│  │ PLEG    │  │SyncLoop  │  │PodWorker   │ │
│  └────┬────┘  └─────┬────┘  └──────┬─────┘ │
│       │             │              │         │
│       └─────────────┼──────────────┘         │
│                     ▼                        │
│  ┌──────────────────────────────────────┐    │
│  │   kubeGenericRuntimeManager          │    │
│  │   (kuberuntime包 - CRI适配层)        │    │
│  └──────────────┬───────────────────────┘    │
│                 │                             │
│  ┌──────────────▼───────────────────────┐    │
│  │   instrumentedRuntimeService         │    │
│  │   instrumentedImageManagerService    │    │
│  └──────────────┬───────────────────────┘    │
│                 │                             │
│  ┌──────────────▼───────────────────────┐    │
│  │   remoteRuntimeService (gRPC)        │    │
│  │   或 dockershim (内置适配)            │    │
│  └──────────────┬───────────────────────┘    │
│                 │                             │
└─────────────────┼────────────────────────────┘
                  ▼
   ┌──────────────────────────────┐
   │  容器运行时 (containerd/CRI-O) │
   └──────────────────────────────┘

二、模块整体结构

2.1 类结构与接口定义

«interface»

KubeGenericRuntime

+Runtime

+StreamingRuntime

+CommandRunner

kubeGenericRuntimeManager

-runtimeName: string

-recorder: EventRecorder

-osInterface: OSInterface

-machineInfo: MachineInfo

-containerGC: containerGC

-keyring: DockerKeyring

-runner: HandlerRunner

-runtimeHelper: RuntimeHelper

-livenessManager: Manager

-readinessManager: Manager

-startupManager: Manager

-cpuCFSQuota: bool

-cpuCFSQuotaPeriod: Duration

-imagePuller: ImageManager

-runtimeService: RuntimeService

-imageService: ImageManagerService

-versionCache: ObjectCache

-seccompProfileRoot: string

-internalLifecycle: InternalContainerLifecycle

-legacyLogProvider: LegacyLogProvider

-logManager: ContainerLogManager

-runtimeClassManager: Manager

-logReduction: LogReduction

-podStateProvider: podStateProvider

+Type() : string

+Version() : Version

+APIVersion() : Version

+Status() : RuntimeStatus

+GetPods(all) : Pod[]

+SyncPod(pod, podStatus, pullSecrets, backOff) : PodSyncResult

+KillPod(pod, runningPod, gracePeriodOverride) : error

+GetPodStatus(uid, name, namespace) : PodStatus

+GetContainerLogs(ctx, pod, containerID, logOptions, stdout, stderr) : error

+DeleteContainer(containerID) : error

+GarbageCollect(gcPolicy, allSourcesReady, evictNonDeletedPods) : error

+UpdatePodCIDR(podCIDR) : error

+GetExec(id, cmd, stdin, stdout, stderr, tty) : URL

+GetAttach(id, stdin, stdout, stderr, tty) : URL

+GetPortForward(podName, podNamespace, podUID, ports) : URL

+RunInContainer(id, cmd, timeout) : byte[]

+PullImage(image, pullSecrets, podSandboxConfig) : string

+GetImageRef(image) : string

+ListImages() : Image[]

+RemoveImage(image) : error

+ImageStats() : ImageStats

+SupportsSingleFileMapping() : bool

«interface»

RuntimeService

+Version(apiVersion) : VersionResponse

+CreateContainer(podSandboxID, config, sandboxConfig) : string

+StartContainer(containerID) : error

+StopContainer(containerID, timeout) : error

+RemoveContainer(containerID) : error

+ListContainers(filter) : Container[]

+ContainerStatus(containerID) : ContainerStatus

+UpdateContainerResources(containerID, resources) : error

+ReopenContainerLog(containerID) : error

+ExecSync(containerID, cmd, timeout) : byte[]

+Exec(req) : ExecResponse

+Attach(req) : AttachResponse

+RunPodSandbox(config, runtimeHandler) : string

+StopPodSandbox(podSandboxID) : error

+RemovePodSandbox(podSandboxID) : error

+PodSandboxStatus(podSandboxID) : PodSandboxStatus

+ListPodSandbox(filter) : PodSandbox[]

+ContainerStats(containerID) : ContainerStats

+ListContainerStats(filter) : ContainerStats[]

+PortForward(req) : PortForwardResponse

+UpdateRuntimeConfig(runtimeConfig) : error

+Status() : RuntimeStatus

«interface»

ImageManagerService

+ListImages(filter) : Image[]

+ImageStatus(image) : Image

+PullImage(image, auth, podSandboxConfig) : string

+RemoveImage(image) : error

+ImageFsInfo() : FilesystemUsage[]

instrumentedRuntimeService

-service: RuntimeService

instrumentedImageManagerService

-service: ImageManagerService

containerGC

-client: RuntimeService

-manager: kubeGenericRuntimeManager

-podStateProvider: podStateProvider

+GarbageCollect(gcPolicy, allSourcesReady, evictTerminatedPods) : error

remoteRuntimeService

-timeout: Duration

-runtimeClient: RuntimeServiceClient

-logReduction: LogReduction

2.2 CRI接口层次图

辅助模块

传输实现层

度量封装层

CRI抽象接口(internalapi)

CRI核心层

Kubelet内部接口层

Kubelet上层调用者

PodWorker/SyncLoop

PLEG

Kubelet Server

kubecontainer.Runtime

kubecontainer.StreamingRuntime

kubecontainer.ImageService

kubecontainer.CommandRunner

kubeGenericRuntimeManager

podActions/computePodActions

startContainer/killContainer

createPodSandbox/generatePodSandboxConfig

RuntimeService

ImageManagerService

RuntimeVersioner

ContainerManager

PodSandboxManager

ContainerStatsManager

instrumentedRuntimeService

instrumentedImageManagerService

remoteRuntimeService - gRPC

remoteImageService - gRPC

dockershim - Docker适配

images.ImageManager

logs.ContainerLogManager

runtimeclass.Manager

containerGC

2.3 核心方法清单

所属结构 方法 功能
kubeGenericRuntimeManager SyncPod Pod同步核心方法,7步完成Pod的创建/更新
kubeGenericRuntimeManager computePodActions 计算Pod变更动作(kill/start/sandbox)
kubeGenericRuntimeManager startContainer 容器启动4步:拉镜像→创建→启动→postStart
kubeGenericRuntimeManager killContainer 容器终止:preStop hook→StopContainer
kubeGenericRuntimeManager killPodWithSyncResult 并发kill所有容器+stop沙箱
kubeGenericRuntimeManager createPodSandbox 创建Pod沙箱
kubeGenericRuntimeManager generatePodSandboxConfig 生成Pod沙箱配置
kubeGenericRuntimeManager generateContainerConfig 生成容器配置
kubeGenericRuntimeManager PullImage 镜像拉取(含凭证链)
kubeGenericRuntimeManager GetPodStatus 获取Pod状态
kubeGenericRuntimeManager doBackOff 容器退避检查
kubeGenericRuntimeManager podSandboxChanged 沙箱变更检测
containerGC GarbageCollect 垃圾回收三步:容器→沙箱→日志
imageManager EnsureImageExists 确保镜像存在(拉取策略)
runtimeclass.Manager LookupRuntimeHandler RuntimeClass查找
containerLogManager Start/Clean 日志轮转与清理

2.4 内部调用关系

SyncPod完整调用链

SyncPod

computePodActions

podSandboxChanged

containerChanged

findNextInitContainerToRun

shouldRestartOnFailure

killPodWithSyncResult

killContainersWithSyncResult

killContainer

executePreStopHook

StopContainer - CRI

StopPodSandbox - CRI

pruneInitContainersBeforeStart

createPodSandbox

generatePodSandboxConfig

generatePodSandboxLinuxConfig

runtimeClassManager.LookupRuntimeHandler

RunPodSandbox - CRI

startContainer

imagePuller.EnsureImageExists

shouldPullImage

PullImage - CRI

generateContainerConfig

generateLinuxContainerConfig

determineEffectiveSecurityContext

CreateContainer - CRI

StartContainer - CRI

runner.Run - PostStartHook

2.5 数据流入流出方式

数据流入:

  • SyncPod接收:v1.Podkubecontainer.PodStatus[]v1.Secret(pullSecrets)、flowcontrol.Backoff
  • CRI gRPC响应:runtimeapi.VersionResponseruntimeapi.ContainerStatusruntimeapi.PodSandboxStatus
  • 探测结果:proberesults.Manager提供liveness/readiness/startup探测结果

数据流出:

  • SyncPod输出:kubecontainer.PodSyncResult
  • CRI gRPC请求:runtimeapi.RunPodSandboxRequestruntimeapi.CreateContainerRequest
  • 事件记录:recorder.Event输出到Kubernetes事件系统
  • 日志输出:通过io.Writer输出到kubectl log请求

三、核心业务逻辑深度解析

3.1 CRI完整调用链路

logManager runtimeclass.Manager imageManager 容器运行时(containerd/CRI-O) remoteRuntimeService(gRPC) instrumentedRuntimeService computePodActions kubeGenericRuntimeManager PodWorker logManager runtimeclass.Manager imageManager 容器运行时(containerd/CRI-O) remoteRuntimeService(gRPC) instrumentedRuntimeService computePodActions kubeGenericRuntimeManager PodWorker Step1: 计算Pod变更 Step2: 杀掉Pod Step3: Kill指定容器 alt [KillPod=true] [个别容器需要kill] Step4: 创建Sandbox alt [CreateSandbox=true] Step5-7: 启动容器 loop [每个待启动容器] SyncPod(pod, podStatus, pullSecrets, backOff) computePodActions(pod, podStatus) podActions{KillPod,CreateSandbox,ContainersToStart,ContainersToKill} killPodWithSyncResult(pod, runningPod) killContainersWithSyncResult(并发kill所有容器) StopPodSandbox(sandboxID) gRPC StopPodSandbox StopPodSandbox killContainer(pod, containerID, name, message, reason) LookupRuntimeHandler(pod.Spec.RuntimeClassName) runtimeHandler RunPodSandbox(config, runtimeHandler) gRPC RunPodSandbox RunPodSandbox(创建网络命名空间+CNI) podSandboxID podSandboxID podSandboxID PodSandboxStatus(podSandboxID) gRPC PodSandboxStatus determinePodSandboxIPs(获取Pod IP) EnsureImageExists(pod, container, pullSecrets) ImageStatus / PullImage imageRef generateContainerConfig(构建CRI ContainerConfig) CreateContainer(podSandboxID, containerConfig, sandboxConfig) gRPC CreateContainer CreateContainer containerID StartContainer(containerID) gRPC StartContainer StartContainer nil(成功) 记录日志symlink PodSyncResult

3.2 容器创建/启动逐行解析——startContainer方法

startContainer是容器启动的核心4步流程,位于kuberuntime_container.go

func (m *kubeGenericRuntimeManager) startContainer(
    podSandboxID string,          // Pod沙箱ID
    podSandboxConfig *runtimeapi.PodSandboxConfig,  // 沙箱配置
    spec *startSpec,              // 启动规格(普通/init/ephemeral容器)
    pod *v1.Pod,                  // Pod对象
    podStatus *kubecontainer.PodStatus, // Pod当前状态
    pullSecrets []v1.Secret,      // 镜像拉取凭证
    podIP string,                 // Pod IP(主IP)
    podIPs []string,              // Pod所有IP(双栈支持)
) (string, error) {

Step 1: 拉取镜像

imageRef, msg, err := m.imagePuller.EnsureImageExists(pod, container, pullSecrets, podSandboxConfig)
  • 调用imageManager.EnsureImageExists,根据ImagePullPolicy决定是否拉取
  • 拉取失败则记录事件并返回

Step 2: 创建容器

restartCount := 0
containerStatus := podStatus.FindContainerStatusByName(container.Name)
if containerStatus != nil {
    restartCount = containerStatus.RestartCount + 1  // 递增重启计数
}

target, err := spec.getTargetID(podStatus)  // ephemeral container的namespace target
containerConfig, cleanupAction, err := m.generateContainerConfig(...)  // 构建CRI配置

err = m.internalLifecycle.PreCreateContainer(pod, container, containerConfig)  // 内部生命周期钩子
containerID, err := m.runtimeService.CreateContainer(podSandboxID, containerConfig, podSandboxConfig)  // CRI调用
  • generateContainerConfig构建完整的CRI ContainerConfig,包含metadata、mounts、envs、security context等
  • PreCreateContainer是device plugin等内部钩子

Step 3: 启动容器

err = m.internalLifecycle.PreStartContainer(pod, container, containerID)  // 内部钩子
err = m.runtimeService.StartContainer(containerID)  // CRI调用启动
// 创建legacy日志symlink(向后兼容)

Step 4: PostStart Hook

if container.Lifecycle != nil && container.Lifecycle.PostStart != nil {
    msg, handlerErr := m.runner.Run(kubeContainerID, pod, container, container.Lifecycle.PostStart)
    if handlerErr != nil {
        // PostStart失败 → kill容器
        m.killContainer(pod, kubeContainerID, container.Name, "FailedPostStartHook", ...)
        return msg, fmt.Errorf("%s: %v", ErrPostStartHook, handlerErr)
    }
}

3.3 Pod创建CRI调用时序图

ImageManagerService(CRI) RuntimeService(CRI) kubeGenericRuntimeManager ImageManagerService(CRI) RuntimeService(CRI) kubeGenericRuntimeManager Phase 1: Sandbox创建 创建网络命名空间 执行CNI插件 设置resolv.conf 设置hostname Phase 2: 镜像准备 alt [镜像不存在或PullPolicy=Always] Phase 3: 容器创建与启动(循环每个容器) 创建容器rootfs 设置cgroup 应用安全策略 启动容器进程 设置namespace 执行entrypoint RunPodSandbox(config, runtimeHandler) podSandboxID PodSandboxStatus(podSandboxID) sandboxStatus(IP/Network信息) ImageStatus(imageSpec) imageStatus / nil PullImage(imageSpec, auth, sandboxConfig) imageRef CreateContainer(sandboxID, containerConfig, sandboxConfig) containerID StartContainer(containerID) nil(成功)

3.4 容器生命周期状态机

Running

Killing

CreateContainer(CRI)

容器启动失败

确认已退出

执行PostStart Hook

Hook成功

执行探测

探测通过

执行PreStop Hook

Hook完成

无Hook

StopContainer(CRI)

超过gracePeriod

优雅退出

GC回收

StartContainer(CRI)

正常退出/错误退出

运行时通信失败

重启(restartPolicy=Always/OnFailure)

恢复通信(仍运行)

Hook失败

Liveness/Startup失败

Created

Exited

Unknown

PostStartPending

PostStartRunning

PostStartDone

ProbeRunning

PreStopPending

PreStopRunning

GracefulStop

SIGTERM

SIGKILL

3.5 Sandbox管理

3.5.1 Sandbox管理流程

Sandbox清理

READY或有容器关联

NOT READY且无容器

evictSandboxes

获取所有containers和sandboxes

sandbox是否active?

标记active=true

可驱逐

Pod已删除/终止?

删除所有sandbox

保留最新1个,删除其余

Sandbox变更检测

podSandboxChanged

有sandbox?

changed=true, attempt=0

readySandboxCount>1?

changed=true, 需要reconcile

sandbox READY?

changed=true, attempt+1

网络命名空间变更?

changed=true, attempt+1

非HostNetwork但无IP?

changed=true, attempt+1

changed=false, 保留sandbox

Sandbox创建流程

非空

createPodSandbox

generatePodSandboxConfig

设置Metadata: Name/Namespace/UID/Attempt

设置DNS配置

设置Hostname: 非HostNetwork时

设置PortMappings

generatePodSandboxLinuxConfig

设置CgroupParent

设置SecurityContext: Privileged/Seccomp/SELinux/RunAsUser

设置Sysctls

设置NamespaceOptions: IPC/Network/PID

设置LogDirectory: /var/log/pods/NAMESPACE_NAME_UID

runtimeClassManager.LookupRuntimeHandler

runtimeHandler?

RunPodSandbox with handler

RunPodSandbox default

podSandboxID

3.5.2 generatePodSandboxConfig深度解析

generatePodSandboxConfig方法将v1.Pod转换为CRI的PodSandboxConfig

关键配置项:

  1. MetadataName/Namespace/Uid/Attempt(attempt用于跟踪沙箱重建次数)
  2. DNS:通过runtimeHelper.GetPodDNS获取,包含nameservers/searches/options
  3. Hostname:非HostNetwork时,通过GeneratePodHostNameAndDomain+GetNodenameForKernel生成
  4. PortMappings:遍历Pod所有容器的端口映射
  5. Linux配置
    • CgroupParent:从runtimeHelper.GetPodCgroupParent获取
    • SecurityContext.Privileged:只要Pod中有任一特权容器则为true
    • SecurityContext.Seccomp:默认RuntimeDefault
    • SecurityContext.NamespaceOptions:根据Pod的hostNetwork/hostIPC/hostPID决定
    • Sysctls:从pod.Spec.SecurityContext.Sysctls读取
    • RunAsUser/RunAsGroup/FSGroup/SupplementalGroups/SELinuxOptions

3.6 镜像拉取策略与逻辑

3.6.1 镜像拉取流程图

imageRef不为空

imageRef为空

PullNever

PullAlways

PullIfNotPresent

全部失败

成功

失败

EnsureImageExists

applyDefaultImageTag

镜像无tag/digest?

追加:latest tag

保持原样

GetImageRef

镜像已存在?

present=true

present=false

shouldPullImage判断

ImagePullPolicy?

present?

返回imageRef, 不拉取

返回ErrImageNeverPull

需要拉取

present?

BackOff中?

返回ErrImagePullBackOff

发起PullImage

puller.pullImage

序列化模式?

serialImagePuller: 队列顺序拉取

parallelImagePuller: 并行拉取

imageService.PullImage

有凭证?

PullImage without auth

keyring.Lookup(repoToPull)

尝试下一个credential

PullImage with auth

成功?

返回imageRef

聚合所有错误返回

PullImage结果

记录事件: PulledImage, 清除BackOff

记录事件: FailedToPullImage, 进入BackOff

Registry不可用?

返回ErrRegistryUnavailable

返回ErrImagePull

3.6.2 PullImage凭证链解析

kubeGenericRuntimeManager.PullImage实现了多凭证链尝试机制:

func (m *kubeGenericRuntimeManager) PullImage(image kubecontainer.ImageSpec, pullSecrets []v1.Secret, ...) (string, error) {
    // 1. 解析镜像名获取仓库名
    repoToPull, _, _, err := parsers.ParseImageName(img)
    
    // 2. 合并pullSecrets与默认keyring创建凭证环
    keyring, err := credentialprovidersecrets.MakeDockerKeyring(pullSecrets, m.keyring)
    
    // 3. 查找凭证
    creds, withCredentials := keyring.Lookup(repoToPull)
    
    if !withCredentials {
        // 无凭证:直接拉取
        imageRef, err := m.imageService.PullImage(imgSpec, nil, podSandboxConfig)
        return imageRef, err
    }
    
    // 4. 有凭证:遍历所有credential尝试
    var pullErrs []error
    for _, currentCreds := range creds {
        auth := &runtimeapi.AuthConfig{
            Username: currentCreds.Username,
            Password: currentCreds.Password,
            Auth:     currentCreds.Auth,
            ...
        }
        imageRef, err := m.imageService.PullImage(imgSpec, auth, podSandboxConfig)
        if err == nil {
            return imageRef, nil  // 任一凭证成功即返回
        }
        pullErrs = append(pullErrs, err)
    }
    // 5. 所有凭证失败:聚合错误
    return "", utilerrors.NewAggregate(pullErrs)
}
3.6.3 镜像拉取QPS控制

NewImageManager中,通过throttleImagePulling包装器实现QPS限流:

imageService = throttleImagePulling(imageService, qps, burst)

序列化/并行拉取策略:

  • serialImagePuller:通过channel队列+单goroutine顺序处理,最大队列深度maxImagePullRequests=10
  • parallelImagePuller:每个拉取请求起独立goroutine,无并发限制

3.7 容器日志管理

3.7.1 容器日志管理架构图

日志清理(GC路径)

removeContainer

logManager.Clean

ContainerStatus获取日志路径

Glob匹配所有日志文件含轮转文件

Remove所有匹配文件

Remove legacy symlink

日志轮转(ContainerLogManager)

Start - 启动周期检查

rotateLogs - 每10秒

ListContainers - 获取运行中容器

ContainerStatus - 获取日志路径

日志大小 > MaxSize?

跳过

rotateLog

cleanupUnusedLogs: 清理临时文件

removeExcessLogs: 删除超限旧日志

compressLog: gzip压缩旧日志

rotateLatestLog: 重命名当前日志+Reopen

日志读取路径

kubectl logs请求

Kubelet Server

GetContainerLogs

ReadLogs

日志格式检测

parseCRILog: CRI格式

parseDockerJSONLog: Docker JSON格式

logWriter

stdout Writer

stderr Writer

Follow模式?

读取到EOF结束

fsnotify监视文件变更

waitLogs等待新数据

容器仍在运行?

读取剩余数据后结束

继续读取

日志写入路径

容器进程stdout/stderr

CRI Runtime

日志文件

CRI日志格式: timestamp stream tag content

Legacy Symlink

/var/log/containers/pod_container_runtimeID.log

3.7.2 CRI日志格式详解

CRI定义了标准日志格式,每行结构为:

<timestamp> <stream> <tag> <log content>
  • timestamp:RFC3339NanoFixed格式,如2016-10-06T00:17:09.669794202Z
  • streamstdoutstderr
  • tag:日志标签,P表示partial line(未结束行),F表示full line(完整行)
  • log content:实际日志内容

parseCRILog解析逻辑:

  1. 以空格分割,第一段为timestamp
  2. 第二段为stream type(stdout/stderr)
  3. 第三段为tag,使用LogTagDelimiter分割
  4. 如果是partial line(tag[0]==P),去除尾部换行符
  5. 剩余部分为log content

同时兼容Docker JSON日志格式parseDockerJSONLog

{"log":"content","stream":"stdout","time":"2016-10-20T18:39:20.57606443Z"}
3.7.3 日志轮转机制

containerLogManager的轮转策略:

  • 触发条件:每10秒检查一次,当日志文件大小超过MaxSize时触发
  • 轮转步骤
    1. 清理上次轮转失败留下的临时文件(.tmp)
    2. 删除超出MaxFiles限制的旧日志
    3. 将未压缩的旧日志用gzip压缩(.gz后缀)
    4. 将当前日志重命名为<log>.<timestamp>
    5. 调用ReopenContainerLog让运行时重新打开日志文件
    6. 如果Reopen失败,将重命名的日志改回原名(避免日志丢失)

3.8 关键判断、分支、循环

3.8.1 computePodActions决策树

这是SyncPod最关键的决策逻辑,决定整个Pod的变更动作:

是: changed=true

否: sandbox未变更

是且restartPolicy=Never

是且restartPolicy!=Never

否: init仍在运行

不存在/非Running

Unknown状态

Running

容器spec变更

LivenessProbe失败

StartupProbe失败

正常

computePodActions

podSandboxChanged

sandbox变更?

restartPolicy!=Never AND attempt!=0 AND 有容器状态?

不应重启?

CreateSandbox=false, 直接返回

需要创建新sandbox

有Init容器?

NextInitContainerToStart=第一个init容器

过滤RestartPolicy=OnFailure中已成功的容器

还有容器要启动?

init容器是否全部完成?

CreateSandbox=false, 返回

ContainersToStart=待启动容器列表

检查Ephemeral容器

findNextInitContainerToRun

init完成?

init容器失败?

KillPod=true

NextInitContainerToStart=失败的init容器

等待,返回

遍历所有普通容器

容器状态检查

ShouldContainerBeRestarted?

加入ContainersToStart

加入ContainersToKill先杀再启

跳过

变更检查

加入ContainersToKill+ToStart

加入ContainersToKill+ToStart, reason=LivenessProbe

加入ContainersToKill+ToStart, reason=StartupProbe

keepCount++

keepCount==0 AND ToStart==0?

KillPod=true

3.8.2 容器终止与优雅关闭流程

StartupProbe

LivenessProbe

其他

killContainer

pod!=nil?

restoreSpecsFromContainerLabels

Note: 从容器label/annotation恢复pod和container规格

ContainerStatus获取labels

恢复: PodUID/Name/Namespace/TerminationGracePeriod

恢复: ContainerName/Ports/PreStop/Lifecycle

获取containerSpec

计算gracePeriod

DeletionGracePeriodSeconds?

gracePeriod = DeletionGracePeriodSeconds

TerminationGracePeriodSeconds?

gracePeriod = TerminationGracePeriodSeconds

ProbeTerminationGracePeriod特性?

Kill原因

用StartupProbe.TerminationGracePeriodSeconds覆盖

用LivenessProbe.TerminationGracePeriodSeconds覆盖

保持原gracePeriod

gracePeriod = minimumGracePeriodInSeconds=2

记录Killing事件

internalLifecycle.PreStopContainer

有PreStop Hook AND gracePeriod > 0?

executePreStopHook

gracePeriod -= hook耗时

跳过PreStop

gracePeriod < minimumGracePeriodInSeconds?

gracePeriod = minimumGracePeriodInSeconds

保持

gracePeriodOverride?

gracePeriod = gracePeriodOverride

保持

StopContainer - CRI调用

Note: CRI运行时先发送SIGTERM
gracePeriod后发送SIGKILL

3.8.3 killContainer中的gracePeriod计算优先级
优先级从高到低:
1. gracePeriodOverride(仅硬驱逐等路径使用)
2. pod.DeletionGracePeriodSeconds
3. pod.Spec.TerminationGracePeriodSeconds
   - 若ProbeTerminationGracePeriod特性启用:
     a. StartupProbe失败 → containerSpec.StartupProbe.TerminationGracePeriodSeconds
     b. LivenessProbe失败 → containerSpec.LivenessProbe.TerminationGracePeriodSeconds
4. 最小值minimumGracePeriodInSeconds = 2秒
3.8.4 PreStop Hook执行机制

executePreStopHook在独立goroutine中执行,受gracePeriod限制:

func (m *kubeGenericRuntimeManager) executePreStopHook(pod, containerID, containerSpec, gracePeriod) int64 {
    start := metav1.Now()
    done := make(chan struct{})
    go func() {
        defer close(done)
        msg, err := m.runner.Run(containerID, pod, containerSpec, containerSpec.Lifecycle.PreStop)
    }()
    
    select {
    case <-time.After(time.Duration(gracePeriod) * time.Second):
        // PreStop超时,继续执行StopContainer
    case <-done:
        // PreStop完成
    }
    return int64(metav1.Now().Sub(start.Time).Seconds())  // 返回实际耗时
}
3.8.5 Init容器状态判断逻辑

findNextInitContainerToRun从后向前遍历init容器:

  1. 如果任何主容器在Running状态 → 所有init已完成,返回done=true
  2. 从后向前查找第一个失败的init容器 → 返回该容器需要重启
  3. 无失败容器时,从后向前查找已完成的init容器 → 返回下一个待执行的init
  4. 找不到任何init容器状态 → 返回第一个init容器需要启动
3.8.6 容器重启策略处理流程

Always

OnFailure

Never

Unknown

其他

容器退出

restartPolicy?

始终重启

退出码==0?

不重启-成功退出

不重启

计算BackOff

BackOff中?

等待BackOff结束, 状态=CrashLoopBackOff

加入ContainersToStart, 重新创建容器

旧容器状态?

先Kill再Start

直接Start

restartPolicy != Never AND attempt != 0

所有容器都有状态?

Note: computePodActions中特殊逻辑

不创建sandbox,直接返回

Note: 防止已完成的Pod反复创建sandbox

restartPolicy == OnFailure

容器成功退出?

跳过该容器,不加入ToStart

加入ContainersToStart

3.9 dockershim适配层架构

dockershim网络流程

RunPodSandbox

创建pause容器

获取pause容器PID

获取netns路径 /proc/PID/ns/net

plugin.Setup - 执行CNI

设置HostPort

返回sandboxID

dockershim架构

Legacy兼容

docker_legacy_service.go

GetContainerLogs - Docker日志驱动

legacyLogProvider - journald等

DockerService接口

RuntimeServiceServer

ImageServiceServer

DockerService实现

DockerClient - libdocker

NetworkPlugin管理

Streaming Server

CheckpointManager

ContainerManager - cm

CNI插件

kubenet插件

HostPort管理

关键适配逻辑

docker_sandbox.go

RunPodSandbox: 创建pause容器+设置网络

StopPodSandbox: 停止pause容器

RemovePodSandbox: 删除pause容器

docker_container.go

CreateContainer: docker create

StartContainer: docker start

StopContainer: docker stop -t gracePeriod

docker_image.go

PullImage: docker pull

ListImages: docker images

RemoveImage: docker rmi

convert.go

CRI类型 ↔ Docker类型转换

dockershim关键设计:

  • 使用pause容器作为Pod Sandbox,pause容器只持有Linux命名空间
  • 通过Docker API创建/管理容器,将CRI语义映射到Docker操作
  • 网络插件通过netns路径调用CNI/kubenet
  • 容器标签使用io.kubernetes.docker.type区分sandbox和container
  • 支持legacy Docker日志驱动(journald等),通过legacyLogProvider接口

3.10 RuntimeClass动态选择流程

RuntimeClass Manager

nil或空

非空

Pod Spec

RuntimeClassName?

返回空handler, 使用默认运行时

runtimeClassManager.LookupRuntimeHandler

runtimeClassLister.Get

RuntimeClass存在?

返回NotFound错误

返回rc.Handler

createPodSandbox传入runtimeHandler

RunPodSandbox CRI调用

运行时根据handler选择runtime

Note: containerd: 多shim进程
CRI-O: 多runtime handler

informerFactory

SharedInformer

RuntimeClassLister缓存

RuntimeClass Manager实现:

  • 使用SharedInformerFactory监听RuntimeClass资源变更
  • resyncPeriod=0,不定期全量同步
  • LookupRuntimeHandler直接从缓存读取,无API调用开销
  • 默认RuntimeClass(nil/空名)映射到空handler字符串

3.11 容器GC策略

日志目录驱逐

沙箱驱逐策略

容器驱逐策略

容器GC三步流程

GarbageCollect

Step1: evictContainers

Step2: evictSandboxes

Step3: evictPodLogsDirectories

evictableContainers: 非Running且创建超过MinAge

allSourcesReady?

删除已删除/终止Pod的容器

保守策略

enforceMaxContainersPerEvictUnit

NumContainers > MaxContainers?

均分配额后删除最旧容器

保持

获取所有containers和sandboxes

标记active: READY或有容器关联

非active的sandbox可驱逐

Pod已删除/终止?

删除所有sandbox

保留最新1个

removeOldestNSandboxes

先StopPodSandbox再RemovePodSandbox

遍历/var/log/pods/下所有目录

Pod已删除?

RemoveAll整个日志目录

保留

清理dead container的legacy symlink

symlink目标不存在?

ContainerStatus检查

容器已EXITED?

删除symlink

可能是日志轮转间隙,保留

3.12 instrumentedServices度量封装

instrumentedRuntimeServiceinstrumentedImageManagerService是装饰器模式,为所有CRI调用添加度量指标:

记录的指标:

  • metrics.RuntimeOperations:操作计数(按operation label)
  • metrics.RuntimeOperationsDuration:操作耗时直方图
  • metrics.RuntimeOperationsErrors:错误计数
  • metrics.RunPodSandboxDuration:沙箱创建耗时(按runtimeHandler label)
  • metrics.RunPodSandboxErrors:沙箱创建错误计数

每个方法的标准包装模式:

func (in instrumentedRuntimeService) XXX(...) (...) {
    const operation = "xxx"
    defer recordOperation(operation, time.Now())   // 记录操作次数和耗时
    
    out, err := in.service.XXX(...)                // 调用底层服务
    recordError(operation, err)                     // 错误时记录错误指标
    return out, err
}

3.13 remoteRuntimeService gRPC客户端

remoteRuntimeService是CRI的gRPC客户端实现,连接远程容器运行时:

关键设计:

  • 使用grpc.DialContext连接运行时endpoint(Unix socket或TCP)
  • maxMsgSize=16*1024*1024(16MB),设置gRPC最大消息大小
  • 默认超时connectionTimeout,RunPodSandbox使用2倍超时
  • 每次调用创建带超时的context:getContextWithTimeout(r.timeout)

连接建立:

func NewRemoteRuntimeService(endpoint string, connectionTimeout time.Duration) (internalapi.RuntimeService, error) {
    addr, dialer, err := util.GetAddressAndDialer(endpoint)  // 解析endpoint
    conn, err := grpc.DialContext(ctx, addr, 
        grpc.WithInsecure(), 
        grpc.WithContextDialer(dialer),
        grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)))
    return &remoteRuntimeService{
        timeout:       connectionTimeout,
        runtimeClient: runtimeapi.NewRuntimeServiceClient(conn),
    }, nil
}

3.14 安全上下文处理

3.14.1 determineEffectiveSecurityContext

安全上下文合并优先级:

  1. container.SecurityContext字段优先
  2. 回退到pod.Spec.SecurityContext
  3. 最终从镜像获取默认用户(uid/username)
  4. 默认以root运行(uid=new(int64), 值为0)

设置的安全属性:

  • RunAsUser/RunAsGroup/RunAsUsername
  • Privileged
  • Capabilities(Add/Drop)
  • SELinuxOptions
  • SeccompProfilePath/Seccomp(SecurityProfile)
  • ApparmorProfile
  • NamespaceOptions(IPC/Network/PID mode + TARGET ID)
  • SupplementalGroups/FSGroup
  • NoNewPrivs
  • ReadonlyRootfs
  • MaskedPaths/ReadonlyPaths(ProcMount类型)
3.14.2 Seccomp优先级链
1. container.SecurityContext.SeccompProfile  ← 最高优先
2. container annotation: container.seccomp.security.alpha.kubernetes.io/<container_name>  ← 已废弃
3. pod.Spec.SecurityContext.SeccompProfile
4. pod annotation: seccomp.security.alpha.kubernetes.io/pod  ← 已废弃
5. 默认: Unconfined(容器级别)/ RuntimeDefault(沙箱级别)

3.15 标签与注解系统

3.15.1 容器标签(Labels)

用于标识和过滤容器,存储在CRI容器的Labels中:

Label Key 含义
io.kubernetes.pod.name Pod名称
io.kubernetes.pod.namespace Pod命名空间
io.kubernetes.pod.uid Pod UID
io.kubernetes.container.name 容器名称
3.15.2 容器注解(Annotations)

用于存储恢复容器所需的信息,存储在CRI容器的Annotations中:

Annotation Key 含义
io.kubernetes.container.hash 容器spec的hash值,用于变更检测
io.kubernetes.container.restartCount 重启次数
io.kubernetes.container.terminationMessagePath 终止消息路径
io.kubernetes.container.terminationMessagePolicy 终止消息策略
io.kubernetes.container.preStopHandler PreStop Hook(JSON序列化)
io.kubernetes.container.ports 容器端口(JSON序列化)
io.kubernetes.pod.deletionGracePeriod Pod删除优雅期
io.kubernetes.pod.terminationGracePeriod Pod终止优雅期

设计意图: 当kubelet重启时,可能无法获取Pod spec(Pod已被删除),此时通过labels/annotations恢复必要信息来执行容器终止操作。

3.16 Termination Message处理

容器终止时,终止消息的获取逻辑:

  1. 从文件读取:如果容器设置了terminationMessagePath,从挂载的文件读取(最大4KB)
  2. 从日志读取:如果terminationMessagePolicy=FallbackToLogsOnError且退出码非0:
    • 优先使用legacyLogProvider(Docker journald等)
    • 否则使用readLastStringFromContainerLogs(读取CRI日志最后80行,最大2KB)
  3. 消息合并:将终止消息追加到ContainerStatus.Message中

3.17 Ephemeral容器处理

Ephemeral容器是临时调试容器,特殊逻辑:

  • 在Step 5中先于init容器启动
  • 不受init容器状态影响(即使init失败也能启动)
  • 永远不会重启(FindContainerStatusByName为nil时才启动)
  • 支持namespace targeting:TargetContainerName指定目标容器的PID命名空间
  • 通过getTargetID解析目标容器ID,设置NamespaceOptions.Pid=TARGET

四、总结

4.1 架构特点

  1. 接口分层清晰kubecontainer.RuntimekubeGenericRuntimeManagerinstrumentedRuntimeServiceremoteRuntimeService→gRPC→运行时,每层职责单一
  2. 装饰器模式:instrumented services为CRI调用透明添加度量
  3. 标签恢复机制:通过容器labels/annotations存储关键信息,解决kubelet重启后的Pod spec丢失问题
  4. 多凭证链:镜像拉取支持多凭证自动尝试
  5. BackOff机制:容器重启和镜像拉取都有退避策略,避免频繁重试
  6. 优雅关闭:PreStop Hook→SIGTERM→gracePeriod→SIGKILL,支持Probe级别的gracePeriod覆盖

4.2 关键路径

  • Pod创建SyncPodcomputePodActionscreatePodSandboxstartContainer(×N)
  • Pod更新SyncPodcomputePodActionskillContainer(变更的)→startContainer(新的)
  • Pod删除KillPodkillContainersWithSyncResult(并发)→StopPodSandbox
  • 镜像拉取EnsureImageExistsshouldPullImagePullImage(带凭证链)
  • 日志读取ReadLogsfsnotifyparseCRILog/parseDockerJSONLoglogWriter
  • GC回收GarbageCollectevictContainersevictSandboxesevictPodLogsDirectories
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐