k8s CNI网络插件

发布时间 2023-08-14 16:01:49作者: 小吉猫

CNI网络插件基础

kubenet是一个非常基础、简单的网络插件,它本身并未实现任何跨节点网络和网络策略一类更高级的功能,且仅适用于Linux系统,于是,Kubernetes试图寻求一个更开放的网络插件接口标准来替代它。分别由Docker与CoreOS设计的CNM(Container Network Model)和CNI是两个主流的竞争模型,但CNM在设计上做了很多与Kubernetes不兼容的假设,而CNI却有着与Kubernetes非常一致的设计哲学,它远比CNM简单,不需要守护进程,并且能够跨多个容器运行时平台。于是,CNM因“专为Docker容器引擎设计且很难分离”而落选,而CNI就成了目前Kubernetes系统上标准的网络插件接口规范。目前,绝大多数为Kubernetes解决Pod网络通信问题的插件都是遵循CNI规范的实现。
CNI是容器引擎与遵循该规范网络插件的中间层,专用于为容器配置网络子系统,目前由RKT、Kubernetes、OpenShift和Apache Mesos等相关的容器运行时环境所运行。
通常,遵循CNI规范的网络插件是一个可执行程序文件,它们可由容器编排系统(例如Kubernetes等)调用,负责向容器的网络名称空间插入一个网络接口并在宿主机上执行必要的任务以完成虚拟网络配置,因而通常被称为网络管理插件,即NetPlugin。随后,NetPlugin还需要借助IPAM插件为容器的网络接口分配IP地址,这意味着CNI允许将核心网络管理功能与IP地址分配等功能相分离,并通过插件组合的方式堆叠出一个完整的解决方案。

CNI插件分类

CNI具有很强的扩展性和灵活性,例如,如果用户对某个插件有特殊的需求,可以通过输入中的args和环境变量CNI_ARGS传递,然后在插件中实现自定义的功能,这大大增加了它的扩展性。CNI插件把main和ipam分开,为用户提供了自由组合它们的机制,甚至一个CNI插件也可以直接调用另外一个插件。

main类别

bridge :创建一个桥,将主机和容器添加到其中
ipvlan :添加 ipvlan容器中的接口
macvlan :创建一个新的MAC地址,将所有流量转发到容器
ptp :创建 veth 对
host-device :将现有设备移动到容器中
vlan :创建一个脱离master的vlan接口

ipam类别

dhcp :在主机上运行守护进程以代表容器发出 DHCP 请求
host-local :维护已分配 IP 的本地数据库
static :为容器分配静态 IPv4/IPv6 地址

meta类别

tuning :更改现有接口的 sysctl 参数
portmap :基于 iptables 的端口映射插件。将主机地址空间的端口映射到容器
bandwidth :允许通过使用流量控制 tbf(入口/出口)来限制带宽
sbr :为接口配置基于源的路由的插件(从该接口链接)
firewall :一个防火墙插件,使用 iptables 或firewalld 添加规则以允许流量进出容器

配置CNI插件

网络配置格式

CNI 为管理员定义了一种网络配置格式。它包含容器运行时以及要使用的插件的指令。在插件执行时,此配置格式由运行时解释并转换为要传递给插件的形式。

一般来说,网络配置是静态的。从概念上讲,它可以被认为是“在磁盘上”,尽管 CNI 规范实际上并不要求这样做。

配置格式

网络配置由具有以下键的 JSON 对象组成:

cniVersion(字符串):此配置列表和所有单独配置符合的 CNI 规范语义版本 2.0 。目前为“1.1.0”
name(字符串):网络名称。这在主机(或其他管理域)上的所有网络配置中应该是唯一的。必须以字母数字字符开头,可选后跟一个或多个字母数字字符、下划线、点 (.) 或连字符 (-) 的任意组合。
disableCheck(布尔值):true或者false。如果disableCheck是true,则运行时不得调用CHECK此网络配置列表。这允许管理员防止CHECK已知插件组合返回虚假错误。
plugins(list):CNI插件及其配置的列表,即插件配置对象的列表。

插件配置对象

插件配置对象可能包含除此处定义的字段之外的其他字段。运行时必须将这些字段原样传递给插件。

配置示例

{
  "cniVersion": "1.1.0",
  "name": "dbnet",
  "plugins": [
    {
      "type": "bridge",
      // plugin specific parameters
      "bridge": "cni0",
      "keyA": ["some more", "plugin specific", "configuration"],
      
      "ipam": {
        "type": "host-local",
        // ipam specific
        "subnet": "10.1.0.0/16",
        "gateway": "10.1.0.1",
        "routes": [
            {"dst": "0.0.0.0/0"}
        ]
      },
      "dns": {
        "nameservers": [ "10.1.0.1" ]
      }
    },
    {
      "type": "tuning",
      "capabilities": {
        "mac": true
      },
      "sysctl": {
        "net.core.somaxconn": "500"
      }
    },
    {
        "type": "portmap",
        "capabilities": {"portMappings": true}
    }
  ]
}

执行协议

CNI 协议基于容器运行时调用的二进制文件的执行。CNI 定义了插件二进制文件和运行时之间的协议。

CNI 插件负责以某种方式配置容器的网络接口。插件分为两大类:

  “接口”插件,它在容器内创建一个网络接口并确保其具有连接性。
  “链接”插件,调整已创建接口的配置(但可能需要创建更多接口才能执行此操作)。
  
运行时通过环境变量和配置将参数传递给插件。它通过标准输入提供配置。如果成功,该插件将在 stdout 上返回结果;如果操作失败,则在 stderr 上返回错误。配置和结果以 JSON 进行编码。

参数定义特定于调用的设置,而配置(除一些例外)对于任何给定网络都是相同的。

运行时必须在运行时的网络域中执行插件。(在大多数情况下,这意味着根网络命名空间 / dom0)。

参数

协议参数通过操作系统环境变量传递给插件。

  CNI_COMMAND:表示所需的操作;ADD, DEL, CHECK, GC, 或VERSION。
  CNI_CONTAINERID:集装箱ID。容器的唯一纯文本标识符,由运行时分配。不得为空。必须以字母数字字符开头,可选后跟一个或多个字母数字字符、下划线 ()、点 (.) 或连字符 (-) 的任意组合。
  CNI_NETNS:对容器的“隔离域”的引用。如果使用网络命名空间,则为网络命名空间的路径(例如/run/netns/[nsname])
  CNI_IFNAME:在容器内创建的接口名称;如果插件无法使用此接口名称,则它必须返回错误。
  CNI_ARGS:用户在调用时传入的额外参数。以分号分隔的字母数字键值对;例如,“FOO=BAR;ABC=123”
  CNI_PATH:搜索 CNI 插件可执行文件的路径列表。路径由操作系统特定的列表分隔符分隔;例如 Linux 上的 ':' 和在 Windows 上的';'。

CNI管理操作

CNI 定义了 5 种操作:ADD、DEL、CHECK、GC和VERSION。这些通过环境变量传递给插件CNI_COMMAND。

ADD:将容器添加到网络,或应用修改
DEL:从网络中删除容器,或取消应用修改
CHECK:检查容器的网络是否符合预期
VERSION:探针插件版本支持
GC:清理所有陈旧资源

网络配置的执行流程

网络配置运行时可能希望添加、删除或检查容器中的网络配置。这会相应地产生一系列插件ADD、DELETE或CHECK执行。

生命周期

在调用任何插件之前,容器运行时必须为容器创建一个新的网络命名空间。
容器运行时不得对同一容器调用并行操作,但允许对不同容器调用并行操作。这包括跨多个附件。
  例外:运行时必须以独占方式执行gc或添加和删除。运行时必须确保在执行gc之前没有任何添加或删除操作正在进行,并且必须等待gc完成才能发出新的添加或删除命令。
插件必须处理跨不同容器并发执行的情况。如有必要,他们必须对共享资源(例如IPAM 数据库)实施锁定。
容器运行时必须确保add最终后面跟着相应的delete。唯一的例外是发生灾难性故障,例如节点丢失。即使添加失败,仍必须执行删除。
删除后可能会进行其他删除。
网络配置不应在添加和删除之间更改。
附件之间的网络配置不应更改。
容器运行时负责清理容器的网络命名空间。

插件参数

虽然网络配置不应在附件之间更改,但容器运行时会为每个附件提供某些参数。他们是:

  Container ID:容器的唯一明文标识符,由运行时分配。不得为空。必须以字母数字字符开头,可选后跟一个或多个字母数字字符、下划线 ()、点 (.) 或连字符 (-) 的任意组合。执行期间,始终设置为 CNI_CONTAINERID参数。
  Namespace:对容器“隔离域”的引用。如果使用网络命名空间,则为网络命名空间的路径(例如/run/netns/[nsname])。执行期间,始终设置为CNI_NETNS参数。
  容器接口名称:在容器内创建的接口的名称。执行期间,始终设置为CNI_IFNAME参数。
  通用参数:与特定附件相关的额外参数,采用键值字符串对的形式。执行期间,始终设置为CNI_ARGS参数。
  其它参数:这些也是键值对。键是字符串,而值是任何 JSON 可序列化类型。键和值是按约定定义的。
此外,必须向运行时提供搜索 CNI 插件的路径列表。这也必须在执行期间通过环境变量提供给插件CNI_PATH。

添加插件

对于plugins网络配置密钥中定义的每个配置,

1. 查找type字段中指定的可执行文件。如果不存在,则这是一个错误。
2. 从插件配置中派生请求配置,具有以下参数:
     如果这是列表中的第一个插件,则不提供先前的结果,
     对于所有附加插件,先前的结果是先前插件的结果。
3. 执行插件二进制文件,带有CNI_COMMAND=ADD. 提供上面定义的参数作为环境变量。通过标准输入提供派生配置。
4. 如果插件返回错误,则停止执行并将错误返回给调用者。

运行时必须持久存储最终插件返回的结果,因为检查和删除操作需要它。

删除插件

删除网络附件与添加网络附件非常相似,但有一些关键区别:

  插件列表按相反顺序执行
  先前提供的结果始终是加法运算的最终结果。
对于plugins网络配置键中定义的每个插件,按相反的顺序,

1. 查找type字段中指定的可执行文件。如果不存在,则这是一个错误。
2. 从插件配置中派生请求配置,并使用初始添加操作的先前结果。
3. 执行插件二进制文件,带有CNI_COMMAND=DEL. 提供上面定义的参数作为环境变量。通过标准输入提供派生配置。
4. 如果插件返回错误,则停止执行并将错误返回给调用者。

如果所有插件都返回成功,则将成功返回给调用者。

检查插件

运行时还可能要求每个插件确认给定的附件仍然有效。运行时必须使用与添加操作相同的附件参数。
检查与添加类似,但有两个例外:

  先前提供的结果始终是加法运算的最终结果。
  如果网络配置定义了disableCheck,则始终将成功返回给调用者。
对于plugins网络配置密钥中定义的每个插件,

1. 查找type字段中指定的可执行文件。如果不存在,则这是一个错误。
2. 从插件配置中派生请求配置,并使用初始添加操作的先前结果。
3. 执行插件二进制文件,带有CNI_COMMAND=CHECK. 提供上面定义的参数作为环境变量。通过标准输入提供派生配置。
4. 如果插件返回错误,则停止执行并将错误返回给调用者。

如果所有插件都返回成功,则将成功返回给调用者。

gc回收

运行时还可能要求网络配置中的每个插件通过GC命令清理任何陈旧资源。

对配置进行垃圾收集时,没有AttachmentParameters。
对于plugins网络配置密钥中定义的每个插件,

1. 查找type字段中指定的可执行文件。如果不存在,则这是一个错误。
2. 从插件配置中派生请求配置。
3. 执行插件二进制文件,带有CNI_COMMAND=GC. 通过标准输入提供派生配置。
4. 如果插件返回错误,则继续执行,将所有错误返回给调用者。

如果所有插件都返回成功,则将成功返回给调用者。

插件委托

有一些操作,无论出于何种原因,都无法合理地实现为离散链式插件。相反,CNI 插件可能希望将某些功能委托给另一个插件。一个常见的例子是 IP 地址管理。

作为其操作的一部分,CNI 插件应为接口分配(并维护)IP 地址,并安装与该接口相关的任何必要路由。这给CNI插件带来了很大的灵活性,但也给它带来了很大的负担。许多 CNI 插件需要具有相同的代码来支持用户可能需要的多种 IP 管理方案(例如 dhcp、host-local)。CNI 插件可以选择将 IP 管理委托给另一个插件。

为了减轻负担并使IP管理策略与CNI插件类型正交,我们定义了第三种类型的插件——IP地址管理插件(IPAM插件),以及插件将功能委托给其他插件的协议。

然而,在执行过程中的适当时刻调用 IPAM 插件是 CNI 插件(而不是运行时)的责任。IPAM 插件必须确定接口 IP/子网、网关和路由,并将此信息返回给“主”插件以应用。IPAM 插件可以通过协议(例如 dhcp)、本地文件系统上存储的数据、网络配置文件的“ipam”部分等获取信息。

委托插件协议

与 CNI 插件一样,委托插件是通过运行可执行文件来调用的。在预定义的路径列表中搜索可执行文件,通过 指示给 CNI 插件CNI_PATH。委托插件必须接收传递给 CNI 插件的所有相同环境变量。就像 CNI 插件一样,委托插件通过 stdin 接收网络配置并通过 stdout 输出结果。

为委托插件提供了传递给“上层”插件的完整网络配置。换句话说,在 IPAM 情况下,不仅仅是ipam配置部分。

成功由零返回代码和成功结果类型输出到标准输出来指示。

委托插件执行流程

当插件执行委托插件时,它应该:

通过搜索环境变量中提供的目录来查找插件二进制文件CNI_PATH。
使用收到的相同环境和配置执行该插件。
确保委托插件的 stderr 输出到调用插件的 stderr。
如果使用CNI_COMMAND=CHECK、DEL或gc执行插件,则它还必须执行任何委托的插件。如果任何委托插件返回错误,则上层插件应返回错误。

如果在ADD上,委托的插件失败,则“上层”插件应在返回失败之前使用DEL再次执行。

CNI插件

CNI规范负责连接容器管理系统和网络插件两类组件,它们之间通过JSON格式的文件进行通信,以完成容器网络管理。具体的管理操作均由插件来实现,包括创建容器netns(网络名称空间)、关联网络接口到对应的netns,以及给网络接口分配IP等。CNI的基本思想是为容器运行时环境在创建容器时,先创建好netns,然后调用CNI插件为这个netns配置网络,而后启动容器内的进程。
CNI本身只是规范,付诸生产还需要有特定的实现。如前所述,目前CNI提供的插件分为main、ipam和meta,各类别中都有不少内置实现。另外,可用的第三方实现的CNI插件也有数十种之多,它们多数都是用于提供NetPlugin功能,隶属main插件类型,主要用于配置容器接口和容器网络,这其中,也有不少实现能够支持Kubernetes的网络策略。

Flannel

由CoreOS提供的CNI网络插件,也是最简单、最受欢迎的网络插件;它使用VXLAN或UDP协议封装IP报文来创建Overlay网络,并借助etcd维护网络的分配信息,同一节点上的Pod间通信可基于本地虚拟网桥(cni0)进行,而跨节点的Pod间通信则要由flanneld守护进程封装隧道协议报文后,通过查询etcd路由到目的地;Flannel也支持host-gw路由模型。

Calico

同Flannel一样广为流行的CNI网络插件,以灵活、良好的性能和网络策略所著称。Calico是路由型CNI网络插件,它在每台机器上运行一个vRouter,并基于BGP路由协议在节点之间路由数据包。Calico支持网络策略,它借助iptables实现访问控制功能。另外,Calico也支持IPIP型的Overlay网络。

Canal

由Flannel和Calico联合发布的一款统一网络插件,它试图将二者的功能集成在一起,由前者提供CNI网络插件,由后者提供网络策略。

Weave Net

由Weaveworks提供的CNI网络插件,支持网络策略。WeaveNet需要在每个节点上部署vRouter路由组件以构建起一个网格化的TCP连接,并通过Gossip协议来同步控制信息。在数据平面上,WeaveNet通过UDP封装实现L2隧道报文,报文封装支持两种模式:一种是运行在用户空间的sleeve(套筒)模式,另一种是运行在内核空间的fastpath(快速路径)模式,当网络拓扑不适合fastpath模式时,Weave将自动切换至sleeve模式。

Multus 

多CNI插件,实现了CNI规范的所有参考类插件(例如Flannel、MAC VLAN、IPVLAN和DHCP等)和第三方插件(例如Calico、Weave和Contiv等),也支持Kubernetes中的SR-IOV、DPDK、OVS-DPDK和VPP工作负载,以及Kubernetes中的云原生应用程序和基于NFV的应用程序,是需要为Pod创建多网络接口时的常用选择。

Antrea

一款致力于成为Kubernetes原生网络解决方案的CNI网络插件,它使用OpenvSwitch构建数据平面,基于Overlay网络模型完成Pod间的报文交换,支持网络策略,支持使用IPSec ESP加密GRE隧道流量。

CNI插件选型

尽管人们倾向于把Overlay网络作为解决跨主机容器网络的主要解决方案,但可用的容器网络插件在功能和类型上差别巨大:某些解决方案与容器引擎无关,而也有些解决方案作用后,容易被特定的供应商或引擎锁定;有些专注于简单易用,而另一些的主要目标则是更丰富的功能特性等,至于哪一个解决方案更适用,通常取决于应用程序自身的需求,例如性能需求、负载位置编排机制等。通常来说,选择网络插件时应该基于底层系统环境限制、容器网络的功能需求和性能需求3个重要的评估标准来衡量插件的适用性。

底层系统环境限制

公有云环境多有自己专有的实现,例如Google GCE、Azure CNI、AWS VPC CNI和Aliyun Terway等,它们通常是相应环境上较佳的选择。若虚拟化环境限制较多,除Overlay网络模型别无选择,则可用的方案有Flannel VXLAN、Calico IPIP、Weave和Antrea等。物理机环境几乎支持任何类型的网络插件,此时一般应该选择性能较好的Calico BGP、Flannel host-gw或DAMM IP VLAN等。

容器网络功能需求

支持NetworkPolicy的解决方案以Calico、WeaveNet和Antrea为代表,而且后两个支持节点到节点间的通信加密。而大量Pod需要与集群外部资源互联互通时,应该选择Underlay网络模型一类的解决方案。

容器网络性能需求

Overlay网络中的协议报文有隧道开销,性能略差,而Underlay网络则几乎不存这方面的问题,但Overlay或Underlay路由模型的网络插件支持较快的Pod创建速度,而Underlay模型中的IPVLAN或MAC VLAN模式则较慢。

CNI main插件实现分类

为了能够满足分布式Pod通信模型中要求的所有Pod必须在同一平面网络中的要求,目前常用的实现方案有Overlay网络(Overlay Network)和Underlay网络(Underlay Network)两类。
其实,Overlay网络的底层网络也就是承载网络,因此,Underlay网络的解决方案也就是一类非借助隧道协议而构建的容器通信网络。相较于承载网络,Overlay网络由于存在额外的隧道报文封装,会存在一定程度的性能开销。然而,用户在不少场景中可能会希望创建跨越多个L2或L3的逻辑网络子网,这就只能借助Overlay封装协议实现。

Overlay

网络借助VXLAN、UDP、IPIP或GRE等隧道协议,通过隧道协议报文封装Pod间的通信报文(IP报文或以太网帧)来构建虚拟网络

Underlay

网络通常使用direct routing(直接路由)技术在Pod的各子网间路由Pod的IP报文,或使用Bridge、MAC VLAN或IP VLAN等技术直接将容器暴露给外部网络。

CNI 创建pod网络接口设备实现方式

为Pod配置网络接口是main Plugin的核心功能之一,但不同的容器虚拟化网络解决方案中,为Pod的网络名称空间创建虚拟接口设备的方式也会有所不同,目前,较为主流的实现方式有veth(虚拟以太网)设备、多路复用及硬件交换3种

注意
IaaS公有云环境中的VPS和云主机内部使用的已经是虚拟网卡,它们通常无法支持硬件交换功能,甚至是对IP VLAN或MAC VLAN等也多有限制,但云服务商通常会提供专有的VPC解决方案。
一般说来,基于VXLAN Overlay网络的虚拟容器网络中,Main Plugin会使用虚拟以太网内核模块为每个Pod创建一对虚拟网卡;基于MAC VLAN/IP VLAN Underlay网络的虚拟容器网络中,Main Plugin会基于多路复用模式中的MAC VLAN/IP VLAN内核模块为每个Pod创建虚拟网络接口设备;而基于IP报文路由技术的Underlay网络中,各Pod接口设备通常也是借助veth设备完成。

veth设备

创建一个网桥,并为每个容器创建一对虚拟以太网接口,一个接入容器内部,另一个留置于根名称空间内添加为Linux内核桥接功能或OpenvSwitch(OVS)网桥的从设备。

多路复用

多路复用可以由一个中间网络设备组成,它暴露多个虚拟接口,使用数据包转发规则来控制每个数据包转到的目标接口;MAC VLAN技术为每个虚拟接口配置一个MAC地址并基于此地址完成二层报文收发,IP VLAN则是分配一个IP地址并共享单个MAC,并根据目标IP完成容器报文转发。

硬件交换

现今市面上有相当数量的NIC都支持SR-IOV(单根I/O虚拟化),SR-IOV是创建虚拟设备的一种实现方式,每个虚拟设备自身表现为一个独立的PCI设备,并有着自己的VLAN及硬件强制关联的QoS;SR-IOV提供了接近硬件级别的性能。

参考文档

 https://github.com/containernetworking/cni

https://www.cni.dev/docs/