源码解析:从 kubelet、容器运行时看 CNI 的使用
2023-1-20 08:2:42 Author: Docker中文社区(查看原文) 阅读量:10 收藏

在上一篇中,通过对 CNI 规范的解读了解了网络配置的操作和相关的流程。在网络的几个操作中除了 CNI_COMMAND 外,有另外三个参数几乎每次都要提供 CNI_CONTAINERIDCNI_IFNAME 和 CNI_NETNS,这些参数无外乎都来自容器运行时。这篇将结合 Kubernetes 和 Containerd[1] 源码,来分析一下 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,找时间再进行研究)。

参考源码

  • `pkg/kubelet/kubelet.go:1985`[2]
  • `pkg/kubelet/kuberuntime/kuberuntime_manager.go:711`[3]

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 容器发挥着至关重要的作用[4],它在 PID 进程空间的进程树中作为 PID 为 1 的进程,其他容器进程都将其作为父进程。当其他容器的进程成为孤儿进程时,可以得到清理。

创建 Sandbox 容器

CRI 的 RuntimeServiceServer 定义了运行时对外提供的服务接口,除了管理 sandbox、容器相关的操作外,还有 streaming 相关的操作,即常用的 execattachportforward。streaming 相关的内容,可以参考之前的一篇 《源码解析 kubectl port-forward 工作原理》

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

Containerd 的 criService 实现了 RuntimeServiceServer 的接口。创建 sandbox 容器的请求通过 CRI 的 UDS(Unix domain socket)[5] 接口 /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》[6]

参考源码:

  • `pkg/cri/server/sandbox_run.go:61`[7]
  • `pkg/cri/server/sandbox_run.go:422`[8]

创建其他容器

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

参考源码

  • kubernetes:`pkg/kubelet/kuberuntime/kuberuntime_manager.go:913`[9]
  • containerd:`pkg/cri/server/container_create.go:51`[10]

总结

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

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

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

参考资料

[1] 

Containerd: https://containerd.io

[2] 

pkg/kubelet/kubelet.go:1985https://github.com/kubernetes/kubernetes/blob/release-1.24/pkg/kubelet/kubelet.go#L1985

[3] 

pkg/kubelet/kuberuntime/kuberuntime_manager.go:711https://github.com/kubernetes/kubernetes/blob/release-1.24/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L711

[4] 

sandbox 容器发挥着至关重要的作用: https://www.ianlewis.org/en/almighty-pause-container

[5] 

UDS(Unix domain socket): https://en.wikipedia.org/wiki/Unix_domain_socket

[6] 

《Learning Containers From The Bottom Up》: https://iximiuz.com/en/posts/container-learning-path/

[7] 

pkg/cri/server/sandbox_run.go:61https://github.com/containerd/containerd/blob/release/1.6/pkg/cri/server/sandbox_run.go#L61

[8] 

pkg/cri/server/sandbox_run.go:422https://github.com/containerd/containerd/blob/release/1.6/pkg/cri/server/sandbox_run.go#L422

[9] 

pkg/kubelet/kuberuntime/kuberuntime_manager.go:913https://github.com/kubernetes/kubernetes/blob/release-1.24/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L913

[10] 

pkg/cri/server/container_create.go:51https://github.com/containerd/containerd/blob/release/1.6/pkg/cri/server/container_create.go#L51

[11] 

Flannel: https://github.com/flannel-io/flannel

[12] 

k3s: https://k3s.io/

[13] 

k8e: https://getk8e.com/

[14] 

Cilium: https://cilium.io/

推荐阅读 点击标题可跳转

《Docker是什么?》

《Kubernetes是什么?》

《Kubernetes和Docker到底有啥关系?》

《教你如何快捷的查询选择网络仓库镜像tag》

《Docker镜像进阶:了解其背后的技术原理》

《教你如何修改运行中的容器端口映射》

《k8s学习笔记:介绍&上手》

《k8s学习笔记:缩扩容&更新》

《Docker 基础用法和命令帮助》

《在K8S上搭建Redis集群》

《灰度部署、滚动部署、蓝绿部署》

《PM2实践指南》

《Docker垃圾清理》

《Kubernetes(k8s)底层网络原理刨析》

《容器环境下Node.js的内存管理》

《MySQL 快速创建千万级测试数据》

《Linux 与 Unix 到底有什么不同?》

《浅谈几种常见 RAID 的异同》

《Git 笔记-程序员都要掌握的 Git》

《老司机必须懂的MySQL规范》

《Docker中Image、Container与Volume的迁移》

《漫画|如何用Kubernetes搞定CICD》

《写给前端的Docker实战教程》

《Linux 操作系统知识地图2.0,我看行》

《16个概念带你入门 Kubernetes》

《程序员因接外包坐牢456天,长文叙述心酸真实经历》

《IT 行业老鸟,有话对你说》

《HTTPS 为什么是安全的?说一下他的底层实现原理?


免责声明:本文内容来源于网络,所载内容仅供参考。转载仅为学习和交流之目的,如无意中侵犯您的合法权益,请及时联系Docker中文社区!



文章来源: http://mp.weixin.qq.com/s?__biz=MzI1NzI5NDM4Mw==&mid=2247494396&idx=1&sn=0519792ee099b604ed0884c5d9754c4d&chksm=ea1b03bcdd6c8aaa3cfc6dac4327c7942037281a40426efd8d5c49ec0fd67f84562572574f94#rd
如有侵权请联系:admin#unsafe.sh