赵占旭的博客

CNI插件

本篇主要是简单了解CNI的插件,代码链接在此

概览

containernetworking团队维护了一些CNI插件。我们就叫他官方插件吧。接下来我们看看官方插件以及比较知名第三方插件都有哪些。

官方插件

Main: interface-creating

  • bridge: 创建一个bridge,给他添加host以及container。
  • ipvlan: 在容器中添加ipvlan的端口。
  • loopback: 将loopback接口的状态设置为up。
  • macvlan: 创建一个新的MAC地址,将所有目的mac对应的流量都转发到该容器。
  • ptp: 创建一个veth pair的接口。
  • vlan: 申请一个vlan设备。
  • host-device: 将一个已经存在的设备转移到容器。
  • win-bridge: win下的插件,功能类似上述的bridge。
  • win-overlay: 给container创建一个overlay端口。

IPAM: IP address allocation

  • dhcp: 在宿主机上运行一个daemon程序,代表container生成DHCP请求。
  • host-local: 维护一个本地的数据库来存储已经申请的IP。
  • static: 申请一个固定的IPv4/IPv6地址,他对debug很有用处。

Meta: other plugins

  • flannel: 生成和flannel配置对应的接口。
  • tuning: 调整已有接口的sysctl参数。
  • portmap: 一个基于iptables的portmapping插件。将端口从宿主机地址空间映射到容器。
  • bandwidth: 允许通过使用流量控制tbf(ingress/egress)限制宽带。
  • sbr: 用于为接口配置基于源的路由。
  • firewall: 一种防火墙插件,使用iptables或者firewalld添加规则,以允许金除容器的流量。

知名第三方插件

  • Calico: Calico使用Linux内核的转发和访问控制功能来提供数据平面的功能,并且优化性能。功能很强大,值得后续学习。
  • Weave: Weave Net创建一个虚拟网络,将多个容器连接起来,不论是同一台宿主机,跨宿主机或者跨机房和云提供商,都好像是连接在同一个交换上。
  • Contiv: 各种网络场景中使用的策略网络。
  • SR-IOV: 具有SR-IOV功能的网卡可以使用,PF由主机使用,每个VF都看作是一个物理网卡给容器使用,配置单独的MAC、VLAN和IP等信息。
  • Cilium: 基于ebpf和xdp的容器网络。
  • Infoblox: 容器的IP地址管理插件,是IPAM类的插件。
  • Multus: 将多个网络接口添加到容器。
  • Romana: Kubernetes的特定组件,支持网络策略的3层CNI插件。
  • CNI_Genie: 华为推出的通用插件,可以对接大部分的插件,并且同事存在,否则只能对接一种插件。
  • Nuage CNI: Kubernets支持的网络策略插件。
  • Silk: 为CloudFoundry提供的cni插件。
  • Linen: 供ovs使用的overlay网络。
  • Vhostuser: 支持ovs-dpdk和vpp的容器网络。
  • Amazon ECS CNI Plugins: AWS的ENIs(elastic network interfaces)专用插件。
  • Bonding CNI: 为高可用和聚合网络使用的插件。
  • ovn-kubernetes: 基于ovs和ovn的容器网络插件。
  • Juniper Contrail/TungstenFabric: 提供overlay SDN解决方案,多云网络,混合云网络等等,反正是一堆,比较复杂。
  • Knitter: 支持多种网络的插件,中兴推出的。
  • DANM: 电信级的网络管理插件,诺基亚推出的。
  • VMware NSX: 为VMware NSX提供的插件,可以实现L2/L3网络和L4/L7负载均衡,pod、node和集群级别的网络隔离以及零信任安全策略。
  • cni-route-override: META类型的插件,覆盖路由信息。
  • Terway: 阿里云的网络插件。

插件了解

我们每种类型都选一种进行了解,首先是内置的插件,第三方插件我们会单开一篇学习,因为比较复杂。

bridge插件

使用bridge插件,所有容器都会连接bridge,使用到veth pair虚拟网卡对,一端连接到容器,另一端连接到bridge。IP地址分配给容器一段的端口,网桥上也可以配置IP作为容器的网关。网络配置指定了使用的网桥名称,如果网桥不存在,插件将县创建一个。如果配置网关模式,则通过IPAM插件的gateway字段分配IP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#normal mode
{
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}

# L2-only
{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"ipam": {}
}
  • name (string, required): 网络名称。
  • type (string, required): 类型是bridge。
  • bridge (string, optional): bridge的名称。默认是cni0。
  • isGateway (boolean, optional): 为网桥分配IP地址。默认是false。
  • isDefaultGateway (boolean, optional): 将isGateway设置为true,并将分配的IP设置为默认路由。默认为false。
  • forceAddress (boolean, optional): 只是如果先前的值已经更改,是否应该设置新的IP地址。默认为false。
  • ipMasq (boolean, optional): 在主机上设置IP伪装,以接收来自该网络并发往其外部的流量。默认为false。
  • mtu (integer, optional): 将MTU显示设置为指定值。默认为内核选择值。
  • hairpinMode (boolean, optional): 网桥上的接口设置为hairpin模式。默认值是false。
  • ipam (dictionary, required): 用于网络的IPAM配置。对于L2-only网络,需要创建空的。
  • promiscMode (boolean, optional): 设置网桥的混杂模式。默认为false。
  • vlan (int, optional): 分配Vlan标签。默认为none。

cmdAdd操作

  • loadNetConf加载上述的配置文件。
    • json.Unmarshall解析数据。
  • 判定是L2模式还是L3模式。
  • 判定是否配置网关。
  • 判定hairpin和promisc模式。
  • setupBridge创建bridge。
    • 调用ensureBridge创建bridge,然后返回bridge信息。
      • 创建netlink.Bridge类型的bridge。
      • 然后调用netlink.LinkAdd添加bridge。
      • 如果配置了promisc,则调用netlink.SetPromiscOn来配置混杂模式。
      • 调用bridgeByName重新获取bridge信息。
      • 调用netlink.LinkSetUp启动bridge。
  • ns.GetNS获取当前的namespace。
  • setupVeth创建veth pair。
    • 在容器中调用ip.SetupVeth创建veth pair,并且将一端转移到宿主机。
    • 调用netlink.LinkByName重新获取宿主机端的veth,因为移动后有些信息变化。
    • 调用netlink.LinkSetMaster将宿主机端的veth连接到bridge。
    • 调用netlink.LinkSetHairpin设置hairpin模式。
    • 如果有vlan,则调用netlink.BridgeVlanAdd给宿主机的veth配置vlan id。
  • L3模式下需要做的操作。
    • ipam.ExecAdd调用IPAM插件,并且返回配置信息。
    • calcGateways收集每个IP的网关信息。
    • 在namespace中获取端口,并且调用ipam.ConfigureIface配置相应的IP,然后发送免费arp通告。
    • 网关模式下,进行一系列的操作。
      • 配置Vlan的话,调用ensureVlanInterface创建vlan接口,并且调用ensureAddr配置IP。
        • ensureVlanInterface主要是拿到br对应vlan的端口,然后调用setupVeth创建veth pair,然后返回veth pair对应的信息。
        • ensureAddr主要是调用netlink.AddrAdd添加IP地址,然后调用netlink.LinkSetHardwareAddr设置bridge的mac。
      • 配置网关地址的话,调用enableIPForward配置网络转发。
      • 配置IPMasq的话,调用ip.SetupIPMasq设置IP伪装。
  • 调用bridgeByName重新获取bridge信息,用于返回值,因为前面添加veth pair和配置ip之后,可能导致bridge信息发生变化。

cmdDel操作

  • loadNetConf加载上述的配置文件。
    • json.Unmarshall解析数据。
  • 判定是L2模式还是L3模式,L3模式调用ipam.ExecDel删除申请的IP
  • 在容器中执行ip.DelLinkByNameAddr,清除端口以及IP地址
  • 如果是L3模式,并且配置了IPMasq,那么调用ip.TeardownIPMasq删除IP伪装。

cmdCheck操作

  • loadNetConf加载上述的配置文件。
    • json.Unmarshall解析数据。
  • ns.GetNS获取对应的namespace。
  • ipam.ExecCheck调用ipam插件的check
  • version.ParsePrevResult将netconf内的RawPrevResult解析到PrevResult
  • current.NewResultFromResult对上面解析的result进行转换
  • 解析端口bridge以及namespace中的接口等信息
  • validateCniBrInterface检测bridge的合法性
    • validateInterface主要是判断bridge是否存在
      • netlink.LinkByName主要判断端口是否存在,并且返回link信息
    • 根据上面获取到的link信息判断是不是bridge
    • 判断配置中的mac信息是否和实际的mac地址匹配
    • 判断是否配置了promisc,如果配置了则判断promisc模式是否对
  • 判断result信息中的namespace和参数namespace是否一致
  • 在namespace中调用validateCniContainerInterface检测接口的合法性
    • validateInterface主要判断container一侧的veth是否存在,并且返回link信息
    • 根据link信息判断是否为veth类型
    • ip.GetVethPeerIfindex获取对端的link信息
    • 判断mac地址是否和实际mac地址匹配
  • validateCniVethInterface检测bridge上的veth和namespace中的veth是否一个pair
    • validateInterface主要是判断veth是否存在,并且返回link信息
    • 根据link信息判断是不是veth
    • ip.GetVethPeerIfindex获取对端的link信息
    • 各种方式判断是不是一个pair,具体看代码
  • 在namespace中调用ip.ValidateExpectedInterfaceIPs检测ip是否符合预期
  • 在namespace中调用ip.ValidateExpectedRoute检测route是否符合预期

host-local插件

host-local IPAM插件在限定的地址范围内分配IP地址,他将状态本地的文件系统上,从而确保单个主机上IP地址的唯一性。
分配器可以分配多个范围,并且支持多个不相交的子网的集合。在每个范围集内,分配策略都是松散循环的。

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
{
"ipam": {
"type": "host-local",
"ranges": [
[
{
"subnet": "10.10.0.0/16",
"rangeStart": "10.10.1.20",
"rangeEnd": "10.10.3.50",
"gateway": "10.10.0.254"
},
{
"subnet": "172.16.5.0/24"
}
],
[
{
"subnet": "3ffe:ffff:0:01ff::/64",
"rangeStart": "3ffe:ffff:0:01ff::0010",
"rangeEnd": "3ffe:ffff:0:01ff::0020"
}
]
],
"routes": [
{ "dst": "0.0.0.0/0" },
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
{ "dst": "3ffe:ffff:0:01ff::1/64" }
],
"dataDir": "/run/my-orchestrator/container-ipam-state"
}
}
  • type (string, required): “host-local”.
  • routes (string, optional): 需要添加到容器namespace的路由列表,如果没有gw配置,那么就使用gateway字段的值。
  • resolvConf (string, optional): resove.conf的路径。
  • dataDir (string, optional): 记录IP申请记录的路径。
  • ranges, (array, required, nonempty) 一组数组。
  • subnet (string, required): 要分配的IP段。
  • rangeStart (string, optional): IP分配的起始地址,默认从.2开始。
  • rangeEnd (string, optional): IP分配的终止地址,IPv4默认值是.254,IPv6默认值是.255。
  • gateway (string, optional): IP网段的网关。默认是.1。

cmdAdd操作

  • allocator.LoadIPAMConfig加载IPAM配置
  • 如果resolvConf配置了,则调用parseResolvConf解析配置
  • disk.New申请锁
  • 查看申请的IP是否在range范围内,如果找到的话,将ip和容器以及其内的端口挂钩,并且调用allocator.Get存储起来,然后存入result.IPs
  • 返回result信息

cmdDel操作

  • allocator.LoadIPAMConfig加载IPAM配置
  • disk.New申请锁
  • 调用ipAllocator.Release释放存储的IP信息

cmdCheck操作

  • allocator.LoadIPAMConfig加载IPAM配置
  • disk.New申请锁
  • store.FindByID查找IP是否能找到,找不到则检测失败,报错

bandwidth插件

插件使用了Linux的TC(traffic control)子系统。在ingress和egress流量上配置tbd(token bucket filter) qdisc(queuing discipline),导致出入流量都会受到影响。
犹如ingress的tc整形规则限制,此插件创建了一个ifb(Intermediate Functional Block)设备来重定向来自宿主机网卡的报文,然后将tc tbf应用于ifb设备。重定向到ifb设备的报文被整形并写上OUT到宿主机网卡。
该插件需要和其他插件一起使用时才有效。

以下是一个示例json配置列表,用于通过veth接口在host-> container之间创建ptp,流量由带宽插件决定:

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
{
"cniVersion": "0.3.1",
"name": "mynet",
"plugins": [
{
"type": "ptp",
"ipMasq": true,
"mtu": 512,
"ipam": {
"type": "host-local",
"subnet": "10.0.0.0/24"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
},
{
"name": "slowdown",
"type": "bandwidth",
"ingressRate": 123,
"ingressBurst": 456,
"egressRate": 123,
"egressBurst": 456
}
]
}
  • ingressRate: 入流量bps。
  • ingressBurst: 最大的bits。
  • egressRate: 出流量bps。
  • egressBurst: 最大的bits。

必须同时设置ingrssRate和ingressBurst才能限制入口宽带,缺一不可,egress同理。

cmdAdd操作

  • 调用parseConfig解析配置文件
    • 解析配置文件,从中提取带宽限制信息,并检测rate和burst的
    • 判断是否有prevResult信息,有的话也进行解析
  • 将配置文件的带宽限制信息存入result中
  • 获取当前容器的namespace
  • 调用getHostInterface获取宿主机的网口
  • 调用CreateIngressQdisc配置ingress相关的tc
    • 调用netlink.LinkByName获取网口
    • 调用createTBF配置策略
      • 对带宽限制信息进行转换成TC能识别的数据,存入netlink.Tbf。
      • 然后调用netlink.QdiscAdd下发规则
  • 调用getMTU获取端口的mtu
  • 调用getIfbDeviceName–>utils.MustFormatHashWithPrefix生成ifb设备的名字
  • 调用CreateIfb–>netlink.LinkAdd创建ifb设备
  • 调用netlink.LinkByName获取ifb设备的句柄
  • 将ifb设备信息存入result
  • 调用CreateEgressQdisc配置egress相关的tc
    • 获取ifb设备的句柄
    • 获取宿主机设备的句柄
    • 将策略信息存入netlink.Ingress
    • 调用netlink.QdiscAdd给宿主机的ingress下发规则
    • 给宿主机设备镜像到ifb设备的流量生成过滤规则
    • 调用netlink.FilterAdd设置过滤规则
    • 调用createTBF给ifb设备配置策略
      • 对带宽限制信息进行转换成TC能识别的数据,存入netlink.Tbf。
      • 然后调用netlink.QdiscAdd下发规则
  • 返回result

cmdDel操作

  • 调用parseConfig解析配置文件
    • 解析配置文件,从中提取带宽限制信息,并检测rate和burst的
    • 判断是否有prevResult信息,有的话也进行解析
  • 调用getIfbDeviceName–>utils.MustFormatHashWithPrefix生成ifb设备的名字
  • 调用TeardownIfb–>ip.DelLinkByNameAddr删除ifb设备

cmdCheck操作

  • 调用parseConfig解析配置文件
    • 解析配置文件,从中提取带宽限制信息,并检测rate和burst的
    • 判断是否有prevResult信息,有的话也进行解析
  • 将配置文件的带宽限制信息存入result中
  • 获取当前容器的namespace
  • 调用getHostInterface获取宿主机的网口
  • 解析带宽信息
  • 将上述信息进行转换,然后调用SafeQdiscList获取qdisc信息
  • 检测qdisc信息和配置文件中的配置是否一致
  • 调用getIfbDeviceName–>utils.MustFormatHashWithPrefix生成ifb设备的名字
  • 获取ifb设备的句柄,调用SafeQdiscList获取qdisc信息
  • 检测qdisc信息和配置文件中的配置是否一致

注意:所有文章非特别说明皆为原创。为保证信息与源同步,转载时请务必注明文章出处!谢谢合作 :-)

原始链接:http://zhaozhanxu.com/2019/10/21/K8S/2019-10-21-CNI-Plugins/

许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。