Kubernetes网络模型与CNI网络插件

Kubernetes网络模型

  • 网络插件真正要做的事情,是通过某种方法,把不同宿主机上的特殊设备连通,从而达到容器跨主机通信的目的
  • Kubernetes通过一个叫做CNI的接口,维护了一个单独的网桥来代替docker0,网桥的名字就叫做CNI网桥,设备名称默认是cni0
  • Kubernetes 之所以要设置这样一个与 docker0 网桥功能几乎一样的 CNI 网桥,主要原因包括两个方面:
    • Kubernetes 项目并没有使用 Docker 的网络模型(CNM),所以它并不希望、也不具备配置 docker0 网桥的能力;
    • 这还与 Kubernetes 如何配置 Pod,也就是 Infra 容器的 Network Namespace 密切相关。
  • CNI的设计思想:Kubernetes 在启动 Infra 容器之后,就可以直接调用 CNI 网络插件,为这个 Infra 容器的 Network Namespace,配置符合预期的网络栈。

网络栈的配置工作是如何完成的?

  • 在部署 Kubernetes 的时候,有一个步骤是安装 kubernetes-cni 包,它的目的就是在宿主机上安装 CNI 插件所需的基础可执行文件。(/opt/cni/bin)
    • 第一类,叫作 Main 插件,它是用来创建具体网络设备的二进制文件。比如,bridge(网桥设备)、ipvlan、loopback(lo 设备)、macvlan、ptp(Veth Pair 设备),以及 vlan。
    • 第二类,叫作 IPAM(IP Address Management)插件,它是负责分配 IP 地址的二进制文件。比如,dhcp,这个文件会向 DHCP 服务器发起请求;host-local,则会使用预先配置的 IP 地址段来进行分配。
    • 第三类,是由 CNI 社区维护的内置 CNI 插件。比如:flannel,就是专门为 Flannel 项目提供的 CNI 插件;tuning,是一个通过 sysctl 调整网络设备参数的二进制文件;portmap,是一个通过 iptables 配置端口映射的二进制文件;bandwidth,是一个使用 Token Bucket Filter (TBF) 来进行限流的二进制文件。
  • 实现一个给Kubernetes用的容器网络方案
    • 实现这个网络方案本身:需要编写的,其实就是 flanneld 进程里的主要逻辑。比如,创建和配置 flannel.1 设备、配置宿主机路由、配置 ARP 和 FDB 表里的信息等等。
    • 实现该网络方案对应的 CNI 插件:配置 Infra 容器里面的网络栈,并把它连接在 CNI 网桥上。
  • 在宿主机上安装flanned。在这个过程中flanneld启动后会在每台宿主机上生成它对应的CNI配置文件(ConfigMap),从而告诉kubernetes,这个集群要使用Flannel作为容器网络方案
  • kubelet调用CRI接口来执行处理容器网络相关的逻辑,docker项目的CRI实现叫做dockershim
  • dockershim加载上述的CNI配置文件,kubernetes不支持多个CNI插件混用,但可以定义多个插件进行协作
  • Flannel项目就指定了flannel和portmap这两个插件,dockershim会把这个CNI配置文件加载起来,flannel 和 portmap 插件会按照定义顺序被调用,从而依次完成“配置容器网络”和“配置端口映射”这两步操作。

CNI插件的工作原理

  • kubelet组件创建Pod时,第一个创建的一定是Infra容器

  • dockershim 就会先调用 Docker API 创建并启动 Infra 容器

  • 执行一个叫作 SetUpPod 的方法,为 CNI 插件准备参数,然后调用 CNI 插件为 Infra 容器配置网络。

    • 调用的CNI插件就是/opt/cni/bin/flannel

    • 所需的参数,第一部分,是由 dockershim 设置的一组 CNI 环境变量。(CNI_COMMAND:ADD和DEL)

      • ADD:把容器添加到CNI网络里
        • 参数:容器里网卡的名字 eth0(CNI_IFNAME)、Pod 的 Network Namespace 文件的路径(CNI_NETNS)、容器的 ID(CNI_CONTAINERID)等。这些参数都属于上述环境变量里的内容。其中,Pod(Infra 容器)的 Network Namespace 文件的路径,我在前面讲解容器基础的时候提到过,即:/proc/< 容器进程的 PID>/ns/net。除此之外,在 CNI 环境变量里,还有一个叫作 CNI_ARGS 的参数。通过这个参数,CRI 实现(比如 dockershim)就可以以 Key-Value 的格式,传递自定义信息给网络插件。这是用户将来自定义 CNI 协议的一个重要方法。
      • DEL:把容器从CNI网络里移除
  • 第二部分,则是 dockershim 从 CNI 配置文件里加载到的、默认插件的配置信息。

    • 这个配置信息在 CNI 中被叫作 Network Configuration,dockershim 会把 Network Configuration 以 JSON 数据的格式,通过标准输入(stdin)的方式传递给 Flannel CNI 插件。

    • 配置文件中的delegate字段,表示CNI插件并不会自己做事,而是会调用内置插件

    • 它唯一作用是补充完整配置文件

    • ipam字段10.244.1.0/24读取自Flannel在宿主机上生成的Flannel配置文件(/run/flannel/subnet.env)

    • 接下来,Flannel CNI 插件就会调用 CNI bridge 插件,也就是执行:/opt/cni/bin/bridge 二进制文件。

    • 这一次,调用 CNI bridge 插件需要的两部分参数的第一部分、也就是 CNI 环境变量,并没有变化。所以,它里面的 CNI_COMMAND 参数的值还是“ADD”。

    • 而第二部分 Network Configration,正是上面补充好的 Delegate 字段。Flannel CNI 插件会把 Delegate 字段的内容以标准输入(stdin)的方式传递给 CNI bridge 插件。(Delegate字段以Json文件方式保存/var/lib/cni/flannel目录下,为了给后面删除容器调用DEL操作使用)

    • 有了两部分参数,CNI bridge插件就可以将容器加入到CNI网络里

      • 首先,CNI bridge 插件会在宿主机上检查 CNI 网桥是否存在。如果没有的话,那就创建它。这相当于在宿主机上执行:

        # 在宿主机上
        $ ip link add cni0 type bridge
        $ ip link set cni0 up
        
      • 接下来,CNI bridge 插件会通过 Infra 容器的 Network Namespace 文件,进入到这个 Network Namespace 里面,然后创建一对 Veth Pair 设备。

      • 紧接着,它会把这个 Veth Pair 的其中一端,“移动”到宿主机上。这相当于在容器里执行如下所示的命令:

        #在容器里
        
        # 创建一对Veth Pair设备。其中一个叫作eth0,另一个叫作vethb4963f3
        $ ip link add eth0 type veth peer name vethb4963f3
        
        # 启动eth0设备
        $ ip link set eth0 up 
        
        # 将Veth Pair设备的另一端(也就是vethb4963f3设备)放到宿主机(也就是Host Namespace)里
        $ ip link set vethb4963f3 netns $HOST_NS
        
        # 通过Host Namespace,启动宿主机上的vethb4963f3设备
        $ ip netns exec $HOST_NS ip link set vethb4963f3 up 
        
      • 接下来,CNI bridge 插件就可以把 vethb4963f3 设备连接在 CNI 网桥上。这相当于在宿主机上执行:

        # 在宿主机上
        $ ip link set vethb4963f3 master cni0
        

      在将 vethb4963f3 设备连接在 CNI 网桥之后,CNI bridge 插件还会为它设置 Hairpin Mode(发夹模式)。这是因为,在默认情况下,网桥设备是不允许一个数据包从一个端口进来后,再从这个端口发出去的。但是,它允许你为这个端口开启 Hairpin Mode,从而取消这个限制。

      • 接下来,CNI bridge 插件会调用 CNI ipam 插件,从 ipam.subnet 字段规定的网段里为容器分配一个可用的 IP 地址。然后,CNI bridge 插件就会把这个 IP 地址添加在容器的 eth0 网卡上,同时为容器设置默认路由。这相当于在容器里执行:

        # 在容器里
        $ ip addr add 10.244.0.2/24 dev eth0
        $ ip route add default via 10.244.0.1 dev eth0
        
      • 最后,CNI bridge 插件会为 CNI 网桥添加 IP 地址。这相当于在宿主机上执行:

        # 在宿主机上
        $ ip addr add 10.244.0.1/24 dev cni0
        

      在执行完上述操作之后,CNI 插件会把容器的 IP 地址等信息返回给 dockershim,然后被 kubelet 添加到 Pod 的 Status 字段。

流程图

CNI插件原理.png

# k8s  

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×