【kubernetes v1.21】(kubelet 2)容器运行时与CRI
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 类结构与接口定义
2.2 CRI接口层次图
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 内部调用关系
2.5 数据流入流出方式
数据流入:
SyncPod接收:v1.Pod、kubecontainer.PodStatus、[]v1.Secret(pullSecrets)、flowcontrol.Backoff- CRI gRPC响应:
runtimeapi.VersionResponse、runtimeapi.ContainerStatus、runtimeapi.PodSandboxStatus等 - 探测结果:
proberesults.Manager提供liveness/readiness/startup探测结果
数据流出:
SyncPod输出:kubecontainer.PodSyncResult- CRI gRPC请求:
runtimeapi.RunPodSandboxRequest、runtimeapi.CreateContainerRequest等 - 事件记录:
recorder.Event输出到Kubernetes事件系统 - 日志输出:通过
io.Writer输出到kubectl log请求
三、核心业务逻辑深度解析
3.1 CRI完整调用链路
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构建完整的CRIContainerConfig,包含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调用时序图
3.4 容器生命周期状态机
3.5 Sandbox管理
3.5.1 Sandbox管理流程
3.5.2 generatePodSandboxConfig深度解析
generatePodSandboxConfig方法将v1.Pod转换为CRI的PodSandboxConfig:
关键配置项:
- Metadata:
Name/Namespace/Uid/Attempt(attempt用于跟踪沙箱重建次数) - DNS:通过
runtimeHelper.GetPodDNS获取,包含nameservers/searches/options - Hostname:非HostNetwork时,通过
GeneratePodHostNameAndDomain+GetNodenameForKernel生成 - PortMappings:遍历Pod所有容器的端口映射
- Linux配置:
CgroupParent:从runtimeHelper.GetPodCgroupParent获取SecurityContext.Privileged:只要Pod中有任一特权容器则为trueSecurityContext.Seccomp:默认RuntimeDefaultSecurityContext.NamespaceOptions:根据Pod的hostNetwork/hostIPC/hostPID决定Sysctls:从pod.Spec.SecurityContext.Sysctls读取RunAsUser/RunAsGroup/FSGroup/SupplementalGroups/SELinuxOptions
3.6 镜像拉取策略与逻辑
3.6.1 镜像拉取流程图
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 容器日志管理架构图
3.7.2 CRI日志格式详解
CRI定义了标准日志格式,每行结构为:
<timestamp> <stream> <tag> <log content>
- timestamp:RFC3339NanoFixed格式,如
2016-10-06T00:17:09.669794202Z - stream:
stdout或stderr - tag:日志标签,
P表示partial line(未结束行),F表示full line(完整行) - log content:实际日志内容
parseCRILog解析逻辑:
- 以空格分割,第一段为timestamp
- 第二段为stream type(stdout/stderr)
- 第三段为tag,使用
LogTagDelimiter分割 - 如果是partial line(tag[0]==P),去除尾部换行符
- 剩余部分为log content
同时兼容Docker JSON日志格式parseDockerJSONLog:
{"log":"content","stream":"stdout","time":"2016-10-20T18:39:20.57606443Z"}
3.7.3 日志轮转机制
containerLogManager的轮转策略:
- 触发条件:每10秒检查一次,当日志文件大小超过
MaxSize时触发 - 轮转步骤:
- 清理上次轮转失败留下的临时文件(
.tmp) - 删除超出
MaxFiles限制的旧日志 - 将未压缩的旧日志用gzip压缩(
.gz后缀) - 将当前日志重命名为
<log>.<timestamp> - 调用
ReopenContainerLog让运行时重新打开日志文件 - 如果Reopen失败,将重命名的日志改回原名(避免日志丢失)
- 清理上次轮转失败留下的临时文件(
3.8 关键判断、分支、循环
3.8.1 computePodActions决策树
这是SyncPod最关键的决策逻辑,决定整个Pod的变更动作:
3.8.2 容器终止与优雅关闭流程
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容器:
- 如果任何主容器在Running状态 → 所有init已完成,返回done=true
- 从后向前查找第一个失败的init容器 → 返回该容器需要重启
- 无失败容器时,从后向前查找已完成的init容器 → 返回下一个待执行的init
- 找不到任何init容器状态 → 返回第一个init容器需要启动
3.8.6 容器重启策略处理流程
3.9 dockershim适配层架构
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实现:
- 使用
SharedInformerFactory监听RuntimeClass资源变更 resyncPeriod=0,不定期全量同步LookupRuntimeHandler直接从缓存读取,无API调用开销- 默认RuntimeClass(nil/空名)映射到空handler字符串
3.11 容器GC策略
3.12 instrumentedServices度量封装
instrumentedRuntimeService和instrumentedImageManagerService是装饰器模式,为所有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
安全上下文合并优先级:
container.SecurityContext字段优先- 回退到
pod.Spec.SecurityContext - 最终从镜像获取默认用户(uid/username)
- 默认以root运行(
uid=new(int64), 值为0)
设置的安全属性:
RunAsUser/RunAsGroup/RunAsUsernamePrivilegedCapabilities(Add/Drop)SELinuxOptionsSeccompProfilePath/Seccomp(SecurityProfile)ApparmorProfileNamespaceOptions(IPC/Network/PID mode + TARGET ID)SupplementalGroups/FSGroupNoNewPrivsReadonlyRootfsMaskedPaths/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处理
容器终止时,终止消息的获取逻辑:
- 从文件读取:如果容器设置了
terminationMessagePath,从挂载的文件读取(最大4KB) - 从日志读取:如果
terminationMessagePolicy=FallbackToLogsOnError且退出码非0:- 优先使用
legacyLogProvider(Docker journald等) - 否则使用
readLastStringFromContainerLogs(读取CRI日志最后80行,最大2KB)
- 优先使用
- 消息合并:将终止消息追加到ContainerStatus.Message中
3.17 Ephemeral容器处理
Ephemeral容器是临时调试容器,特殊逻辑:
- 在Step 5中先于init容器启动
- 不受init容器状态影响(即使init失败也能启动)
- 永远不会重启(
FindContainerStatusByName为nil时才启动) - 支持namespace targeting:
TargetContainerName指定目标容器的PID命名空间 - 通过
getTargetID解析目标容器ID,设置NamespaceOptions.Pid=TARGET
四、总结
4.1 架构特点
- 接口分层清晰:
kubecontainer.Runtime→kubeGenericRuntimeManager→instrumentedRuntimeService→remoteRuntimeService→gRPC→运行时,每层职责单一 - 装饰器模式:instrumented services为CRI调用透明添加度量
- 标签恢复机制:通过容器labels/annotations存储关键信息,解决kubelet重启后的Pod spec丢失问题
- 多凭证链:镜像拉取支持多凭证自动尝试
- BackOff机制:容器重启和镜像拉取都有退避策略,避免频繁重试
- 优雅关闭:PreStop Hook→SIGTERM→gracePeriod→SIGKILL,支持Probe级别的gracePeriod覆盖
4.2 关键路径
- Pod创建:
SyncPod→computePodActions→createPodSandbox→startContainer(×N) - Pod更新:
SyncPod→computePodActions→killContainer(变更的)→startContainer(新的) - Pod删除:
KillPod→killContainersWithSyncResult(并发)→StopPodSandbox - 镜像拉取:
EnsureImageExists→shouldPullImage→PullImage(带凭证链) - 日志读取:
ReadLogs→fsnotify→parseCRILog/parseDockerJSONLog→logWriter - GC回收:
GarbageCollect→evictContainers→evictSandboxes→evictPodLogsDirectories
更多推荐


所有评论(0)