Kubernetes 基础体系 · 第 12/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes CRI、CNI 与 CSI:运行时、网络、存储插件责任边界
Kubernetes 并不直接实现“启动容器、配置 Pod 网络、挂载云盘”这三类具体能力,而是分别通过三套接口与外部组件协作:
- CRI(Container Runtime Interface):Kubelet 与容器运行时之间的接口,负责 Pod Sandbox、容器、镜像、日志、执行命令和运行时状态。
- CNI(Container Network Interface):容器运行时为网络命名空间配置网络的插件接口,负责网卡、IP、路由和网络资源清理。
- CSI(Container Storage Interface):Kubernetes 存储控制器与存储插件之间的接口,负责卷的创建、附加、格式化、挂载、发布、扩容和快照等能力。
三者经常被并列称为 Kubernetes 的“插件接口”,但它们解决的问题不同:
这个关系不是严格的串行调用链。CRI 是 Kubelet 与运行时的直接边界;CNI 通常由容器运行时在创建 Pod Sandbox 时调用;CSI 则主要由 Kubernetes 控制器和 Kubelet 的存储管理路径调用。三者共同影响一个 Pod,但调用方、状态来源和故障表现都不同。
一、先建立对象模型:Pod、Sandbox、容器、网络命名空间和卷
1. Pod 不是一个容器
Kubernetes 的 Pod 是一组共享部分运行环境的容器。至少有三个概念需要区分:
- Pod 对象:存储在 API Server 中的声明,例如镜像、命令、卷和资源请求。
- Pod Sandbox:容器运行时为 Pod 建立的运行环境,通常对应一个网络命名空间和一个 pause/infra 容器。
- 业务容器:Pod 中真正运行应用的容器。
典型结构如下:
Pod
├── Pod Sandbox
│ ├── Network Namespace
│ ├── Pod IP
│ └── pause/infra container
├── app container
└── sidecar container
业务容器通常加入 Sandbox 已经创建好的网络命名空间,因此同一个 Pod 内的容器共享 Pod IP 和端口空间。两个容器不能分别监听同一个端口,即使它们是不同的容器;这是共享网络命名空间的直接结果。
2. Pod Sandbox 为什么存在
如果每个业务容器都独立创建网络命名空间,Pod 内多个容器就无法自然地共享同一个 IP。Sandbox 把 Pod 级别的网络生命周期从业务容器生命周期中分离出来:
- 创建 Sandbox;
- 创建 Sandbox 的网络命名空间;
- 为该网络命名空间配置 Pod IP 和路由;
- 创建业务容器并让它们加入该命名空间;
- 业务容器退出时,Sandbox 仍可暂时存在;
- Sandbox 被删除时,才清理整个 Pod 级网络资源。
这也是为什么“容器启动失败”和“Pod 网络创建失败”可能是两个独立问题。
二、CRI:Kubelet 与容器运行时的责任边界
1. CRI 是什么
CRI 是 Kubelet 调用容器运行时的 gRPC 接口。它主要分为两个服务:
- RuntimeService:管理 Pod Sandbox、容器、执行命令、日志、状态和统计信息;
- ImageService:拉取、删除、列出镜像并查询镜像信息。
逻辑关系可以表示为:
Kubelet
│
│ CRI gRPC
▼
Container Runtime
├── containerd
├── CRI-O
└── 其他实现 CRI 的运行时
Kubelet 不应该依赖某个具体运行时的内部目录或私有命令。它通过 CRI 请求运行时完成操作,例如:
RunPodSandbox
CreateContainer
StartContainer
StopContainer
RemoveContainer
PullImage
ExecSync
ContainerStatus
PodSandboxStatus
CRI 定义的是 Kubernetes 需要的抽象,而不是运行时内部必须如何实现。containerd、CRI-O 可以使用不同的内部组件、存储布局和监控方式,只要对 Kubelet 提供兼容的 CRI 行为即可。
2. CRI 的典型启动流程
一个 Pod 从 API Server 中出现到容器真正运行,简化后经过以下步骤:
sequenceDiagram
participant A as API Server
participant K as Kubelet
participant R as CRI Runtime
participant C as CNI Plugin
participant I as Image Registry
A->>K: PodSpec
K->>R: RunPodSandbox
R->>C: ADD(network namespace, config)
C-->>R: Pod IP, routes, interfaces
R-->>K: Sandbox ID
K->>R: PullImage
R->>I: Pull image
I-->>R: Image layers
R-->>K: Image available
K->>R: CreateContainer
K->>R: StartContainer
R-->>K: Container running
实际实现可能有更多检查、重试和事件,但因果顺序通常是:
- Kubelet 根据 PodSpec 创建 Sandbox;
- 运行时建立 Pod 的网络命名空间;
- 运行时配置 Pod 网络;
- Kubelet 确认 Sandbox 可用;
- 拉取镜像;
- 创建并启动业务容器;
- Kubelet 持续检查状态、探针和资源信息。
3. CRI 与 CNI 的连接点
在典型实现中,Kubelet 调用:
RunPodSandbox()
容器运行时随后调用 CNI 插件,为 Sandbox 的网络命名空间配置网络。
因此,严格来说:
Kubelet ──CRI──> Runtime ──CNI──> Network Plugin
而不是:
Kubelet ──直接──> CNI
不同运行时的内部调用路径可能有差异,但 Kubernetes 依赖的边界是 CRI;Kubelet 不需要知道 CNI 二进制如何执行。
4. CRI 的生命周期与幂等性
Kubelet 是声明式控制循环,而不是只执行一次的安装脚本。它会反复观察实际状态,并尝试使实际状态接近期望状态。
例如,Kubelet 发现 Pod 需要一个容器:
期望状态:容器 running
实际状态:容器不存在
它可能执行:
PullImage
CreateContainer
StartContainer
如果在 StartContainer 后 Kubelet 进程重启,恢复后不会简单地再创建一个重复容器,而是通过 Sandbox 和 ContainerStatus 等接口重新发现已有对象,并根据状态继续协调。
这要求运行时接口具备可重复调用的语义。实际是否严格幂等,取决于 CRI 实现和具体调用;Kubelet 也会通过状态查询、ID 和错误处理避免无条件重复创建。
5. PLEG、Probe 和资源状态属于哪一层
PLEG
PLEG,即 Pod Lifecycle Event Generator,是 Kubelet 内部用于感知容器运行时状态变化的机制。它不是 CRI,也不是独立插件。
粗略的数据流是:
Runtime 状态
↓
Kubelet 通过 CRI 查询或接收事件
↓
PLEG 生成 Pod 生命周期事件
↓
Pod Sync Loop 重新协调 Pod
如果 PLEG 长时间没有更新,常见表现是:
NodeNotReady
Container runtime is down
PLEG is not healthy
这通常指向运行时响应慢、运行时卡死、节点负载过高或 Kubelet 与运行时通信异常,而不是直接证明 CNI 或 CSI 有问题。
Probe
Liveness、Readiness 和 Startup Probe 是 Kubelet 对容器执行的健康检查:
- Startup Probe:应用是否完成启动;
- Readiness Probe:是否应接收 Service 流量;
- Liveness Probe:是否需要重启容器。
Probe 失败通常由 Kubelet 触发容器重启或更新就绪状态,不是 CRI 运行时决定探针语义。Kubelet 可能通过 CRI 的 ExecSync 执行 exec probe,也可能由 Kubelet 自己发起 HTTP/TCP 检查。
例如,应用因为数据库连接失败而 Readiness Probe 失败:
容器仍然 Running
Pod 可能是 Running
Pod 不一定 Ready
Service EndpointSlice 通常不会把它作为就绪后端
这与容器进程崩溃导致的 CrashLoopBackOff 不同。前者是健康状态问题,后者是进程和重启策略问题。
资源状态
Kubelet 从容器运行时和节点系统收集 CPU、内存、文件系统等信息,并将部分状态用于:
- Pod 和容器状态;
- 驱逐决策;
- 节点条件;
- Metrics 相关组件的数据来源。
CRI 提供运行时可见的容器统计接口,但完整资源观测还涉及 cgroup、节点文件系统、内核和 kubelet 自身。不能把所有资源指标都归因于 CRI。
三、CRI 不负责什么
明确边界比记住接口名称更重要。
1. CRI 不负责调度
Scheduler 决定 Pod 被放到哪个节点。CRI 只在 Pod 已经被调度到某个节点后参与运行。
2. CRI 不负责 Service 和集群路由
Service、EndpointSlice、kube-proxy 或 eBPF 数据面负责服务发现和服务转发。CRI 只负责运行时层面的容器和 Sandbox 生命周期。
3. CRI 不定义网络插件算法
运行时可以调用 CNI,但 CRI 不规定:
- Pod IP 从哪个地址池分配;
- 是否使用 Overlay;
- 是否使用 Underlay;
- 是否由 eBPF 转发;
- NetworkPolicy 如何执行。
这些属于 CNI 插件及其数据面实现。
4. CRI 不负责持久卷生命周期
容器可以看到一个挂载后的目录,但“云盘如何创建、卷如何附加、设备如何格式化”属于 CSI 和存储系统。运行时只负责按照 Kubelet 传入的容器配置启动进程,并不理解 PVC 的业务语义。
四、CNI:为网络命名空间配置 Pod 网络
1. CNI 的基本模型
CNI 是一组面向容器网络配置的规范。运行时创建或删除网络命名空间时,调用 CNI 插件完成网络操作。
抽象调用如下:
ADD:
输入:网络命名空间、容器 ID、网络配置、运行时上下文
输出:接口、IP、路由、DNS 等网络结果
DEL:
输入:同一个容器和网络上下文
输出:删除网络资源
CHECK:
输入:已有网络状态
输出:检查结果
具体插件通常包括:
- 主网络插件:创建 veth、虚拟接口、路由或其他数据面;
- IPAM 插件:分配和回收 IP;
- 其他 meta 插件:链路、带宽、端口映射或多网络编排。
CNI 的核心目标是“配置网络”,不是管理 Kubernetes API 中的 Pod 状态。
2. 一个 Pod 网络是如何建立的
以最常见的 veth 模型为例:
Node Network Namespace
┌────────────────────────────────────────┐
│ host-side veth ── bridge / dataplane │
└──────────────┬─────────────────────────┘
│ veth pair
┌──────────────▼─────────────────────────┐
│ Pod Network Namespace │
│ eth0: Pod IP │
│ default route │
└────────────────────────────────────────┘
创建过程可以拆成以下步骤:
- 运行时创建 Pod Network Namespace;
- CNI 创建一对 veth;
- 一端放入 Pod 命名空间并命名为
eth0; - 另一端留在节点网络命名空间;
- IPAM 分配 Pod IP;
- CNI 为
eth0配置 IP; - CNI 配置默认路由和必要的邻居、规则;
- 插件将网络结果返回给运行时;
- 运行时把该 Sandbox 作为业务容器的网络环境。
eth0 只是常见结果,不是所有 CNI 实现必须采用的内部方式。
3. Pod IP、节点 IP 和 Service IP 不是同一个概念
假设:
Node IP: 192.168.10.20
Pod IP: 10.244.2.17
Service IP: 10.96.0.10
它们的职责不同:
- Node IP:节点对外通信和节点级组件使用的地址;
- Pod IP:Pod 网络命名空间中的地址,通常由 CNI 分配;
- Service IP:虚拟服务地址,由 Service 数据面转发到后端 Pod。
当应用监听 0.0.0.0:8080 时,它通常监听的是 Pod 网络命名空间中的所有地址,而不是直接监听节点地址。访问 Node IP 是否能到达 Pod,还取决于 Service 类型、端口映射、路由和数据面规则。
4. Overlay 与 Underlay 的区别
Overlay
Overlay 使用额外的封装网络承载 Pod 流量。例如节点之间传输的是封装后的包:
原始包:Pod A IP → Pod B IP
封装后:Node A IP → Node B IP
[外层头部][内层 Pod 包]
优点是可以在底层网络不理解 Pod 网段时构建独立的 Pod 网络。代价包括:
- 封装带来额外头部;
- MTU 需要扣除封装开销;
- 故障诊断同时涉及内层和外层;
- 节点 CPU、网络设备和流量路径可能受到影响。
Underlay
Underlay 让 Pod 地址直接使用底层网络可路由的地址,或让底层网络显式学习 Pod 网段。
优点是路径更直接、封装更少。代价是:
- 需要底层网络支持 Pod 地址;
- IP 地址规划、路由发布和安全边界更复杂;
- 云厂商 VPC、子网和安全组规则可能成为限制。
Overlay 和 Underlay 不是“谁一定更好”的关系,而是网络可达性、地址规划、MTU、运维能力和云平台限制之间的取舍。
5. IPAM 只是 CNI 的一个组成部分
CNI 主插件负责把网络接入 Pod,但 IP 地址从哪里来通常由 IPAM 插件管理。
典型过程为:
主插件请求 IPAM
↓
IPAM 从地址池分配 10.244.2.17
↓
主插件配置接口和路由
↓
删除 Pod 时 IPAM 回收 10.244.2.17
如果 IPAM 状态丢失或回收失败,可能出现:
- Pod 创建失败并提示地址池耗尽;
- Pod 删除后地址没有回收;
- 节点重启后地址状态与实际接口不一致;
- 同一地址被错误复用。
因此,“Pod 网络不通”不一定是路由问题,也可能是 IPAM 分配、回收或状态文件问题。
6. NetworkPolicy 不等于 CNI 的基础联网
NetworkPolicy API 描述允许和拒绝规则,但具体执行必须由支持 NetworkPolicy 的网络实现完成。仅仅安装一个能分配 Pod IP 的 CNI,不代表 NetworkPolicy 一定生效。
例如:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: only-from-frontend
namespace: app
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: frontend
这份对象由 API Server 接收,控制器或网络数据面读取它,但是否真正阻断流量取决于网络插件是否实现相应策略。对象存在而流量未被阻断,并不能证明 Kubernetes API 失效,可能是 CNI 数据面不支持该能力或配置不完整。
五、CNI 的失败路径与诊断方法
1. Sandbox 创建失败
如果 CNI ADD 失败,常见状态是:
Pod phase: Pending
事件:FailedCreatePodSandBox
这时业务容器通常还没有启动。典型原因包括:
- CNI 配置文件缺失或格式错误;
- CNI 二进制不存在或无执行权限;
- IPAM 地址池耗尽;
- 节点网络插件 DaemonSet 未运行;
- 内核模块、路由或 eBPF 前置条件不满足;
- Pod 网卡创建成功但路由配置失败;
- Overlay MTU 或封装参数错误。
应先检查 Pod 事件和节点上的运行时日志:
kubectl describe pod <pod> -n <namespace>
kubectl get pods -n kube-system -o wide
crictl pods
crictl ps -a
kubectl describe pod 中如果持续出现 FailedCreatePodSandBox,优先检查 CRI 到 CNI 的调用链,而不是先看应用日志,因为应用容器可能尚未存在。
2. 网络命名空间存在但业务不通
如果容器已经运行,问题可能发生在 CNI 之后:
- Service 规则未生成;
- NetworkPolicy 阻断;
- Pod 到节点有路由,但跨节点路由缺失;
- 云安全组或底层 ACL 阻断;
- DNS 配置错误;
- MTU 导致大包或特定协议失败;
- 应用只监听
127.0.0.1; - 端口没有被应用监听。
基础检查示例:
kubectl exec -n <namespace> <pod> -- ip addr
kubectl exec -n <namespace> <pod> -- ip route
kubectl exec -n <namespace> <pod> -- cat /etc/resolv.conf
kubectl get pod -n <namespace> -o wide
镜像没有 ip 或 cat 时,命令本身失败并不意味着网络故障。应使用临时调试容器、节点网络命名空间检查或网络插件提供的诊断工具。
3. CNI DEL 失败
删除 Pod 时,运行时应调用 CNI DEL 清理接口、IP 和路由。如果 DEL 失败,可能产生:
- 残留 veth;
- IP 地址泄漏;
- 网络命名空间或插件状态残留;
- 节点后续创建 Pod 时地址或接口冲突。
强制删除 Pod 可能让 API 对象迅速消失,但不保证底层网络资源已经被正确清理。生产环境中应先确认节点和插件状态,再决定是否手工清理,避免同时存在多个控制路径修改网络状态。
六、CSI:从 PVC 声明到卷挂载
1. CSI 解决什么问题
CSI 将存储系统的操作抽象成标准接口。Kubernetes 不需要为每种云盘、SAN、分布式文件系统实现一套内置逻辑,而是通过 CSI 驱动调用外部存储系统。
CSI 驱动通常由两类组件组成:
CSI Controller Plugin
├── CreateVolume
├── DeleteVolume
├── ControllerPublishVolume
├── ControllerUnpublishVolume
├── ValidateVolumeCapabilities
└── CreateSnapshot / DeleteSnapshot 等
CSI Node Plugin
├── NodeStageVolume
├── NodeUnstageVolume
├── NodePublishVolume
├── NodeUnpublishVolume
└── NodeGetInfo / NodeGetVolumeStats 等
Kubernetes 还会部署一组 sidecar 容器,例如:
- external-provisioner;
- external-attacher;
- external-resizer;
- external-snapshotter;
- livenessprobe。
sidecar 负责观察 Kubernetes API 对象并调用 CSI 驱动。sidecar 不是 CSI 规范本身,但它们是 Kubernetes 中常见的集成实现。
2. 动态供给的完整路径
下面以动态创建块存储卷为例。
用户提交:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
namespace: demo
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast
resources:
requests:
storage: 10Gi
再提交使用该 PVC 的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: app
namespace: demo
spec:
containers:
- name: app
image: nginx:stable
volumeMounts:
- name: data
mountPath: /var/lib/data
volumes:
- name: data
persistentVolumeClaim:
claimName: data
过程不是“创建 PVC 就立刻挂载磁盘”,而是多个阶段:
sequenceDiagram
participant U as User
participant A as API Server
participant P as external-provisioner
participant D as CSI Controller
participant S as Storage Backend
participant K as Kubelet
participant N as CSI Node Plugin
U->>A: Create PVC
A-->>P: PVC watch event
P->>D: CreateVolume
D->>S: Create volume
S-->>D: Volume ID
D-->>P: Volume created
P->>A: Create PV / bind PVC
U->>A: Create Pod
A-->>K: Pod assigned to node
K->>A: Observe PVC/PV
K->>D: ControllerPublishVolume
D->>S: Attach volume to node
S-->>D: Device available
D-->>K: Volume attached
K->>N: NodeStageVolume
N->>N: Format and mount to staging path
K->>N: NodePublishVolume
N->>N: Bind mount into Pod path
K->>K: Start container through CRI
其中最后一步很重要:CSI 挂载完成后,Kubelet 才能让容器运行时创建一个包含该挂载的容器。
3. Controller 和 Node 的区别
Controller 侧
Controller 侧面向存储系统和节点级附加操作,通常负责:
- 创建或删除卷;
- 将卷附加到节点;
- 从节点解除附加;
- 扩展控制器侧卷容量;
- 创建和删除快照。
它不应该直接进入每个 Pod 的网络命名空间或容器文件系统。
Node 侧
Node 插件运行在具体节点上,负责:
- 识别块设备或远端文件系统;
- 格式化卷;
- 将卷挂载到节点 staging 目录;
- 将 staging 目录发布到 Pod 目录;
- 在 Pod 删除时解除发布和卸载。
常见的两阶段挂载关系为:
设备或远端卷
↓ NodeStageVolume
节点 staging path
↓ NodePublishVolume
Pod volume path
↓
容器内 /var/lib/data
NodeStageVolume 常用于在节点上完成一次设备级挂载,多个 Pod 可能再通过 NodePublishVolume 使用该 staging 路径。是否能被多个 Pod 使用,仍受访问模式和驱动能力约束。
4. Attach、Mount 和 Publish 不是同一个动作
以块存储为例:
- Attach:云平台把云盘连接到某个节点,节点可能出现
/dev/...设备; - Stage/Mount:CSI Node 插件识别设备、格式化并挂载到节点目录;
- Publish:把节点上的卷路径绑定挂载到某个 Pod 的目录;
- Container start:CRI 创建容器时使用这个已准备好的挂载点。
如果 Pod 卡在:
FailedAttachVolume
问题通常在控制器、云平台附加权限、卷状态或节点绑定关系。
如果出现:
FailedMount
问题更可能在 CSI Node 插件、设备识别、文件系统、挂载参数或节点内核。
如果挂载成功但应用无法写入,则还要检查:
- 文件系统只读;
- UID/GID 和权限;
fsGroup处理;- SELinux/AppArmor;
- 容器内路径是否正确;
- 应用是否使用了错误的子路径。
5. 访问模式不是存储能力的实现
PVC 中的访问模式表达工作负载要求,例如:
ReadWriteOnce:卷可被一个节点以读写方式使用;ReadOnlyMany:多个节点可只读使用;ReadWriteMany:多个节点可读写使用;ReadWriteOncePod:限制为单个 Pod 使用,具体支持依赖版本和驱动能力。
访问模式不是把块设备自动变成共享文件系统。一个普通云块盘即使 Kubernetes 对象写了 ReadWriteMany,如果底层设备和 CSI 驱动不支持多节点并发读写,仍然不能安全使用。
反例:
节点 A:把同一个普通块盘格式化并挂载为 ext4
节点 B:也把同一块盘挂载为 ext4 并读写
这通常会造成文件系统损坏。Kubernetes 不会把不具备共享语义的块设备变成安全的分布式文件系统。
七、CSI 快照、扩容和故障恢复
1. 快照不是 PV 的简单复制
Kubernetes 的 VolumeSnapshot 使用快照相关 API 对象表达快照请求。实际快照由 CSI 驱动和存储后端完成。
基本关系是:
VolumeSnapshot
↓
VolumeSnapshotContent
↓
存储系统中的 snapshot ID
从快照恢复 PVC 时,控制器向 CSI 驱动请求基于快照创建新卷。快照的一致性取决于:
- 存储系统是否支持崩溃一致快照;
- 应用是否在快照前暂停写入;
- 数据库是否完成 flush 或使用专用备份机制;
- CSI 驱动是否正确实现快照语义。
因此,“快照成功”不自动等于“应用级备份成功”。
2. 扩容分为控制器和节点阶段
卷扩容通常包括:
控制器扩容:
存储后端容量变大
节点扩容:
文件系统或设备在节点上识别新的容量
如果只完成后端扩容,应用容器内看到的文件系统容量可能仍未增加。必须确认 CSI 驱动、文件系统和 Kubernetes 版本共同支持对应流程。
3. 节点故障时的 Attach 风险
假设节点 A 失联,但云平台仍认为卷附着在节点 A:
Pod 被重新调度到节点 B
ControllerPublishVolume(B)
↓
云平台拒绝:volume already attached to A
这不是 Kubelet 单独能够解决的问题。控制器、CSI 驱动和云平台需要确认旧节点上的卷已经安全解除附加。强行 detach 可能导致数据未刷盘、文件系统损坏或双写。
生产环境必须把以下事实区分开:
- Kubernetes API 中 Pod 是否已删除;
- Kubelet 是否已停止容器;
- 节点是否仍可访问;
- 存储平台是否已完成 detach;
- 文件系统是否允许在新节点重新挂载。
删除 Pod 对象不等于存储设备已经安全释放。
八、三套接口如何共同影响一个 Pod
一个带网络和持久卷的 Pod,大致经过如下路径:
flowchart LR
API[Pod/PVC/PV API 对象]
S[Scheduler]
K[Kubelet]
CRI[CRI Runtime]
CNI[CNI Plugin]
CSIc[CSI Controller]
CSIn[CSI Node Plugin]
NET[Pod Network Namespace]
VOL[Mounted Volume]
APP[Application Container]
API --> S
S --> API
API --> K
K --> CSIc
CSIc --> VOL
K --> CSIn
CSIn --> VOL
K --> CRI
CRI --> CNI
CNI --> NET
K --> CRI
VOL --> APP
NET --> APP
CRI --> APP
更严格地看,卷准备通常发生在容器启动前:
Pod 被调度
↓
Kubelet 发现卷需求
↓
CSI Attach/Stage/Publish
↓
CRI RunPodSandbox
↓
CNI ADD
↓
CRI CreateContainer/StartContainer
具体顺序可能因卷类型、运行时和 Kubelet 内部协调细节有所不同,但应用容器必须在其所需的网络和卷环境准备好后才能正常启动。
三者的状态来源也不同:
| 能力 | 主要调用方 | 主要状态 | 典型失败事件 |
|---|---|---|---|
| CRI | Kubelet | Sandbox、Container、Image 状态 | CreateContainerError、CrashLoopBackOff |
| CNI | Runtime 调用网络插件 | 网卡、IP、路由、网络命名空间 | FailedCreatePodSandBox |
| CSI Controller | 控制器/sidecar | Volume、Attachment、Snapshot | ProvisioningFailed、FailedAttachVolume |
| CSI Node | Kubelet 调用 Node 插件 | 设备、staging、publish、mount | FailedMount、FailedUnmount |
九、一个完整的故障定位顺序
情况一:Pod 一直 Pending,且没有 Pod IP
先执行:
kubectl get pod -n demo app -o wide
kubectl describe pod -n demo app
若事件类似:
Failed to create pod sandbox:
failed to setup network for sandbox ...
应沿以下路径排查:
Kubelet
→ CRI Runtime socket
→ CNI 配置
→ CNI 二进制
→ IPAM
→ 节点路由/内核
此时不应先检查应用进程,因为业务容器可能根本没有被创建。
情况二:Pod 有 IP,但 PVC 未挂载
检查:
kubectl get pvc,pv -n demo
kubectl describe pod -n demo app
kubectl get volumeattachment
诊断分层:
- PVC 未绑定:看 StorageClass、provisioner 和配额;
- Attach 失败:看 CSI Controller、云平台卷状态和权限;
- Mount 失败:看 CSI Node、节点设备和文件系统;
- 容器内目录为空:检查
volumeMounts、路径和子路径。
情况三:Pod Running 但应用不可访问
此时 CRI、CNI、CSI 都可能已经“部分成功”:
- 容器进程是否真的监听目标端口;
- Readiness Probe 是否成功;
- Pod IP 是否可达;
- Service selector 是否选中了 Pod;
- EndpointSlice 是否包含该 Pod;
- NetworkPolicy 是否允许流量;
- CNI 跨节点路径和 MTU 是否正确;
- 应用是否读取到了正确的卷路径和数据。
“Pod 是 Running”只表示容器进程处于运行状态,不保证网络可达、服务就绪或存储内容正确。
十、常见误解与反例
误解一:安装 containerd 就等于安装了 Kubernetes 网络
不成立。containerd 是容器运行时的一种实现。它通常需要可用的 CRI 集成,并在创建 Sandbox 时调用配置好的 CNI。没有正确的 CNI 配置时,镜像可能能够拉取,但 Pod Sandbox 仍然创建失败。
误解二:Pod IP 由 Kubelet 直接分配
通常不成立。Kubelet 通过 CRI 请求运行时创建 Sandbox,运行时再调用 CNI 和 IPAM。Kubelet 会观察并报告 Pod 网络结果,但不负责实现地址池分配算法。
误解三:PVC 已经 Bound 就代表容器已经可以读写
不成立。PVC Bound 表示 PVC 与 PV 已建立绑定关系,不代表:
- 卷已附加到目标节点;
- 设备已被识别;
- 文件系统已挂载;
- 卷已发布到 Pod;
- 应用用户有读写权限。
误解四:CSI 驱动只负责创建磁盘
不完整。动态供给只是 CSI 的一部分。一个可用的块存储 CSI 集成还可能涉及 Attach、Detach、Stage、Publish、Unmount、扩容、快照和节点故障恢复。
误解五:删掉 Pod 就一定清理网络和磁盘
不保证立即完成。删除动作会触发异步控制循环:
删除 Pod
↓
Kubelet 停止容器
↓
CRI 删除容器和 Sandbox
↓
CNI DEL 清理网络
↓
CSI Node Unpublish/Unstage
↓
CSI Controller Unpublish
任何一步失败,都可能暂时留下网络接口、挂载点或云盘附着状态。应观察事件、控制器日志和节点实际状态,而不是只看 API 对象是否消失。
十一、版本和实现边界
1. CRI 版本
Kubernetes 已移除内置 dockershim;较新的 Kubernetes 集群需要使用实现 CRI 的运行时,例如 containerd 或 CRI-O。CRI 的 API 版本、运行时版本和 Kubernetes 版本必须兼容,不能仅凭“能启动进程”判断兼容性。
诊断运行时可以使用:
crictl info
crictl version
crictl pods
crictl ps -a
crictl 的 endpoint 配置必须指向实际 CRI socket。把它指向普通 containerd socket、错误的 CRI-O socket,或在多运行时节点上选错 endpoint,都会产生误导性结果。
2. CNI 版本和插件能力
CNI 规范版本、具体网络插件版本与 Kubernetes 版本是三个不同维度。插件是否支持:
- NetworkPolicy;
- 多网络;
- IPv6 或双栈;
- Windows;
- eBPF 数据面;
- 特定 MTU 或云网络模式;
不能只根据“它是 CNI 插件”推断。云厂商托管 Kubernetes 还可能把 CNI 与 VPC、ENI、路由表和安全组绑定,升级或切换插件的风险高于修改一个普通 DaemonSet。
3. CSI 驱动和 sidecar 必须匹配
CSI 驱动版本、sidecar 版本、Kubernetes API 版本和存储后端能力共同决定最终行为。尤其要注意:
- Attach 是否由驱动支持;
ReadWriteMany是否有真实共享语义;- 在线扩容是否支持;
- 快照 CRD 和控制器是否安装;
- 拓扑感知是否正确;
- 节点重启和强制 detach 是否有恢复机制。
某些旧的内置存储插件已经被 CSI Migration 替代或弃用。生产集群不应把旧教程中的 in-tree provisioner 名称直接复制到当前集群。
4. 云厂商差异
云平台可能改变以下语义:
- Pod IP 是否直接来自 VPC;
- 节点是否能直接访问 Pod 网段;
- 云盘是否支持跨可用区附加;
- 安全组是否作用于 Pod ENI;
- 网络和存储插件是否由云平台托管;
- 是否允许用户替换默认 CNI 或 CSI。
因此,Kubernetes API 中相同的 Pod、PVC 和 Service,不一定对应相同的数据路径。需要同时核对 Kubernetes 对象、节点内核和云平台控制面状态。
十二、如何设计清晰的责任边界
可以用三个问题快速判断组件归属:
问题一:谁创建和管理进程?
看 CRI:
镜像、Sandbox、容器、启动、停止、执行命令、容器状态
问题二:谁让网络命名空间获得地址并可达?
看 CNI:
网卡、IPAM、路由、网络策略数据面、网络资源清理
问题三:谁让节点和 Pod 获得持久化卷?
看 CSI:
创建卷、附加设备、格式化、挂载、发布、扩容、快照
一个故障也可能跨越边界。例如:
CSI 挂载目录存在
↓
CRI 成功启动容器
↓
应用向卷写入失败
这时问题可能是文件系统权限,而不是 CSI 控制器;又如:
CNI 成功分配 Pod IP
↓
应用仍然无法访问 Service
这时应检查 Service、EndpointSlice、kube-proxy/eBPF 数据面和 NetworkPolicy,而不是重复安装 CNI。
最终可以把三者压缩成一条判断链:
容器是否被创建和运行? → CRI
Pod 是否有正确的网络环境? → CNI
卷是否被正确提供和挂载? → CSI
CRI、CNI 和 CSI 的价值不只是“插件化”,而是把不同资源域的状态、失败路径和升级责任分开。理解这些边界后,Pending、ContainerCreating、CrashLoopBackOff、FailedCreatePodSandBox、FailedAttachVolume 和 FailedMount 就不再是相似的错误字符串,而是能够映射到不同控制循环和不同组件的数据流。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubelet 与容器运行时:CRI、Pod Sync、PLEG、Probe 和资源状态
- 下一篇:Kubernetes Pod 完整模型:共享 Namespace、容器、Sandbox 和生命周期
- 延伸:Kubernetes CNI 网络:Pod IP、路由、Overlay、Underlay 和插件选型
- 延伸:Kubernetes CSI:Controller、Node、Attach、Mount、快照和故障
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论