1. Overview

Kuberentes 对任何网络实现提出了以下要求:

  • Pod 可以不借助 NAT 访问同一集群中任意 Node 上所有 Pod(包括自己)
  • Node 上的服务可以不借助 NAT 访问同一集群中任意 Node 上所有 Pod

Kubernetes 采用了 CNI 规范,第三方厂商只要遵循该规范就可为 Kubernetes 实现 L3 层的网络自定义。

绝大部分 Kubernetes 网络插件包括两个部分:

  • 二进制 CNI 文件:分配 Pod IP,创建虚拟网络设备,Pod插上网线
  • 网络模型:通过在 Kubernetese 运行 controller/agent 服务私有 overlay/underlay 网络,保证Pod联通性

CNI 规范主要对二进制 CNI 文件进行限制,而如何实现网络模型 Kubernetes 并没有要求。

2. CNI Spec

当前CNI 1.0.0依然处于开发状态,最新的稳定为 v0.4.0,不同版本之间CNI约束条件不一样,参数也不一样。

官方Git

官方SPEC

1. 限制

CNI和CRI交互时有以下几个约定:

  • CRI实现调用CNI接口前,必须创建好相应的网络命名空间。
  • CRI必须指定容器归属的网络,并且对于每个网络,CRI需要判断需要运行那些插件
  • CNI的配置文件必须是JSON格式
  • 单个容器加入/退出网络时,CRI串行运行插件
  • 容器生命周期完成后,运行时必须以相反的顺序(相对于添加容器执行的顺序)执行插件,以使容器与网络断开连接
  • 同一容器不能并行操作,但可以为不同容器调用并行操作
  • ADD和DEL是顺序的,不能重复ADD,但是可以重复DEL(要求DEL应该是幂等的)
  • CNI存储网络状态为:(network name, CNI_CONTAINERID, CNI_IFNAME)

2. 调用方式约定

  • CNI插件必须实现成可执行文件给Kubernetes调用
  • CRI会使用配置文件中的Type值调用二进制文件,Kubernetes原生集成了一些CNI插件,被放置在/opt/cni/bin目录下

以下是配置文件 calico-vxlan 的 cni 配置文件( /etc/cni/net.d/10-calico.conflist ):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
  "name": "k8s-pod-network",
  "cniVersion": "0.3.1",
  "plugins": [
    {
      "type": "calico",
      "log_level": "info",
      "log_file_path": "/var/log/calico/cni/cni.log",
      "datastore_type": "kubernetes",
      "nodename": "ebdedde0-9d1b-5088-a05f-a47620a0b75b",
      "mtu": 0,
      "ipam": {
          "type": "calico-ipam"
      },
      "policy": {
          "type": "k8s"
      },
      "kubernetes": {
          "kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
      }
    },
    {
      "type": "portmap",
      "snat": true,
      "capabilities": {"portMappings": true}
    },
    {
      "type": "bandwidth",
      "capabilities": {"bandwidth": true}
    }
  ]
}

上述配置文件中,串行定义了以下三个不同类型 CNI 插件,这些插件通过二进制的方式保存在 /opt/cni/bin/ 目录下,当 Kubelet 创建容器时依次调用这些二进制文件。

  • calico
  • portmap
  • bandwidth

3. 调用接口约定

  • 插件支持的Operations
    • ADD:添加容器到网络,操作的输入如上面两点所描述,返回值如下
      • Interfaces list:接口列表,根据CNI的实现有所不同
      • IP configuration assigned to each interface:分配到容器的IP地址,路由、网关等信息
      • DNS:容器内的DNS信息
    • DEL:删除容器网络
      • 所有参数和ADD一样,但是JSON参数中可能会多一个prevResult字段,该字段是ADD操作的返回值。如何不带这个字段表示删除容器中的所有网络资源,否则只删除prevResult字段描述的资源
      • DEL操作要在不返回ERROR的情况下,尽可能回收分配的资源
    • CHECK:检查容器的网络是否符合预期
      • 输入参数和ADD完全一样,但是JSON参数中必须多一个prevResult字段
      • 返回none或者抛出一个异常
    • VERSION:返回CNI spec的版本信息

4. 调用参数约定

  • CRI通过环境变量的方式向这些二进制文件传递参数:
    • CNI_COMMAND:操作类型,包括ADD、DEL、CHECK、VERSION
    • CNI_CONTAINERID:容器ID
    • CNI_NETNS:容器命名空间地址
    • CNI_IFNAME:容器内网络设备名称
    • CNI_ARGS:用户额外传递的参数,例如,“ FOO = BAR; ABC = 123”
    • CNI_PATH:CNI插件执行文件的搜索路径,用":“分隔
  • CNI插件的JSON配置信息,通过stdin流式传输到插件
    • CNI插件的配置文件被保存在磁盘上(通常是/etc/cni/net.d/10-xxxx.conflist),或者由CRI实时生成
    • JSON中通常会有一个插件列表,列表中的插件会串行运行

5. 返回值约定

插件通过标准输出流返回JSON格式的信息,主要包括:cniVersioninterfacesipsroutesdns 等信息

3.CNI 自带的插件

为了避免CNI插件开发者反复实现一些相同的功能,CNI项目维护了一些基础插件。这些插件的源码在:containernetworking/pluginsCNI官网有比较详细的说明文档。

  • IPAM类插件:

    • dhcp plugin:基于DHCP的IP地址分配
      • /opt/cni/bin/dhcp需要在宿主机上启动一个进程,这个进程是容器网络命名空间发送DHCP的代理,并不是DHCP服务器
      • 服务端启动后会生成**/run/cni/dhcp.sock**,CRI操作网络时通过这个通道将请求发送到宿主机的DHCP进程上
    • host-local IP
      • 可以指定IP
      • 可以指定DNS配置
      • /var/lib/cni/networks/$NETWORK_NAME
    • 静态IP
  • Main插件:这类插件可以在容器内创建网络设备,或者关联网络设备到指定命名空间

    • bridge
    • ipvlan
    • macvlan
    • ptp:只创建一个veth对,相比bridge不会将veth的一端挂载到网桥上
    • host-device:将请求的设备从主机的网络名称空间移动到容器的网络名称空间。
  • Meta类型的插件:辅助功能类的插件

    • flannel:根据flanneld生成的**/run/flannel/subnet.env**,动态调用bridge插件来创建容器网络
    • tuning :用来在容器中修改内核参数和网卡的参数
    • portmap :用来在容器内创建iptable规则,比如进行SNAT、DNAT
    • bandwidth:基于Introduction to Linux Traffic Control的流量控制插件
    • sbr:基于源地址路由插件
    • firewall:防火墙配置插件
    • vrf:vrf接口插件,在容器中创建多个路由表