源码解析:从 kubelet、容器运行时看 CNI 的使用
这是 Kubernetes 网络学习的第三篇笔记。
- 深入探索 Kubernetes 网络模型和网络通信
- 认识一下容器网络接口 CNI
- 源码分析:从 kubelet、容器运行时看 CNI 的使用(本篇)
- 从 Flannel 学习 Kubernetes VXLAN 网络
- Kubernetes 网络学习之 Cilium 与 eBPF
- …
在上一篇中,通过对 CNI 规范的解读了解了网络配置的操作和相关的流程。在网络的几个操作中除了 CNI_COMMAND
外,有另外三个参数几乎每次都要提供 CNI_CONTAINERID
、CNI_IFNAME
和 CNI_NETNS
,这些参数无外乎都来自容器运行时。这篇将结合 Kubernetes 和 Containerd 源码,来分析一下 CNI 的使用。
Kubernetes 的源码来自分支 release-1.24
,Containerd 的来自分支 release/1.6
。
CNI 的使用
创建 Pod
在之前做过的 kubelet 源码分析 中曾提到 Kubelet#syncLoop()
会持续监控来自 文件、apiserver、http 的变更,来更新 pod 的状态。写那篇文章的时候,分析到这里就结束了。因为这之后的工作就交给容器运行时来完成 sandbox 和各种容器的创建和运行,见 kubeGenericRuntimeManager#SyncPod()
。
kubelet
封装 sandbox 和容器创建、运行请求,调用容器运行时的接口,将具体工作交由容器运行时来完成来完成(容器运行时接口 Container Runtime Interface,简称 CRI,找时间再进行研究)。
参考源码
Sandbox 容器
记得在 系列的第一篇 中,当我们在节点上查看命名空间时,网络命名空间的进程是 /pause
。
lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531992 net 126 1 root unassigned /lib/systemd/systemd --system --deserialize 31
4026532247 net 1 83224 uuidd unassigned /usr/sbin/uuidd --socket-activation
4026532317 net 4 129820 65535 0 /run/netns/cni-607c5530-b6d8-ba57-420e-a467d7b10c56 /pause
Kubernetes 在创建 pod 时,会先一个 sandbox 容器(使用 pause
镜像,启动时执行 /pause
进入休眠状态)。我们知道 Kubernetes 的 pod 中是允许多容器的,由这个 sandbox 容器来创建和维持网络命名空间,pod 的其他容器会加入到该命名空间中。因为 pause 镜像足够简单,不会出错导致网络管理空间在出错时被删除。sandbox 容器发挥着至关重要的作用,它在 PID 进程空间的进程树中作为 PID 为 1 的进程,其他容器进程都将其作为父进程。当其他容器的进程成为孤儿进程时,可以得到清理。
创建 Sandbox 容器
CRI 的 RuntimeServiceServer
定义了运行时对外提供的服务接口,除了管理 sandbox、容器相关的操作外,还有 streaming 相关的操作,即常用的 exec
、attach
、portforward
。streaming 相关的内容,可以参考之前的一篇 《源码解析 kubectl port-forward 工作原理》。
让我们来看容器相关的部分。
Containerd 的 criService
实现了 RuntimeServiceServer
的接口。创建 sandbox 容器的请求通过 CRI 的 UDS(Unix domain socket) 接口 /runtime.v1.RuntimeService/RunPodSandbox
,进入到 criService
的处理流程中。在 criService#RunPodSandbox()
,负责创建和运行 sandbox 容器,并保证容器状态正常。
- 容器运行时首先初始化容器对象,产生必要的参数
CNI_CONTAINERID
- 会创建 pod 网络命名空间,产生必要的参数
CNI_NETNS
- 然后调用 CNI 的接口来对 pod 的网络空间进行配置,比如创建网络接口、分配 IP 地址、创建 veth、设置路由等等一系列的操作。这些操作正是由具体的网络插件实现完成,不同插件之间的实现存在差异。了解了规范之后之后,网络的配置就不难了,其中 2 和 3 可能执行多次:
- 读取网络配置
- 查找二进制文件
- 执行二进制文件
- 向容器运行时反馈结果
- 最后便是创建 sandbox 容器,这个过程与操作系统的类型相关,会调用对应操作系统的方法来完成容器的创建。
从零开始学习容器,推荐阅读 Ivan Velichko 的 《Learning Containers From The Bottom Up》
参考源码:
创建其他容器
接下来就是创建 pod 内的其他容器:临时(ephemeral
)、初始化(init
)和普通容器,创建这些容器的时候,会将 sandbox 容器的。会加入到 sandox 的网络命名空间中。这里不展开,详细逻辑可参考 containerd 的 containerStore#Create()
。
参考源码
- kubernetes:
pkg/kubelet/kuberuntime/kuberuntime_manager.go:913
- containerd:
pkg/cri/server/container_create.go:51
总结
接着上篇 CNI 的规范介绍,这次又介绍了 CNI 的使用,以及如何与容器运行时的交互、Pod 的创建流程。
不同的 CNI 插件,实现了不一样的网络功能。下篇,将以 Flannel 为例来了解下 CNI 的实现,以及 Kubernetes VXLAN 网络。
为什么介绍 flannel?因为我常用的开发环境之一 k3s 默认就用的 flannel 网络。另一个开发环境是 k8e ,k8e 默认用的是 Cilium,cilium 的 cni 也是系列的文章之一。