Kubernetes 基础体系 · 第 11/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubelet 与容器运行时:CRI、Pod Sync、PLEG、Probe 和资源状态
Kubelet 是每个节点上的节点代理。它从 API Server 获取分配给本节点的 Pod,对这些 Pod 进行本地编排,并通过容器运行时接口(Container Runtime Interface,CRI)请求容器运行时创建、启动、停止和查询容器。
Kubelet 并不直接负责实现容器隔离。真正创建 Linux namespace、cgroup、网络接口和容器进程的是容器运行时及其底层组件。Kubelet 的核心工作可以概括为:
这个过程不是一次性脚本,而是一个持续运行的控制循环。CRI、Pod Sync、PLEG、Probe 和资源状态分别处在这个循环的不同位置。
一、先建立组件关系
典型的数据流如下:
flowchart LR
API[API Server] -->|PodSpec、NodeSpec| K[Kubelet]
K --> P[Pod Sync / Pod Workers]
P -->|CRI gRPC| R[Container Runtime]
R --> S[Sandbox]
R --> C[Containers]
R -->|容器状态、事件、统计| PLEG[PLEG]
PLEG --> P
P --> Probe[Probe Manager]
Probe -->|HTTP/TCP/Exec/gRPC| C
Probe --> P
K -->|PodStatus、NodeStatus| API
K --> Evict[Eviction Manager]
Evict -->|资源压力、驱逐决定| P
Stats[CRI / cAdvisor / cgroup / 文件系统] --> Evict
关键路径是:
- API Server 中存在一个绑定到节点的 Pod。
- Kubelet 收到 Pod 变化,或者定期重新处理该 Pod。
- Pod Sync 根据 PodSpec 和当前状态计算需要执行的动作。
- Kubelet 通过 CRI 调用容器运行时。
- PLEG 观察运行时状态变化,帮助 Kubelet 发现容器退出、创建或删除。
- Probe Manager 对运行中的容器执行健康检查。
- Kubelet 将 PodStatus 和 NodeStatus 汇报回 API Server。
- 资源监控和驱逐逻辑可能改变 Pod 的生命周期。
这几个组件并非严格串行。Probe、PLEG、状态汇报和资源监控都有自己的事件或周期,因此需要把 Kubelet 理解成多个相互协调的异步控制循环,而不是单个函数顺序执行。
二、CRI:Kubelet 与容器运行时之间的稳定边界
2.1 CRI 解决什么问题
CRI 是 Kubelet 与容器运行时之间的 gRPC 接口。它把 Kubernetes 节点管理逻辑与具体运行时解耦:
- Kubelet 不需要知道运行时如何实现镜像存储;
- Kubelet 不需要直接调用
runc或其他 OCI runtime; - 运行时可以使用不同的容器管理实现;
- Kubelet 通过统一接口查询 Pod、Sandbox、Container 和资源统计。
当前 Kubernetes 使用 CRI v1 作为正式接口。节点上的运行时通常通过 Unix Domain Socket 提供服务,例如:
unix:///run/containerd/containerd.sock
unix:///var/run/crio/crio.sock
具体 socket 路径取决于发行版和运行时,不应在脚本中硬编码为某一个路径。
CRI 有两类主要服务:
- RuntimeService:管理 Pod Sandbox 和容器;
- ImageService:拉取、删除、查询镜像。
CRI 并不是 Kubernetes API。它使用自己的对象和状态模型;例如,CRI 中常见的对象是:
PodSandboxContainerPodSandboxStatusContainerStatusImage
2.2 为什么 Pod 需要 Sandbox
Kubernetes 的 Pod 是调度和管理单位。一个 Pod 中的多个容器通常共享:
- 网络命名空间;
- Pod IP;
- IPC 命名空间;
- 可选的 PID 命名空间;
- 通过
emptyDir等机制提供的共享卷。
因此,运行时通常先为 Pod 创建一个 Sandbox,再把 Pod 内的普通容器加入这个 Sandbox。很多 Linux 容器运行时会创建一个保持网络命名空间存活的基础容器,常被称为 pause 或 infra container,但这个名称和实现不是 Kubernetes API 的规范保证。
典型顺序如下:
RunPodSandbox
↓
CreateContainer(init-1)
StartContainer(init-1)
↓
CreateContainer(init-2)
StartContainer(init-2)
↓
CreateContainer(app)
StartContainer(app)
↓
配置 Probe、同步状态
如果 Sandbox 失效,Kubelet 通常不能只重新启动普通容器,因为普通容器依赖原有的 namespace。常见处理是:
停止或移除旧 Sandbox
↓
创建新 Sandbox
↓
重新创建 Pod 内容器
这也是为什么“容器进程还在”不一定意味着 Pod 网络仍然正常:Sandbox 或其网络配置可能已经异常。
2.3 Pod 生命周期中的 CRI 调用
一个简化的 Pod 创建流程可以表示为:
sequenceDiagram
participant A as API Server
participant K as Kubelet
participant R as Runtime
participant N as CNI
participant P as Pod进程
A->>K: PodSpec
K->>R: RunPodSandbox(config)
R->>N: 配置 Pod 网络
N-->>R: Pod IP / 网络结果
R-->>K: sandboxID
K->>R: PullImage(必要时)
K->>R: CreateContainer(init)
K->>R: StartContainer(init)
R-->>K: init 完成
K->>R: CreateContainer(app)
K->>R: StartContainer(app)
R-->>P: 启动进程
K->>R: ContainerStatus / PodSandboxStatus
K->>A: PodStatus
这里的 PullImage 不一定每次都真正下载镜像。运行时会根据镜像引用、镜像缓存和 imagePullPolicy 决定是否需要拉取。
CRI 调用失败时,错误必须按阶段区分:
| 阶段 | 典型失败 | 可能表现 |
|---|---|---|
RunPodSandbox |
CNI、网络、cgroup、运行时故障 | Pod 长期 Pending 或 Sandbox 创建失败 |
PullImage |
镜像不存在、认证失败、网络失败 | ErrImagePull、ImagePullBackOff |
CreateContainer |
volume、权限、namespace 或参数错误 | 容器创建失败 |
StartContainer |
入口程序、挂载、运行时启动失败 | CrashLoopBackOff 或快速退出 |
StopContainer |
进程不响应、运行时卡死 | Pod 删除延迟、节点清理不完整 |
CrashLoopBackOff 不是 CRI 返回的独立容器状态,而是 Kubelet 针对反复启动失败实施退避后,在 Pod 事件和容器状态中呈现的结果。
2.4 使用 crictl 观察 CRI
crictl 直接访问 CRI,适合绕过 Kubernetes API 检查节点本地状态。前提是已经安装它,并且配置了正确的运行时 endpoint。
sudo crictl info
sudo crictl pods
sudo crictl ps -a
sudo crictl images
可能看到:
CONTAINER IMAGE CREATED STATE
a1b2c3d4 nginx:1.27 2 minutes ago Running
e5f6g7h8 busybox:1.36 2 minutes ago Exited
查询某个容器:
sudo crictl inspect a1b2c3d4
查询 Sandbox:
sudo crictl pods --name my-pod
sudo crictl inspectp <sandbox-id>
这些输出反映的是 CRI 运行时所见的状态,不等价于 API Server 中的 PodStatus。例如,运行时可能已经报告容器退出,但 Kubelet 尚未完成状态同步,因此短时间内 crictl 与 kubectl get pod 可能不一致。
生产环境中直接执行 crictl rm、crictl stopp 或手工删除运行时目录有较高风险:Kubelet 可能同时在进行 Pod Sync,造成状态竞争、残留挂载或错误事件。诊断时优先使用只读命令;确需强制清理,应先确认 Kubelet 和运行时的并发行为,并准备节点级恢复方案。
三、Pod Sync:Kubelet 如何把 PodSpec 变成本地动作
3.1 Pod Sync 不是一次同步,而是持续调谐
Kubelet 维护每个本地 Pod 的期望状态和实际状态。可以形式化为:
其中:
- 是一个 Pod;
- 是 API Server 提供的期望状态,例如镜像、命令、挂载、Probe 和资源限制;
- 是运行时和 Kubelet 当前观察到的状态;
- 是事件、Probe 结果、删除请求和节点资源压力等外部输入;
- 是需要执行的动作集合,例如创建、启动、停止、重建或更新状态。
理想情况下,调谐会使:
但这个关系不是简单的“字段完全相等”。容器一旦创建,某些字段不能原地修改,例如:
- 容器镜像;
- 启动命令;
- 大部分安全上下文;
- 端口和挂载等容器创建参数。
所以当 PodSpec 发生不可变字段变化时,Kubelet 的实际动作不是更新已有容器,而是停止旧容器并创建新容器。Deployment 的滚动更新正是利用了新的 Pod,而不是原地修改现有容器。
3.2 Kubelet 如何知道需要重新 Sync
常见触发源包括:
- Pod Informer 收到新增、更新或删除事件;
- PLEG 报告容器或 Sandbox 状态变化;
- Probe 状态发生变化;
- 定时同步;
- 节点资源压力触发驱逐;
- Kubelet 启动后加载本地状态;
- Pod 删除或终止流程继续推进。
事件通常先进入队列,再由 Pod worker 处理。实现细节会随 Kubelet 版本变化,但有两个稳定的工程事实:
- 事件处理是异步的;
- 同一个 Pod 的操作需要避免并发地互相破坏。
因此,从 API Server 修改 PodSpec,到容器实际改变,再到 PodStatus 更新,通常存在延迟。不能把 API 写入成功理解成节点动作已经完成。
3.3 Pod Sync 的重要状态分层
一个 Pod 至少有以下几层状态:
- API 期望状态:PodSpec;
- Kubelet 内部状态:同步阶段、容器重启策略、终止进度;
- CRI 状态:Sandbox 和容器是否存在、是否运行、退出码和原因;
- Probe 状态:就绪、存活、启动探针的最近结果;
- API 可见状态:PodStatus、ContainerStatus 和 Conditions。
这些状态可能短暂不一致。例如:
容器进程已退出
↓
运行时已更新 ContainerStatus
↓
PLEG 检测到退出事件
↓
Pod worker 执行重启策略
↓
Kubelet 更新 PodStatus
如果在第二步和第五步之间执行查询,crictl 与 kubectl 可能显示不同结果。这不一定表示数据损坏,而是异步状态传播的正常表现。
3.4 Pod Sync 与 Pod 状态的几个边界
restartPolicy 只控制容器重启策略
Pod 的 restartPolicy 目前包括:
AlwaysOnFailureNever
它作用于 Pod 内普通容器的退出处理,不等价于“整个 Pod 永远恢复”。
例如,restartPolicy: Never 的容器退出后,Kubelet 不会在同一个 Pod 内重新启动它;但控制器可能根据 Job、Deployment 等更高层对象创建新的 Pod。
Pod phase 不是完整状态机
Pod phase 是较粗粒度的摘要状态:
PendingRunningSucceededFailedUnknown
它不应该被当作容器级状态的替代品。一个 Pod 处于 Running,并不保证所有应用容器都 Ready;一个 Pod 处于 Pending,也可能是镜像未拉取、Sandbox 未创建或调度之外的多种原因。
删除 Pod 是终止流程
删除 Pod 后,API 对象通常先进入 terminating 状态。Kubelet 会:
- 执行容器终止逻辑;
- 处理
preStop; - 向容器发送终止信号;
- 等待终止宽限期;
- 必要时发送强制终止;
- 清理容器、Sandbox、挂载和本地状态。
preStop 不是 Probe,也不是 CRI 的独立健康检查。它是容器终止流程的一部分,执行失败不会自动阻止删除流程。
四、PLEG:Kubelet 如何发现运行时发生了什么
4.1 PLEG 的职责
PLEG 是 Pod Lifecycle Event Generator,即 Pod 生命周期事件生成器。它的作用是观察容器运行时状态,并生成供 Kubelet 处理的生命周期事件,例如:
- 容器创建;
- 容器启动;
- 容器退出;
- 容器删除;
- Sandbox 状态变化。
PLEG 的关键价值在于:Kubelet 不能只依赖 API Server 事件,因为容器运行时的状态变化并不会先写回 API Server。容器可能因为进程自身崩溃、节点内存不足、运行时异常或外部信号退出,PLEG 帮助 Kubelet 发现这些本地变化。
4.2 传统 relist 模型
传统 PLEG 的基本思路是周期性查询运行时:
读取运行时中所有 PodSandbox 和 Container
↓
与上一次缓存比较
↓
发现新增、消失或状态变化
↓
生成 PodLifecycleEvent
↓
唤醒对应 Pod 的同步逻辑
设第 次查询得到运行时状态集合 ,上次缓存为 ,则:
这里的差异不仅是对象增删,也包括对象状态字段变化。PLEG 将差异转换成生命周期事件。
这种方式的优点是简单、对运行时事件可靠性要求较低;缺点是需要周期性列举对象。当节点上 Pod 和容器数量很多、CRI 响应慢或运行时被拖垮时,relist 可能变慢。
4.3 PLEG 不是重启器,也不是 Probe
常见误解包括:
- PLEG 发现容器退出,所以 PLEG 负责重启容器;
- PLEG 检查 HTTP,所以 PLEG 等于 liveness probe;
- PLEG 卡住,所以所有容器都已经停止。
更准确的因果关系是:
运行时状态变化
↓
PLEG 发现并生成事件
↓
Pod Sync 重新计算期望动作
↓
Kubelet 根据 restartPolicy 和 Pod 状态决定是否重启
PLEG 只负责观察和通知。真正执行重启、删除旧 Sandbox、创建容器的是 Pod Sync 和 CRI 调用。
Probe 检查的是应用定义的健康条件;PLEG 检查的是容器和 Sandbox 的生命周期状态。一个进程可能仍然运行但 HTTP 服务已经失效,这时 Probe 可以失败,而 PLEG 仍会认为容器处于 Running。
4.4 PLEG 延迟的故障表现
当 PLEG 或 CRI 查询长期变慢,常见表现包括:
- 容器已经退出,但
kubectl中状态长时间不更新; - Pod 删除卡住;
- Kubelet 日志出现 PLEG relist 或 runtime 操作耗时过长;
- 节点被标记为不健康,最终可能影响
Ready; - 大量 Pod 同时发生状态延迟。
诊断时应同时检查:
kubectl describe node <node-name>
kubectl get events --field-selector involvedObject.kind=Pod --sort-by=.lastTimestamp
sudo journalctl -u kubelet --since "30 min ago"
sudo journalctl -u containerd --since "30 min ago" # 使用 containerd 时
sudo crictl ps -a
sudo crictl pods
如果 crictl 查询本身就卡住或极慢,问题重点通常在运行时、磁盘、文件系统、cgroup 或节点负载,而不是 API Server。
不同 Kubernetes 版本可能提供不同的 PLEG 实现或实验性事件机制。不要把某个版本的内部指标名、默认 relist 周期或特性门控当成跨版本 API 保证;诊断时应以当前版本的 Kubelet 日志、指标和源码配置为准。
五、Probe:Kubelet 如何判断应用是否可用
Probe 是 Kubelet 对容器执行的健康检查。它不是由 kubelet 进程直接读取应用内部状态,而是通过指定的探测方式得到一个结果。
常用探测方式包括:
- HTTP GET;
- TCP Socket;
- Exec;
- gRPC;
tcpSocket、httpGet和exec是长期常用的 API 方式;- gRPC Probe 需要注意 Kubernetes 版本和运行时环境的支持情况。
Probe 结果通常可以理解为:
Success 检查成功
Failure 检查明确失败
Unknown 无法得到可靠结果
具体状态处理和重试由 Kubelet 按探针参数执行,不能简单把一次网络超时直接等同于容器立刻重启。
5.1 三种 Probe 的职责不同
Startup Probe:启动阶段保护
startupProbe 用于判断应用是否已经完成启动。只要启动探针尚未成功:
- liveness probe 不会开始决定容器是否不健康;
- readiness probe 也不会把容器判定为可接收流量。
这适合启动时间不稳定的应用,例如需要加载大型模型、执行数据库迁移或恢复本地缓存的服务。
Liveness Probe:是否应重启
liveness probe 失败达到阈值后,Kubelet 会认为容器不健康,并根据 Pod 的重启策略处理容器。它的典型作用是发现:
- 进程仍在,但死锁;
- 内部工作线程永久停止;
- 服务进入无法自行恢复的状态。
liveness 失败并不表示 Pod 会被删除或重新调度。通常是当前节点上的容器被重启;如果节点本身失效,才可能由更高层控制器在其他节点创建替代 Pod。
Readiness Probe:是否接收流量
readiness probe 失败会使容器不 Ready。对于由 Service 选择的 Pod,EndpointSlice 控制器通常会据此移除或标记该端点,使流量不再发送到该容器。
readiness 失败一般不会重启容器。一个应用可以:
进程仍正常运行
↓
readiness 失败
↓
停止接收 Service 流量
这正是排空、过载保护和依赖不可用时的常见行为。
5.2 一个可运行的 Probe 示例
下面的 Pod 使用一个 HTTP 服务作为示例:
apiVersion: v1
kind: Pod
metadata:
name: probe-demo
labels:
app: probe-demo
spec:
containers:
- name: app
image: registry.k8s.io/e2e-test-images/agnhost:2.53
args:
- netexec
- --http-port=8080
ports:
- name: http
containerPort: 8080
startupProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 5
failureThreshold: 12
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
创建并观察:
kubectl apply -f probe-demo.yaml
kubectl get pod probe-demo -w
kubectl describe pod probe-demo
describe 中可能看到:
Readiness probe failed: HTTP probe failed with statuscode: 500
如果 readiness 失败,Pod 可能仍显示 Running,但 Ready 条件为 False。如果 liveness 连续失败达到 failureThreshold,则可能出现容器重启和事件:
Container probe-demo failed liveness probe, will be restarted
这个示例成立的前提是镜像实际提供 netexec 和 /healthz 端点;镜像版本或测试镜像行为发生变化时,应先检查镜像说明。生产环境不要直接复制测试镜像和端点,应让应用提供语义稳定的健康检查接口。
5.3 Probe 时间参数的计算
对一个启动探针,允许的最长探测窗口可粗略估算为:
其中:
- 是首次探测前的
initialDelaySeconds; - 是允许失败次数
failureThreshold; - 是
periodSeconds。
但这是近似值,不应当当作精确调度保证。探测执行时间、调度延迟、超时和边界时刻都会影响实际结果。
例如:
startupProbe:
periodSeconds: 5
failureThreshold: 12
直觉上提供约 60 秒的失败容忍窗口。若应用实际需要 90 秒,Kubelet 可能在应用完成启动前将其判定为启动失败,并进一步触发容器重启。
一个常见反例是把相同的快速 liveness probe 用于慢启动应用:
应用启动需要 2 分钟
liveness 每 5 秒检查一次
failureThreshold = 3
应用可能在 15 秒左右就被重启,永远到不了可服务状态。这是启动探针存在的主要原因之一。
5.4 Exec Probe 的边界
Exec Probe 在容器的执行环境中运行命令。它不是在宿主机 shell 中运行,因此:
- 命令必须存在于容器镜像中;
- 路径和环境变量以容器环境为准;
- 探测命令本身会消耗容器的 CPU、PID 和文件系统资源;
- 对高频 Probe 使用复杂脚本可能制造额外负载。
Probe 成功只表示探测命令返回成功,不代表所有业务依赖都健康。反过来,Probe 也可能因为探测接口依赖了一个非关键下游服务而过于敏感,导致应用频繁从 Service 中摘除或反复重启。
5.5 Probe 与容器重启的准确因果
以 liveness 失败为例:
探测失败
↓
失败计数达到 failureThreshold
↓
Kubelet 将容器视为不健康
↓
按 Pod 的重启策略停止并重启容器
↓
容器 restartCount 增加
如果容器原本已经退出,Kubelet 可能根据退出码和 restartPolicy 重启它;这与 liveness 失败触发的重启是两条不同路径。诊断时应查看:
kubectl get pod probe-demo -o jsonpath='{.status.containerStatuses[0].lastState}'
kubectl get pod probe-demo -o jsonpath='{.status.containerStatuses[0].restartCount}'
kubectl describe pod probe-demo
六、资源状态:Capacity、Allocatable、Conditions 与驱逐
Kubelet 不只负责容器生命周期,还要把节点资源和健康状态报告给 Kubernetes,并在资源不足时执行本地驱逐逻辑。
6.1 Capacity 与 Allocatable
Node 的资源状态至少要区分:
- Capacity:节点理论上或检测到的总资源;
- Allocatable:可以分配给普通 Pod 的资源上限。
对某资源 ,可以粗略表示为:
实际实现还可能受到 kubelet 配置、操作系统、设备插件、拓扑管理和特定资源模型影响,因此不能把公式视为所有资源的精确计算规范。
查看节点资源:
kubectl get node <node-name> -o jsonpath='{.status.capacity}{"\n"}{.status.allocatable}{"\n"}'
kubectl describe node <node-name>
典型输出可能类似:
Capacity:
cpu: 8
memory: 32Gi
pods: 110
Allocatable:
cpu: 7800m
memory: 30Gi
pods: 110
7800m 表示 7.8 个 CPU。Allocatable 小于 Capacity 的原因是节点需要为操作系统、Kubelet、容器运行时和资源驱逐保留空间。
调度器主要依据 Node 的可分配资源和 Pod 的资源请求做调度。调度成功不保证应用一定不会发生运行时资源争用,因为:
- 资源请求是调度和计量依据,不是进程绝对上限;
- CPU request 通常影响配额和权重,CPU limit 影响 cgroup 上限;
- memory limit 超出后可能发生 OOM;
- 本地磁盘、PID、网络和设备资源也可能成为瓶颈。
6.2 Pod 的 requests、limits 与 QoS
对一个容器:
requests表示调度和资源保证计算中使用的请求量;limits表示运行时资源上限或控制参数;- 未设置 limit 不代表资源无限,也不代表一定获得节点全部剩余资源。
Pod QoS 类别通常为:
GuaranteedBurstableBestEffort
例如,所有容器的 CPU 和内存 request、limit 都设置且相等时,Pod 才可能属于 Guaranteed;完全没有 CPU 和内存请求/限制时,才可能属于 BestEffort;其余多数情况属于 Burstable。
QoS 主要影响资源压力下的处理优先级和 cgroup 管理,不是“Guaranteed 永不被杀”的保证。节点出现严重内核 OOM、硬件故障或管理员强制操作时,任何 Pod 都可能受到影响。
6.3 Node Conditions
常见 Node Condition 包括:
Ready:节点是否被认为可正常运行 Pod;MemoryPressure:内存压力;DiskPressure:磁盘或 inode 压力;PIDPressure:进程 ID 资源压力;NetworkUnavailable:网络不可用状态,部分场景由网络插件或控制器参与设置。
查看条件:
kubectl get node <node-name> \
-o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{" message="}{.message}{"\n"}{end}'
条件为 True 时,不能只看条件名称,还要结合 reason 和 message。例如 DiskPressure=True 可能对应:
- 容器镜像层占满;
/var/lib/kubelet所在文件系统空间不足;- inode 耗尽;
- ephemeral-storage 使用过高;
- 日志文件增长过快。
6.4 资源统计与驱逐
Kubelet 的资源判断会使用来自 cgroup、文件系统、运行时和系统监控的数据。不同版本和运行时组合下,统计来源可能不同;不要假设所有指标都由同一个组件提供。
当资源压力达到驱逐阈值时,简化过程是:
采集资源统计
↓
计算内存、磁盘、inode、PID 等压力
↓
设置 Node Condition
↓
选择需要驱逐的 Pod
↓
终止 Pod 并更新状态
驱逐不是 Probe 失败。它是节点级资源保护机制。
MemoryPressure
内存压力可能导致:
- Kubelet 报告
MemoryPressure=True; - 调度行为受到影响;
- Kubelet 驱逐 Pod;
- 容器被 Linux OOM Killer 杀死。
容器被 OOM Killer 杀死时,Pod 中可能看到:
Reason: OOMKilled
Exit Code: 137
但退出码 137 只表示常见的 SIGKILL 结果,不能单凭它证明一定是某一种 OOM 场景。还应检查内核日志、cgroup 事件和节点监控。
DiskPressure
磁盘压力涉及两种不同维度:
- 字节空间不足;
- inode 数量不足。
即使 df -h 仍有空间,df -i 显示 inode 已耗尽,也可能触发 DiskPressure。
常用检查:
df -h
df -i
sudo du -xhd1 /var/lib/kubelet 2>/dev/null
sudo du -xhd1 /var/lib/containerd 2>/dev/null
不要直接删除 /var/lib/kubelet 或运行时目录来“释放磁盘”。其中可能包含正在运行 Pod 的挂载、检查点和元数据。应先识别镜像、容器日志、Pod ephemeral-storage 和孤儿挂载的来源,再通过运行时、日志系统或 Pod 生命周期进行清理。
PIDPressure
每个节点可创建的进程数受 PID 上限影响。PIDPressure 可能来自:
- 容器内进程泄漏;
- 高频短命进程;
- 过多 Pod 或 sidecar;
- 宿主机服务异常创建进程。
检查节点 PID:
ps -eLf | wc -l
cat /proc/sys/kernel/pid_max
kubectl describe node <node-name>
容器的 pids cgroup 限制和节点整体 PID 资源是两个层次。某个容器的 PID limit 触发,不一定意味着 Node 已经 PIDPressure=True;反过来也成立。
七、把 Pod Sync、PLEG、Probe 和资源压力串成故障路径
7.1 应用进程崩溃
应用进程退出
↓
运行时记录容器 Exited
↓
PLEG 发现退出事件
↓
Pod Sync 根据 restartPolicy 判断
↓
重新 StartContainer 或 CreateContainer
↓
更新 restartCount 和 lastState
如果容器镜像、命令和 Sandbox 仍可复用,Kubelet 可能重新启动容器;如果 Sandbox 或容器配置不可恢复,则可能重建更多对象。
7.2 应用仍在运行但失去服务能力
进程仍存在
↓
readinessProbe 失败
↓
Pod Ready=False
↓
EndpointSlice 不再把端点作为可用后端
此时 PLEG 可能没有任何异常事件,因为从运行时角度看容器仍然 Running。若 livenessProbe 后续也失败,才会进一步触发容器重启。
7.3 运行时不可用
CRI 调用超时或失败
↓
Pod Sync 无法获得可靠状态
↓
PLEG 查询延迟或失败
↓
Pod 状态更新滞后
↓
Node Ready 可能受影响
这种情况下,kubectl 中的 Pod 状态不一定能实时反映节点真实情况,因为状态汇报本身也可能依赖 Kubelet 和运行时恢复。
7.4 节点磁盘耗尽
容器日志或镜像持续增长
↓
文件系统空间或 inode 下降
↓
DiskPressure=True
↓
新 Pod 调度、镜像拉取或容器创建失败
↓
Kubelet 根据驱逐策略终止部分 Pod
磁盘压力既可能造成“新容器创建失败”,也可能导致“已有容器被驱逐”。两者不能只通过 Pod phase 区分,应查看 Node 事件、Kubelet 日志和文件系统状态。
八、从 API、Kubelet、CRI 三层进行排障
排障时要先确认问题发生在哪一层。
8.1 第一层:API 视角
kubectl get pod <pod> -o wide
kubectl describe pod <pod>
kubectl get pod <pod> -o json
kubectl get events --sort-by=.lastTimestamp
重点看:
- Pod 是否已经绑定到目标节点;
- Pod phase 和 conditions;
containerStatuses[*].state;lastState、reason、exitCode;restartCount;- 事件中的 Failed、BackOff、Unhealthy、Killing;
- Pod IP 和容器状态是否一致。
例如:
kubectl get pod <pod> \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{" state="}{.state}{" last="}{.lastState}{" restarts="}{.restartCount}{"\n"}{end}'
这能快速判断是容器尚未创建、正在运行、反复退出,还是刚刚被 Probe 或资源压力影响。
8.2 第二层:节点与 Kubelet 视角
kubectl describe node <node-name>
sudo systemctl status kubelet
sudo journalctl -u kubelet --since "15 min ago"
重点看:
- Node Conditions;
- 磁盘、内存和 PID 压力;
- PLEG 或 runtime 操作超时;
- Probe 失败;
- 驱逐事件;
- CNI、volume、mount 和 cgroup 错误;
- Kubelet 是否无法连接 API Server。
如果 Node Ready=False,不要直接推断是容器运行时故障。还需要检查:
- Kubelet 是否运行;
- Kubelet 证书和 API 连接;
- 容器运行时;
- CNI;
- 节点时间;
- 磁盘和内存;
- 是否存在网络隔离。
8.3 第三层:CRI 与系统视角
sudo crictl info
sudo crictl pods
sudo crictl ps -a
sudo crictl stats
sudo journalctl -u containerd --since "15 min ago"
sudo dmesg -T | egrep -i 'oom|killed process|cgroup|overlay|filesystem'
如果 Kubernetes API 显示容器不存在,但 crictl ps -a 显示它存在,可能是状态传播延迟;如果两者长期不一致,则应重点检查:
- Kubelet 是否卡住;
- PLEG 是否持续 relist;
- runtime endpoint 是否错误;
- 运行时数据库是否异常;
- 容器是否由非 Kubernetes 工具创建;
- 节点上是否存在残留 Sandbox。
crictl stats 是否能提供完整 CPU、内存和磁盘指标,取决于运行时、CRI 版本和节点配置。缺少某些字段不一定表示资源为零。
九、常见误解与反例
误解一:Pod 是一个容器
Pod 是一组共享部分 namespace、网络和生命周期语义的容器。运行时通常先创建 Sandbox,再创建 init container 和应用容器。把 Pod 当成一个容器,会错误理解网络、重启、日志和资源统计。
误解二:容器 Running 就表示应用可用
Running 通常只表示容器主进程仍在运行。应用可能已经无法处理请求,此时应由 readiness probe 反映流量接收能力。
误解三:readiness 失败会重启容器
readiness 的主要作用是控制可用端点,不负责重启。重启通常由容器退出、liveness 失败或其他 Kubelet 生命周期逻辑触发。
误解四:PLEG 发现问题后立即修复
PLEG 只报告运行时变化。修复动作由 Pod Sync 根据 PodSpec、restartPolicy、Sandbox 状态和其他条件决定。
误解五:Node Ready 就代表所有 Pod 正常
Node Ready 是节点级条件,不是每个 Pod 的业务健康汇总。节点可以 Ready,同时某个 Pod 的 readiness 为 False、容器反复重启或服务完全不可用。
误解六:资源 limit 是节点级资源保证
limit 主要通过 cgroup 等机制约束容器;它不保证节点永远有对应资源,也不保证应用一定获得稳定性能。节点级资源不足时,Kubelet、内核 OOM 和运行时都可能介入。
十、规范保证、实现细节和生产取舍
需要区分三类事实。
Kubernetes API 和生命周期语义
以下属于应优先依赖的 API 语义:
- Pod 是调度和管理单位;
- Probe 的 startup、readiness、liveness 职责不同;
- PodStatus、NodeStatus 和 Conditions 通过 API 暴露;
restartPolicy定义容器退出后的基本处理策略;- CRI 是 Kubelet 与运行时的接口边界;
- requests、limits 和 QoS 参与调度与资源管理。
常见实现
以下是常见但不应视为所有版本、所有运行时都完全相同的实现:
- 使用 pause/infra 容器承载 Pod Sandbox;
- PLEG 通过周期性 relist 发现运行时变化;
- Kubelet 使用 cAdvisor、CRI 和 cgroup 获取不同资源统计;
- Pod worker 按 Pod 串行化部分同步动作;
- Probe 由 Kubelet 执行,而不是由 Service 或 API Server 执行。
生产环境取舍
生产系统中最危险的做法通常不是 Probe 参数偏小,而是把不同层次的状态混为一谈:
- 用 Pod phase 代替应用健康状态;
- 用容器 Running 代替流量可用;
- 用 API Server 状态代替运行时实时状态;
- 用
df -h代替完整磁盘和 inode 检查; - 用手工删除运行时目录代替生命周期清理;
- 用 liveness probe 检查所有下游依赖;
- 用资源 limit 数值代替节点容量和实际工作集分析。
更可靠的排障顺序是:
确认 Pod 是否已调度
↓
确认 Pod 是否有 Sandbox
↓
确认容器是否创建、启动或退出
↓
区分进程失败、Probe 失败和资源驱逐
↓
比较 kubectl、crictl、Kubelet 日志和 Node Conditions
↓
最后判断是 API、Kubelet、CRI、CNI、存储还是内核层问题
Kubelet 的核心不是“启动几个容器”,而是持续地把声明式 PodSpec 转换为节点上的实际进程、namespace、cgroup、网络和挂载,并把这些异步变化重新汇总为 API 可见状态。CRI 提供执行边界,Pod Sync 负责调谐,PLEG 负责发现生命周期变化,Probe 负责应用层健康判断,资源状态负责节点级保护。只有把这五者放在同一条状态传播链路上,才能正确解释 Pod 为什么没有启动、为什么反复重启、为什么不接收流量,以及为什么节点会进入压力状态。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 调度器:Queue、Filter、Score、Bind、插件和扩展
- 下一篇:Kubernetes CRI、CNI 与 CSI:运行时、网络、存储插件责任边界
- 延伸:Kubernetes Pod 完整模型:共享 Namespace、容器、Sandbox 和生命周期
- 延伸:Kubernetes 节点排障:Kubelet、Runtime、DiskPressure、PID 和网络
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论