目录
文章目录
- 目录
- 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)能够通过相同的接口调用不同的网络组件。
CNI 规范
CNI 规范的几点原则:
- CNI Plugin 负责连接容器(Linux network namespace)。
- CNI 的网络定义以 JSON 的格式存储。
- 有关网络的配置通过 stdin(Linux 标准输入)的方式传递给 CNI Plugin,其他的参数通过 ENV(环境变量)的方式传递。
- CNI 插件是以 exec(可执行文件)的方式实现的。
CNI 规范定义的核心接口:
- ADD:将容器添加到网络;
- DEL:从网络中删除一个容器;
- CHECK:检查容器的网络是否符合预期等;
- etc…
CNI 对饭定义了两个组件,包括:
- Container Management System
- Network Plugin:CNI 接收到的具体请求都是由 Plugin 来完成的,例如:创建容器网络空间(network namespace)、把网络接口(interface)放到对应的网络空间、给网络接口分配 IP 等。
CNI Plugin
从 Network Plugin 实现的功能可以把 CNI Plugin 分为 5 类:
- Main 插件:创建具体的网络设备。有以下类型:
2. bridge:网桥设备,连接 Container 和 Host;
3. ipvlan:为容器增加 ipvlan 网卡;
4. loopback:回环设备;
5. macvlan:为容器创建一个 MAC 地址;
6. ptp:创建一对 Veth Pair;
7. vlan:分配一个 vlan 设备;
8. host-device:将已存在的设备移入容器内。 - IPAM 插件:负责分配 IP 地址。有以下类型:
- dhcp:容器向 DHCP 服务器发起请求,给 Pod 发放或回收 IP 地址;
- host-local:使用预先配置的 IP 地址段来进行分配;
- static:为容器分配一个静态 IPv4/IPv6 地址,主要用于 Debug 场景。
- META 插件:其他功能的插件。有以下类型
- tuning:通过 sysctl 调整网络设备参数;
- portmap:通过 iptables 配置端口映射;
- bandwidth:使用 Token Bucket Filter 来限流;
- sbr:为网卡设置 source based routing;
- firewall:通过 iptables 给容器网络的进出流量进行限制。
- Windows 插件:专门用于 Windows 平台的 CNI 插件。
- win-bridge
- win-overlay 网络插件。
- 第三方网络插件:第三方开源的网络插件众多,每个组件都有各自的优点及适应的场景,难以形成统一的标准组件,常用有 Flannel、Calico、Cilium、OVN 网络插件。
大部分的 CN I插件功能设计上遵守功能职责单一原则,比如:
- Bridge 插件负责网桥的相关配置;
- Firewall 插件负责防火墙相关配置;
- Portmap 插件负责端口映射相关配置。
因此,当网络设置比较复杂时,通常通过调用多个插件来完成。CNI 通过链式调(NetworkConfigList)用多个插件,将多个插件组合起来按顺序调用。
例如:Flannel CNI 插件配置 POD 网络时的链式调用。
Main 插件
Bridge 插件
- 下载源码:
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
- 创建 Network Namespace:
ip netns add 1234567890
- 新增 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
- 将 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": {}
}
- 查看 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 件事情:
- 收到 ADD 命令时,bin/host-device 根据命令参数,将网卡移入到指定的 Network Namespace。
- 收到 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。
- 下载 CNI:https://github.com/containernetworking/plugins/releases
- 把 CNI 的 binary 放置到每个 Node 的 /opt/cni/bin/。
- 为每个 Node 配置 kubelet:
$ vi /etc/kubernetes/kubelet
...
KUBELET_ARGS="--network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin"
- 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 种实现模式:
- Overlay:靠隧道打通,不依赖底层网络;
- Underlay:靠底层网络打通,强依赖底层网络;
- 路由:靠路由打通,部分依赖底层网络;
常见的第三方网络插件:
- 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 打造的专用网络解决方案,旨在提供操作简单性和性能。
CNI 插件项目 Forks 数量比较:
CNI 插件项目 10Gbit 网络下的 CPU 消耗比较:
CNI 使用的 I/O 接口虚拟化
根据 CNI 插件不同的实现方式,也会使用到不同的 I/O 接口虚拟化技术。
- Veth-Pair:创建一个 Veth-Pair 对,两端分别接入到 Host root namespace(Linux Bridge / Open vSwitch)和 Container network namespace。Container 内发出的网络数据包,通过 vSwitch 进入到 Host OS Kernel Network Stack。
- Multi-Plexing(多路复用):使用一个 MACVLAN / IPVLAN 中间网络设备,虚拟出多个 Virtual NIC 接入到 Container,根据 MAC/IP 地址来区分数据报文如何转发到具体的容器。
- Hardware switching(硬件交换):SR-IOV 内部实现了一个 Hardware Switch,通过 VFs 的方式接入到每个 Pods。