Kubernetes | 读代码:Flannel Backends(3) - CNI

2022 09 27, Tue

续上之前鸽了的 flannel 系列。

之前的 Thread:

前面我们读了很多 flannel 在节点上的 DaemonSet 的实现 - flanneld,这个 DaemonSet 解决了节点间子网的通信问题。但是很显然容器本身的 IP 分配、网络配置等需求,并没有在这里面体现出来。

之前根据 go.mod 中有 github.com/containernetworking/plugins 猜测 flanneld 中有 CNI 的实现,后来阅读了源码发现也并没有,只是 vxlan 用了这个包中的 sysctl。也就是说 flanneld 和 CNI 并没有什么关系。

结合 Kubernetes 的网络实现经常和 CNI 插件一起被提及,我搜索了一下 CNI 的内容。

容器网络接口

CNI 全称 容器网络接口 ( Container Network Interface ) ,根据 Github 上 CNI 对自己的定义,这是一套配置容器网络的接口标准:

CNI (Container Network Interface), a Cloud Native Computing Foundation project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins.

既然是容器网络接口,那么这里按照我的理解就是这一套接口实际上的用途就是在容器启动过程中配置容器网络,比如说在特定的命名空间中创建接口、配置路由这些。

内置的插件

CNI 项目自带一部分插件,来满足非常常见的一些需求:

  • 创建接口
    • bridge: 最常见的桥接
    • ipvlan
    • macvlan
    • loopback
    • ptp
    • vlan
    • host-device: 把宿主机的网络设备移动到容器网络命名空间中
  • IP 管理
    • dhcp
    • host-local
    • static
  • 其他
    • tuning: 通过sysctl调优
    • portmapping: 通过iptables端口映射
    • bandwidth: 通过tc做流量整形
    • sbr: 源地址路由
    • firewall: 容器防火墙

Kubernetes 的网络设计

也就是说 CNI 可以认为是在容器启停的时候的一个 hook,Kubernetes 希望通过这种方法来配置容器网络,以此解决容器网络连通性的问题。

比如说创建一个 Pod 以后,kubelet 会做这样一个事情

  • 创建各种命名空间
  • 启动一个 sandbox 容器
  • 根据 /etc/cni/net.d/* 逐个启动插件
    • 插件创建网络接口,配置网络,确保连通
  • 用 sandbox 容器的命名空间启动其他容器

类似的,删除 Pod 的时候,就是把上面的过程逆转过来

基于 DaemonSet 的网络插件

那么既然 CNI 把配置网络这些什么都做了,为什么还要 flannel 这种基于 DaemonSet 的网络插件呢?

CNI 配置管理

一方面 kubelet 是根据 CNI 的配置来执行 CNI 插件的,这些配置的管理需要标准化,需要一个程序来管理。

打个比方,假如说我们办公室的开发用虚拟化集群手动管理,就有一个问题,我们有很多物理机,物理机上虚拟机的 IP 和 路由需要管理员手动管理,久而久之规模大了这些信息的信息量非常大,管理起来非常吃力,很难管理虚拟机的 IP。

相比较而言,更合理的方案就是用一个常驻的插件,通过插件启停来判断子网占用情况,启动时占用子网,并且写入网络插件的配置、告诉网络设备配置、IP 分配的范围、方法,停止时释放子网。

减少 CNI 插件工作量

另一方面是很多工作是不需要每次都在 CNI 插件中完成的,比如说子网的路由,在节点启动时配置就好了。

如果我们每次启动 Pod 都要执行一遍 CNI 插件,就会造成大量的资源浪费。Kubernetes 设计上支持大量 Pod 并发启动,每次都执行会非常影响启动的效率。

集群外配置

除此之外有一些网络插件集成的是集群外的资源,比如说 BGP Router,比如说云主机的 SDN 网络,通常会带有一些秘钥一类的信息。

在 Kubernetes 中我们可以通过 Secret + Secret Provider 来安全的储存这些信息,并且挂载到 Pod 里面。但是 CNI 插件的启动依赖于 kubelet 或者 CRI 运行时,依据的只有 Pod 的信息和配置文件,没有办法保存这些信息。所以用 DaemonSet 可以更好的管理这些内容。

flannel 架构设计

有 DaemonSet 以后 flannel 就不需要 CNI 了吗?

我们之前看到 flanneld 没有用到 CNI 的东西,那么 flannel 是真的不需要 CNI 插件,只要配置好宿主机网络就行了吗?

猜测:根据 Kubernetes 设计和安装过程 -> 有

Kubernetes 寄希望于 CNI 来解决网络问题,安装时各种安装器也都有下载并解压 containernetworking/plugins 到 /opt/cni 目录,所以猜测这些 plugins 应该是有用的,不需要的话完全可以缩短安装过程不安装这些东西。

猜测:根据 Pod 启动过程 -> 有

虽然节点子网的网络连通性由 flannel 的 DaemonSet 解决了,但是 Pod 启动过程还是需要一个方法来分配网络接口,从这一点来说 CNI 插件的存在还是必要的。

猜测:部署好的集群中存在 /etc/cni/net.d/10-flannel.conflist -> 有

在我们安装好的集群中我们可以看到 CNI 配置目录中存在一个配置文件,是 flannel 的,内容是

{
    "name": "cbr0",
    "version": "0.3.1",
    "plugins": [
        {
            "type": "flannel",
            "delegate": {
                "harpinMode": true,
                "isDefaultGateway": true
            }
        },
        {
            "type": "portmap",
            "capabilities": {
                "portMappings": true
            }
        }
    ]
}

根据 type: flannel 看了一眼 /opt/cni/bin 的确是有一个二进制 flannel

$ flannel --version
CNI flannel plugin v0.9.1

说明 flannel 还是用到 CNI 插件了的。

文档: flannel-io/flannel

仔细阅读 flannel 项目的 README 以后,发现 README 里面写了的确是提供了一个 CNI 插件:

Flannel does not control how containers are networked to the host, only how the traffic is transported between hosts. However, flannel does provide a CNI plugin for Kubernetes and a guidance on integrating with Docker.

但是项目里面的确是没有 CNI 插件。

文档: flannel-io/cni-plugin

在 flannel-io 组织中翻找以后发现 cni-plugin 仓库,并且有很完善的文档。

根据文档

  • flannel 启动以后会产生一个 /run/flannel/subnet.env 文件
  • 根据 10-flannel.conflist 启动 flannel 的 cni 插件以后,插件会产生一个类似下面的 bridge 的配置文件,并调用 bridge
    {
      "name": "mynet",
      "type": "bridge",
      "mtu": 1472,
      "ipMasq": false,
      "isGateway": true,
      "ipam": {
        "type": "host-local",
        "subnet": "10.1.17.0/24"
      }
    }
    
  • 由 bridge 完成网络配置,由 host-local 完成 IP 分配。

host-local 实现了一个简易的本地 IP 池。数据在 /var/lib/cni/networks/<NETWORK> 里面

其中有意思的事,flannel 中我们可以通过 delegate 添加额外的选项,这些选项会加入 bridge 的配置中,比如说我们可以设置 hairpinMode。

验证: 通过 flannel 安装用的 yaml 文件验证想法

https://github.com/flannel-io/flannel/blob/master/Documentation/kube-flannel.yml

可以看到 flannel 的 DaemonSet 从 91行开始,其中包括一个 init container用来安装 CNI 插件 一个 init container 用来安装 CNI 插件配置,然后就只剩下用来维护主机网络的container了

Flannel 网络实现设计

也就是说,flannel 其实就是通过分解问题,来分两步解决 Kubernetes 的网络问题:

  • 通过 DaemonSet 解决节点间子网通信和路由问题:确保主机之间可以正确分配子网配置路由
  • 通过 CNI 插件为 Pod 分配 IP:确保 Pod 可以分配到子网内的 IP

Pod/Status 中 IP 写回

除了这个之外,这次调研解决了我另一个问题,就是 IP 具体怎么分配的问题。

host-local

CNI 插件中有一类插件叫 ipam,用来分配 IP 地址,host-local 指的是在本地目录中记录 IP 池分配情况并分配 IP。

Pod 创建

Pod 创建过程中其实并不需要 IP 的信息,而是 CRI 创建 Pod 后,通过 PodSandboxStatus 在 Pod 同步的过程中读取 PodIPs.

Next …

也许我们下来可以看一眼 calico 的实现?