目录


文章目录

  • 目录
  • CNI
  • CNI 规范
  • CNI Plugin
  • Main 插件
  • Bridge 插件
  • HOST-DEVICE
  • MACVLAN
  • 第三方网络插件
  • CNI 使用的 I/O 接口虚拟化


CNI

Kubernetes 本身并没有实现自己的容器网络,而是借助 CNI 标准,通过插件化的方式来集成各种网络插件,实现集群内部网络相互通信。

CNI(Container Network Interface,容器网络的 API 接口),是 Google 和 CoreOS 联合定制的网络标准,它是在 RKT 网络提议的基础上发展起来的,综合考虑了灵活性、扩展性、IP 分配、多网卡等因素。Kubernetes 网络的发展方向是希望通过 Plugin 的方式来集成不同的网络方案, CNI 就是这一努力的结果。

CNI 旨在为容器平台提供网络的标准化,为解决容器网络连接和容器销毁时的资源释放,提供了一套框架。所以 CNI 可以支持大量不同的网络模式,并且容易实现。不同的容器平台(e.g. Kubernetes、Mesos 和 RKT)能够通过相同的接口调用不同的网络组件。

singularity 容器 网络 容器网络插件_singularity 容器 网络

CNI 规范

CNI 规范的几点原则:

  1. CNI Plugin 负责连接容器(Linux network namespace)。
  2. CNI 的网络定义以 JSON 的格式存储。
  3. 有关网络的配置通过 stdin(Linux 标准输入)的方式传递给 CNI Plugin,其他的参数通过 ENV(环境变量)的方式传递。
  4. CNI 插件是以 exec(可执行文件)的方式实现的。

CNI 规范定义的核心接口:

  1. ADD:将容器添加到网络;
  2. DEL:从网络中删除一个容器;
  3. CHECK:检查容器的网络是否符合预期等;
  4. etc…

CNI 对饭定义了两个组件,包括:

  1. Container Management System
  2. Network Plugin:CNI 接收到的具体请求都是由 Plugin 来完成的,例如:创建容器网络空间(network namespace)、把网络接口(interface)放到对应的网络空间、给网络接口分配 IP 等。

CNI Plugin

从 Network Plugin 实现的功能可以把 CNI Plugin 分为 5 类:

  1. Main 插件:创建具体的网络设备。有以下类型:
    2. bridge:网桥设备,连接 Container 和 Host;
    3. ipvlan:为容器增加 ipvlan 网卡;
    4. loopback:回环设备;
    5. macvlan:为容器创建一个 MAC 地址;
    6. ptp:创建一对 Veth Pair;
    7. vlan:分配一个 vlan 设备;
    8. host-device:将已存在的设备移入容器内。
  2. IPAM 插件:负责分配 IP 地址。有以下类型:
  1. dhcp:容器向 DHCP 服务器发起请求,给 Pod 发放或回收 IP 地址;
  2. host-local:使用预先配置的 IP 地址段来进行分配;
  3. static:为容器分配一个静态 IPv4/IPv6 地址,主要用于 Debug 场景。
  1. META 插件:其他功能的插件。有以下类型
  1. tuning:通过 sysctl 调整网络设备参数;
  2. portmap:通过 iptables 配置端口映射;
  3. bandwidth:使用 Token Bucket Filter 来限流;
  4. sbr:为网卡设置 source based routing;
  5. firewall:通过 iptables 给容器网络的进出流量进行限制。
  1. Windows 插件:专门用于 Windows 平台的 CNI 插件。
  1. win-bridge
  2. win-overlay 网络插件。
  1. 第三方网络插件:第三方开源的网络插件众多,每个组件都有各自的优点及适应的场景,难以形成统一的标准组件,常用有 Flannel、Calico、Cilium、OVN 网络插件。

大部分的 CN I插件功能设计上遵守功能职责单一原则,比如:

  • Bridge 插件负责网桥的相关配置;
  • Firewall 插件负责防火墙相关配置;
  • Portmap 插件负责端口映射相关配置。

因此,当网络设置比较复杂时,通常通过调用多个插件来完成。CNI 通过链式调(NetworkConfigList)用多个插件,将多个插件组合起来按顺序调用。

例如:Flannel CNI 插件配置 POD 网络时的链式调用。

singularity 容器 网络 容器网络插件_kubernetes_02

Main 插件

Bridge 插件

  1. 下载源码:
cd cni/
curl -O -L https://github.com/containernetworking/cni/releases/download/v0.4.0/cni-amd64-v0.4.0.tgz
tar -xzvf cni-amd64-v0.4.0.tgz
  1. 创建 Network Namespace:
ip netns add 1234567890
  1. 新增 CNI Bridge Plugin 的配置文件:
cat > mybridge.conf <<"EOF"
{
    "cniVersion": "0.2.0",          # CNI 规范的版本
    "name": "mybridge",             # 网络的名字
    "type": "bridge",               # 使用 CNI 的 Bridge Plugin
    "bridge": "cni_bridge0",
    "isGateway": true,              # 如果是 true,为网桥分配 IP 地址,以便连接到它的容器可以将其作为网关。
    "ipMasq": true,                 # 在插件支持的情况下,设置 IP 伪装。
    "hairpinMode":true,             # 让网络设备能够让数据包从一个端口发进来一个端口发出去。
    "ipam": {
        "type": "host-local",       # IPAM 可执行文件的名字。
        "subnet": "10.15.20.0/24",  # 要分配给容器的子网。
        "routes": [                 # 子网路由。
            { "dst": "0.0.0.0/0" },
            { "dst": "1.1.1.1/32", "gw":"10.15.20.1"}
        ]
    }
}
EOF
  1. 将 Pod Network 加入到 Network Namespace 中:
$ cd cni
$ CNI_COMMAND=ADD
$ CNI_CONTAINERID=1234567890
$ CNI_NETNS=/var/run/netns/1234567890
$ CNI_IFNAME=eth12
$ CNI_PATH=`pwd`
$ .cni/bin/bridge < mybridge.conf
2020/03/02 22:14:57 Error retriving last reserved ip: Failed to retrieve last reserved ip: open /var/lib/cni/networks/mybridge/last_reserved_ip: no such file or directory
{
    "ip4": {
        "ip": "10.15.20.2/24",
        "gateway": "10.15.20.1",
        "routes": [
            {
                "dst": "0.0.0.0/0"
            },
            {
                "dst": "1.1.1.1/32",
                "gw": "10.15.20.1"
            }
        ]
    },
    "dns": {}
}
  1. 查看 Network Namespace 的网络配置:
$ ip netns exec 1234567890 ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth12@if1137099: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 0a:58:0a:0f:14:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.15.20.2/24 scope global eth12
       valid_lft forever preferred_lft forever
    inet6 fe80::34da:9fff:febe:f332/64 scope link
       valid_lft forever preferred_lft forever

$ ip netns exec 1234567890 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.15.20.1      0.0.0.0         UG    0      0        0 eth12
1.1.1.1         10.15.20.1      255.255.255.255 UGH   0      0        0 eth12
10.15.20.0      0.0.0.0         255.255.255.0   U     0      0        0 eth12

$ ip netns exec 1234567890 ifconfig
eth12: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.15.20.2  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::34da:9fff:febe:f332  prefixlen 64  scopeid 0x20<link>
        ether 0a:58:0a:0f:14:02  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 9  bytes 738 (738.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

HOST-DEVICE

host-device CNI 的作用就是把 Physical Network Interface 直接交给 Pod 使用。

实现很简单,做了 2 件事情:

  1. 收到 ADD 命令时,bin/host-device 根据命令参数,将网卡移入到指定的 Network Namespace。
  2. 收到 DEL 命令时,bin/host-device 根据命令参数,将网卡从指定的 Network Namespace 移出到 Root Namespace。

原理也比较简单,使用下述指令就可以做到,将 dev “移动” 到指定的 Network Namespace 中:

$ ip a
...
3: ens8: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 52:54:00:5e:1f:f5 brd ff:ff:ff:ff:ff:ff

$ ip netns add test

$ ip link set dev ens8 netns test

$ ip netns exec test ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: ens8: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 52:54:00:5e:1f:f5 brd ff:ff:ff:ff:ff:ff
  • 新增 host-device CNI 的配置文件:
cat > myhost-device.conf <<"EOF"
{
	"cniVersion": "0.3.1",
	"type": "host-device",
	"device": "ens8",
	"name": "host"
}
EOF
  • 使用 host-device CNI 将 host-device 加到 Network Namespace 中:
export CNI_COMMAND=ADD
export CNI_NETNS=/var/run/netns/test
export CNI_IFNAME=eth0
export CNI_ARGS
export CNI_PATH=/opt/cni/bin
export CNI_CONTAINERID="aaa" 

$ /opt/cni/bin/host-device < myhost-device.conf

$ ip netns exec test ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: eth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 52:54:00:5e:1f:f5 brd ff:ff:ff:ff:ff:ff

MACVLAN

MACVLAN 是 Linux Kernel 比较新的特性,允许在主机的一个网络接口上配置多个虚拟的网络接口,这些网络 interface 有自己独立的 MAC 地址,也可以配置上 IP 地址进行通信。macvlan 下的虚拟机或者容器网络和主机在同一个网段中,共享同一个广播域。macvlan 和 bridge 比较相似,但因为它省去了 bridge 的存在,所以配置和调试起来比较简单,而且效率也相对高。除此之外,macvlan 自身也完美支持 VLAN。

基于 Linux Kernel MACVLAN feature,将 VNI 子接口交给 Pod 使用,作为 Pod Network Namespace 的一个 Interface。

  1. 下载 CNI:https://github.com/containernetworking/plugins/releases
  2. 把 CNI 的 binary 放置到每个 Node 的 /opt/cni/bin/。
  3. 为每个 Node 配置 kubelet:
$ vi /etc/kubernetes/kubelet
...
KUBELET_ARGS="--network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin"
  1. MACVLAN CNI 的配置文件:
{
    "name": "macvlannet",
    "type": "macvlan",
    "master": "ens33",
    "mode": "vepa",
    "isGateway": true,
    "ipMasq": false,
    "ipam": {
        "type": "host-local",
        "subnet": "192.168.166.0/24",
        "rangeStart": "192.168.166.21",
        "rangeEnd": "192.168.166.29",
        "gateway": "192.168.166.2",
        "routes": [
            { "dst": "0.0.0.0/0" }
        ]
    }
}

第三方网络插件

CNI 第三方网络插件通常有 3 种实现模式:

  1. Overlay:靠隧道打通,不依赖底层网络;
  2. Underlay:靠底层网络打通,强依赖底层网络;
  3. 路由:靠路由打通,部分依赖底层网络;

常见的第三方网络插件:

  • Calico(性能好、灵活性最强,目前的企业级主流):是一个基于 BGP 路由协议的纯 L3 的数据中心网络方案(不需要 Overlay),提供简单,可扩展的网络。除了可扩展的网络, Calico 还提供策略隔离。
  • Flannel(最成熟、最简单的选择):基于 Linux TUN/TAP,使用 UDP 封装 IP 数据包的方式来创建 Overlay 网络,并借助 etcd 来维护网络资源的分配情况,是一种简单易用的 Overlay 网络方案。
  • Weave:支持多主机容器网络,可以跨越不同的云网络配置。独有的功能,是对整个网络的简单加密,会增加网络开销。
  • Cilium:是一个开源软件,基于 Linux Kernel BPF 技术,可以在 Linux Kernel 内部动态地插入具有安全性、可见性的网络控制逻辑。
  • kopeio-networking:是专为 Kubernetes 而设计的网络方案,充分利用了 Kubernetes API,因此更简单,更可靠。
  • kube-router:也是专为 Kubernetes 打造的专用网络解决方案,旨在提供操作简单性和性能。

singularity 容器 网络 容器网络插件_kubernetes_03


singularity 容器 网络 容器网络插件_网络_04

CNI 插件项目 Forks 数量比较:

singularity 容器 网络 容器网络插件_云原生_05

CNI 插件项目 10Gbit 网络下的 CPU 消耗比较:

singularity 容器 网络 容器网络插件_kubernetes_06

CNI 使用的 I/O 接口虚拟化

根据 CNI 插件不同的实现方式,也会使用到不同的 I/O 接口虚拟化技术。

  1. Veth-Pair:创建一个 Veth-Pair 对,两端分别接入到 Host root namespace(Linux Bridge / Open vSwitch)和 Container network namespace。Container 内发出的网络数据包,通过 vSwitch 进入到 Host OS Kernel Network Stack。
  2. Multi-Plexing(多路复用):使用一个 MACVLAN / IPVLAN 中间网络设备,虚拟出多个 Virtual NIC 接入到 Container,根据 MAC/IP 地址来区分数据报文如何转发到具体的容器。
  3. Hardware switching(硬件交换):SR-IOV 内部实现了一个 Hardware Switch,通过 VFs 的方式接入到每个 Pods。

singularity 容器 网络 容器网络插件_网络_07