源码解析:从 kubelet、容器运行时看 CNI 的使用

源码解析:从 kubelet、容器运行时看 CNI 的使用

这是 Kubernetes 网络学习的第三篇笔记。

在上一篇中,通过对 CNI 规范的解读了解了网络配置的操作和相关的流程。在网络的几个操作中除了 CNI_COMMAND 外,有另外三个参数几乎每次都要提供 CNI_CONTAINERIDCNI_IFNAMECNI_NETNS,这些参数无外乎都来自容器运行时。这篇将结合 Kubernetes 和 Containerd 源码,来分析一下 CNI 的使用。

Kubernetes 的源码来自分支 release-1.24,Containerd 的来自分支 release/1.6

CNI 的使用

runtime-with-cni

创建 Pod

在之前做过的 kubelet 源码分析 中曾提到 Kubelet#syncLoop() 会持续监控来自 文件apiserverhttp 的变更,来更新 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 相关的操作,即常用的 execattachportforward。streaming 相关的内容,可以参考之前的一篇 《源码解析 kubectl port-forward 工作原理》

让我们来看容器相关的部分。

Containerd 的 criService 实现了 RuntimeServiceServer 的接口。创建 sandbox 容器的请求通过 CRI 的 UDS(Unix domain socket) 接口 /runtime.v1.RuntimeService/RunPodSandbox,进入到 criService 的处理流程中。在 criService#RunPodSandbox(),负责创建和运行 sandbox 容器,并保证容器状态正常。

  1. 容器运行时首先初始化容器对象,产生必要的参数 CNI_CONTAINERID
  2. 会创建 pod 网络命名空间,产生必要的参数 CNI_NETNS
  3. 然后调用 CNI 的接口来对 pod 的网络空间进行配置,比如创建网络接口、分配 IP 地址、创建 veth、设置路由等等一系列的操作。这些操作正是由具体的网络插件实现完成,不同插件之间的实现存在差异。了解了规范之后之后,网络的配置就不难了,其中 2 和 3 可能执行多次:
    1. 读取网络配置
    2. 查找二进制文件
    3. 执行二进制文件
    4. 向容器运行时反馈结果
  4. 最后便是创建 sandbox 容器,这个过程与操作系统的类型相关,会调用对应操作系统的方法来完成容器的创建。

从零开始学习容器,推荐阅读 Ivan Velichko 的 《Learning Containers From The Bottom Up》

参考源码:

创建其他容器

接下来就是创建 pod 内的其他容器:临时(ephemeral)、初始化(init)和普通容器,创建这些容器的时候,会将 sandbox 容器的。会加入到 sandox 的网络命名空间中。这里不展开,详细逻辑可参考 containerd 的 containerStore#Create()

参考源码

总结

接着上篇 CNI 的规范介绍,这次又介绍了 CNI 的使用,以及如何与容器运行时的交互、Pod 的创建流程。

不同的 CNI 插件,实现了不一样的网络功能。下篇,将以 Flannel 为例来了解下 CNI 的实现,以及 Kubernetes VXLAN 网络。

为什么介绍 flannel?因为我常用的开发环境之一 k3s 默认就用的 flannel 网络。另一个开发环境是 k8e ,k8e 默认用的是 Cilium,cilium 的 cni 也是系列的文章之一。

(转载本站文章请注明作者和出处乱世浮生,请勿用于任何商业用途)

comments powered by Disqus