Heath Kang

Kubernetes

Kubernetes 的抽象并不是“把 Docker 放到多台机器上”。它至少要同时虚拟化三件事:计算如何运行、网络如何连通、存储如何挂载。CRI、CNI、CSI 的价值在于:kubelet 和控制面只描述意图,具体实现可以替换。

PodSpec 到一个真正可访问的应用,可以把过程看成三条并行但有依赖的链路。

PodSpec
  ├─ CRI:创建 sandbox 与业务容器
  ├─ CNI:为 sandbox 配置网卡、IP、路由与策略
  └─ CSI:attach / mount 所需 volume

Kubernetes 控制面通过 API Server、调度器与 kubelet 将期望状态落实到节点

这张总览图里最容易被忽略的是 kubelet 的位置:控制面不直接在节点上创建容器或挂载卷,而是通过 API Server 写入期望状态;scheduler 选择节点后,节点 kubelet 再协调 CRI、CNI、CSI 等插件把这个 Pod 真正落地。下面三条链路,都是从 kubelet 所在的节点向下展开的。

CRI:把“运行容器”从 kubelet 中拆出去

kubelet 是节点上的期望状态执行者:它接收分配给本节点的 PodSpec,并确保 Pod 处于期望状态。但 kubelet 不应该内嵌某一家 runtime,因此它通过 gRPC 的 Container Runtime Interface 调用 containerd、CRI-O 等运行时。Kubernetes 已移除内置 dockershim;现代集群要求节点 runtime 实现 CRI。

一个关键顺序是:先 RunPodSandbox,再 CreateContainerStartContainer。sandbox 先建立 Pod 的共享环境,尤其是 network namespace;业务容器随后加入它。这正是 Pod 能拥有稳定 IP、sidecar 能通过 localhost 通讯的原因。

一个 Pod 中的 pause sandbox、veth 与两个业务容器共享同一个网络命名空间

图中的 pause 是对 sandbox/infra container 的常见直观表达。重点不在 pause 进程本身做了什么,而在于它先把 network namespace 固定下来:container 1container 2 看到的是同一张网卡、同一个 Pod IP、同一个端口空间。因此同一 Pod 内的 sidecar 可以访问 localhost,而两个不同 Pod 即使都监听 8080,也不会冲突。

CRI 处理的是“进程在哪里、以什么隔离与资源限制运行”;它不直接决定跨节点网络或持久卷,这两件事交给 CNI 与 CSI。

CNI:把 Pod 变成网络中的一等公民

runtime 创建 sandbox 后会调用 CNI 插件。CNI 的基本职责很小:给某个 network namespace 配置接口、地址、路由,并在删除时清理。CNI 不是 Kubernetes 专有协议,因此它能让不同网络实现挂接到同一套生命周期。

从默认 bridge 开始,理解 Kubernetes 为什么还需要 CNI

下面的图很像刚接触 Docker 时看到的网络:每个节点上都有 docker0,容器通过 veth 接入 bridge。单机内这没有问题;但若所有节点都各自使用相同的 172.17.0.0/16,上层路由器无法判断去往 172.17.0.2 的包究竟应该送到哪一台节点。它只能解决“容器接到本机网络”,还没有解决“Pod 在集群网络里可达”。

两个节点各自使用 Docker bridge 的单机容器网络示意

Kubernetes 的网络模型要求每个 Pod 有集群内可达的 IP,并尽量让 Pod-to-Pod 通信不需要经过额外的地址转换。因此 CNI 首先要处理的就是:为节点分配不重叠的 Pod CIDR,并把去往其他 Pod CIDR 的路由装进节点或底层网络。

常见的跨节点模型有两类:

  • underlay:Pod IP 直接来自物理/云网络可路由的地址空间,网络设备或云路由知道 Pod 网段;
  • overlay:Pod 包被封装在节点间可达的 underlay 包中,例如 VXLAN/Geneve。底层只需知道节点地址,代价是封装开销与 MTU 管理。

在 underlay/路由模式中,每个节点拿到一段不同的 Pod CIDR,例如图中的 10.0.1.0/2410.0.2.0/24。节点的 cbr0 把本机 Pod 接入该网段;上层 router 或云路由表保存“目的 Pod CIDR → 下一跳节点 IP”的映射。包从一个 Pod 发出时,外层并不需要封装,只是按路由转发到持有目标 Pod 网段的节点。

通过节点 Pod CIDR 和路由表让跨节点 Pod IP 直接可达

overlay 则换了一种取舍:底层网络不认识每个 Pod CIDR,只需要认识节点 IP。以图中的 Flannel/VXLAN 思路为例,原始包仍保持 src=pod1dst=pod4;节点 1 在 flannel0 处把它封进 UDP/VXLAN 报文,外层地址改为 src=node1dst=node2,到达节点 2 后再解封。这样能在底层路由不可控时工作,但要为封装预留 MTU,并接受额外的封装与解封路径。

Flannel overlay 将 Pod 到 Pod 的原始报文封装为节点到节点的 UDP 报文

下面这张动态图把上述过程放回节点网络命名空间:Pod 的 eth0 经 veth 接入 cbr0,再由 flannel0 走向另一台节点;具体实现的设备名会不同,但“Pod namespace → 节点数据平面 → 节点间网络 → 目标 Pod namespace”的层次相同。

Flannel 跨节点 Pod 通信的数据路径动画

无论哪种模型,Kubernetes 关心的语义相同:每个 Pod 有自己的 IP;Pod 之间可以直接通信;节点代理不应篡改 Pod-to-Pod 流量。实际 CNI 还会增加 NetworkPolicy、eBPF 数据平面、多网卡等能力。Cilium、Multus 分别是后两类扩展的代表。

Service:虚拟 IP 并不是真实网卡

Pod IP 会随重建变化,Service 的 ClusterIP 解决的是“服务地址稳定、后端可变”。它通常不存在于某块网卡上;数据平面把发往 VIP:port 的连接转换到某个 Endpoint。

client → ClusterIP:443
       → 选择一个 endpoint
       → DNAT 为 PodIP:targetPort

早期 userspace kube-proxy 自己收包并转发,路径长且有额外拷贝。iptables 模式把选择和 DNAT 编译为内核规则;IPVS 用内核负载均衡表改善大规模 Service 的规则管理。较新的 kube-proxy 也提供 nftables 模式;而 eBPF 实现可以在 CNI 数据平面中绕开部分传统规则路径。它们解决的是同一问题,只是“把服务发现编译到哪里”的不同答案。官方文档也明确区分 iptables、IPVS 与 nftables 模式。

要特别区分 DNAT 与 SNAT:DNAT 改目标,让请求抵达后端;SNAT 改源,常用于回程路由不对称或出集群流量。错误理解这两者,是排查 externalTrafficPolicy、客户端源 IP 丢失和回包失败时最常见的根源。

CSI:把“卷”变成可调度的声明

CSI 将存储供应商实现从 Kubernetes 核心中移出。典型链路分为控制面和节点面:控制器负责创建/删除 volume、必要时 attach;节点插件负责把设备 mount 到节点,再把它 bind mount 到 Pod。对于网络文件系统或临时卷,并不一定发生 attach;对于块存储,attach 和 mount 通常是两步。

PVC/PV/StorageClass 的关键不是对象名,而是把“我需要什么样的容量与访问模式”交给调度和供应系统。ReadWriteOnceReadWriteMany、拓扑限制、扩容与快照,都是存储语义的一部分,不能靠容器镜像解决。

这里还有一个很实际的先后关系:如果 volume 有可用区、节点或磁盘类型的拓扑限制,调度器不能先随便挑一台节点、再希望 CSI 临时解决。StorageClass 的绑定模式会影响“先为 PVC 供给卷”还是“等 Pod 被调度到合适的节点后再供给”。遇到 Pod 一直 Pending、卷却已经创建的情况时,检查 PVC/PV 事件、NodeAffinity 与 CSI controller 日志,通常比反复重建 Pod 更有效。

用一条请求来验证三种虚拟化

当一个 Service 访问失败时,按链路验证最有效:

  1. kubelet/CRI 是否真的创建并启动了 sandbox 与 container?
  2. CNI 是否给 Pod 配了正确的 IP、路由和策略?
  3. EndpointSlice 是否包含 ready 的 Pod?
  4. Service 的转发规则是否把 VIP 正确映射到 endpoint?
  5. 若应用依赖数据,CSI volume 是否已 attach/mount 且权限正确?

References

Notion 相关零散笔记

  • Kubernetes
  • Docker
  • cilium
  • Multus
  • iptables
  • NAT
  • Storage
  • Block storage

相关文档