首页 > 数据库 > 数据库开发技术 > 多网卡管理高手:Multus-CNI的奇妙之旅
点击上方蓝字 👆关注公众号,别让消息溜走
后台回复【加群】,与牛人互动不停
一个容器启动后,在默认情况下一般都会只存在两个虚拟网络接口(loopback和eth0),而loopback的流量始终都会在本容器内或本机循环,真正对业务起到支撑作用的只有eth0,当然这对大部分业务场景而言已经能够满足。
但是如果一个应用或服务既需要对外提供API调用服务,也需要满足自身基于分布式特性产生的数据同步,那么这时候一张网卡的性能显然很难达到生产级别的要求,网络流量延时、阻塞便成为此应用的一项瓶颈。
基于上述痛点和需求,容器多网络方案不断涌现。k8s有一个多网卡规范:K8sNetworkPlumbingWG/multi-net-spec。目前已知的多网卡方案:
k8s多网卡规范:K8sNetworkPlumbingWG/multi-net-spec
华为开发的多网络CNI插件:Huawei-PaaS/CNI-Genie
Intel 开发的多网络CNI插件: Multus-CNI
| 活跃度 | 文档 | 功能 | |
| Huawei-PaaS/CNI-Genie | 不太活跃 | 较少 https://github.com/cni-genie/CNI-Genie/blob/master/docs/CNIGenieFeatureSet.md |
具备多网卡支持 |
| Multus-CNI | 较为活跃 | 较多,较新 https://github.com/k8snetworkplumbingwg/multus-cni/tree/master/docs |
具备多网卡支持 |
而根据开源社区活跃度、是否实现CNI规范以及稳定性,我们采用multus-cni作为在K8s环境下的容器多网络方案。
Multus CNI简单来说是一种符合CNI(Container Network Interface)规范的开源插件,旨在为实现K8s(Kubernetes) 环境下容器多网卡而提出的解决方案。它作为一种“元插件”,可以与其他 CNI插件搭配使用。
Multus CNI enables attaching multiple network interfaces to pods in Kubernetes.
以上是Multus CNI项目官方对其存在意义的精简描述,它的存在就是帮助K8s的Pod(可简单理解为一组容器的集合,是K8s可管理的最小“容器”单位)建立多网络接口。
Multus CNI 本身不提供网络配置功能,它是通过用其他满足CNI规范的插件进行容器的网络配置。
如下图所示,在此场景下我们可以把Pod抽象为单个容器。原本容器里应仅存在eth0接口(loopback忽略不计),是由主插件产生创建并配置的;而当集群环境存在 Multus CNI 插件,并添加额外配置后,将会发现此容器内不再仅有eth0接口,你可以利用这些新增的接口去契合实际业务需求。
CNI (Container Network Interface), a Cloud Native Computing Foundation project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins. CNI concerns itself only with network connectivity of containers and removing allocated resources when the container is deleted. Because of this focus, CNI has a wide range of support and the specification is simple to implement.
以上引用了官方对其的描述,简短来讲CNI是一组限于容器网络面的规范,定义了容器网络资源创建、管理的规则。
但它并不仅仅是规范,它也包含了库和实现,CNI自身实现并提供了内置且通用的网络插件,同时为第三方实现其规范预留了扩展。
CNI 将插件分成三种类型:
到这里,我们已经明白,Multus CNI 属于Meta类, 它可以与其他第三方插件适配(也就是主插件),主插件来作为Pod的主网络并且被K8s所感知,它们可以搭配使用且不冲突。
这边具体讲下multus-cni的使用场景,一般可以在需要网络隔离的情况下使用额外网络,包括分离数据平面流量与控制平面流量。隔离网络流量对以下性能和安全性是很有用的:
安全性:
• 用户可以将敏感的流量发送到专为安全考虑而管理的网络平面,也可隔离不能在租户或客户间共享的私密数据。
性能:
• 用户可以在两个不同的平面上发送流量,以管理每个平面上流量的多少。
我们的集群已选用 Calico 作为网络插件并配置为 IPIP 模式。
$ cat /etc/cni/net.d/10-calico.conflist | jq
{
"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": "multuscni-test0",
"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
}
}
]
}
Multus 的快速入门方法是使用 Daemonset(一种在集群中的每个节点上运行 pod 的方法)进行部署,这会启动安装 Multus 二进制文件并配置 Multus 以供使用的 pod。
首先,克隆这个 GitHub 存储库。
git clone https://github.com/k8snetworkplumbingwg/multus-cni.git && cd multus-cni
使用kubectl从这个 repo 应用一个 YAML 文件。
cat ./deployments/multus-daemonset-thick-plugin.yml | kubectl apply -f -
Multus daemonset 作用
• 启动一个 Multus 守护程序集,这会在每个节点上运行一个 pod,它在每个节点上放置一个 Multus 二进制文件/opt/cni/bin
• 读取 按字典顺序(按字母顺序)的第一个配置文件/etc/cni/net.d,并在每个节点上为 Multus 创建一个新的**配置文件/etc/cni/net.d/00-multus.conf**,此配置是自动生成的,并且基于默认网络配置(假定为按字母顺序排列的第一个配置)
• 在每个节点上创建一个/etc/cni/net.d/multus.d目录,其中包含 Multus 访问 Kubernetes API 的身份验证信息。
kubectl get pods --all-namespaces | grep -i multus
你可以通过查看/etc/cni/net.d/目录进一步验证它是否已运行,并确保自动生成的/etc/cni/net.d/00-multus.conf存在对应于按字母顺序排列的第一个配置文件。
因为Multus CNI使用的是 DaemonSet 类型,所以默认在所有节点都有一个实例,以下Whereabouts 同理。
经观察,Pod运行不久后,将会在各节点上的/opt/cni/bin/下生成multus的可执行文件,/etc/cni/下生成网络定义文件以及用于配置集群访问的文件。
$ ls /etc/cni/net.d
00-multus.conf 10-calico.conflist calico-kubeconfig multus.d
$ ls /etc/cni/net.d/multus.d
multus.kubeconfig
$ cat /etc/cni/net.d/multus.d/multus.kubeconfig
# Kubeconfig file for Multus CNI plugin.
apiVersion: v1
kind: Config
clusters:
- name: local
cluster:
server: https://[10.233.0.1]:443
certificate-authority-data:
users:
- name: multus
user:
token:
contexts:
- name: multus-context
context:
cluster: local
user: multus
current-context: multus-context
可执行文件的作用是配置Pod的网络栈,DaemonSet的作用是实现网络互通。
注:
一个Network Namespace的网络栈包括:网卡(Network interface)、回环设备(Loopback Device)、路由表(Routing Table)和iptables规则。
打开 00-multus.conf,查看其内容:
$ cat /etc/cni/net.d/00-multus.conf | jq
{
"capabilities": {
"bandwidth": true,
"portMappings": true
},
"cniVersion": "0.3.1",
"delegates": [
{
"cniVersion": "0.3.1",
"name": "k8s-pod-network",
"plugins": [
{
"datastore_type": "kubernetes",
"ipam": {
"type": "calico-ipam"
},
"kubernetes": {
"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
},
"log_file_path": "/var/log/calico/cni/cni.log",
"log_level": "info",
"mtu": 1440,
"nodename": "master",
"policy": {
"type": "k8s"
},
"type": "calico"
},
{
"capabilities": {
"portMappings": true
},
"snat": true,
"type": "portmap"
},
{
"capabilities": {
"bandwidth": true
},
"type": "bandwidth"
}
]
}
],
"logLevel": "verbose",
"logToStderr": true,
"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig",
"name": "multus-cni-network",
"type": "multus"
}
其中Multus CNI在K8s环境下的调用关系下图所示,在Kubernetes中,处理容器网络相关的逻辑并不会在kubelet主干代码里执行,而是会在具体的CRI(Container Runtime Interface,容器运行时接口)实现里完成。
CRI将网络定义文件以JSON格式通过STDIN方式传递给Multus CNI插件可执行文件。文件中delegates的意义在于Multus会调用其delegates 指定的插件来执行,这里还有一点需要说明下,如果/etc/cni/net.d/ 目录下有多个网络定义文件,CRI只会加载按字典顺序排在第一位的文件(即插件),即默认情况下创建Pod时使用的是Calico 插件配置网络。
我们要做的第一件事是为附加到 pod 的每个附加网络接口创建配置(NetworkAttachmentDefinition)。我们将通过创建自定义资源来做到这一点。快速启动安装的一部分会创建一个“CRD”,它是我们保存这些自定义资源的主目录——我们将在其中存储每个接口的配置。
我们将添加的是一个 CNI 配置。如果你不熟悉它们,让我们快速分解它们。这是一个示例 CNI 配置:
{
"cniVersion": "0.3.0",
"type": "loopback",
"additional": "information"
}
CNI 配置是 JSON,我们在这里有一个结构,其中包含一些我们感兴趣的东西:
1. cniVersion:告诉每个 CNI 插件正在使用哪个版本,如果它使用的版本太晚(或太早),可以提供插件信息。
2. type:这告诉 CNI 在磁盘上调用哪个二进制文件。每个 CNI 插件都是一个被调用的二进制文件。通常,这些二进制文件存储在/opt/cni/bin每个节点上,CNI 执行这个二进制文件。在这种情况下,我们指定了loopback二进制文件(它创建了一个环回类型的网络接口)。如果这是你第一次安装 Multus,你可能需要验证“type字段中的插件实际上是否在/opt/cni/bin目录中的磁盘上。
3. additional:这里以这个字段为例,每个CNI插件都可以在JSON中指定他们想要的任何配置参数。这些特定于你在type现场调用的二进制文件。
再举一个例子——看看bridge CNI plugin README,它显示了额外的细节。
如果你想了解有关 CNI 配置的更多信息,可以阅读整个 CNI 规范。查看CNI 参考插件并了解它们的配置方式也可能很有用。
当 CNI 配置发生变化时,你无需重新加载或刷新 Kubelet。每次创建和删除 pod 时都会读取这些内容。因此,如果你更改配置,它将在下次创建 pod 时应用。如果需要新配置,可能需要重新启动现有的 pod。
所以,我们要创建一个额外的接口。让我们创建一个 macvlan 接口供 pod 使用。我们将创建一个自定义资源来定义接口的 CNI 配置。
请注意,在以下命令中有一个kind: NetworkAttachmentDefinition. 这是我们配置的自定义资源类型——它是 Kubernetes 的自定义扩展,它定义了我们如何将网络连接到我们的 pod。
其次,注意config信息。你会看到这是一个 CNI 配置,就像我们之前解释的那样。
最后但非常重要的是,请注意metadata字段下的name - 这里是我们为这个配置命名的地方,也是我们告诉 pod 使用这个配置的方式。
这是创建此示例配置的命令:
cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf
spec:
config: '{
"cniVersion": "0.3.0",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "192.168.1.0/24",
"rangeStart": "192.168.1.200",
"rangeEnd": "192.168.1.216",
"routes": [
{ "dst": "0.0.0.0/0" }
],
"gateway": "192.168.1.1"
}
}'
EOF
注意:此示例master使用的是eth0,应与集群中主机上的网络接口名称匹配。
containernetworking 团队维护的通用 CNI 网络插件(见https://www.cni.dev/plugins/current/),网路插件根据用途分成Main插件、Ipam插件和Meta插件:
• Main插件:主要用来创建具体的网络设备的二进制文件;
• Ipam插件:主要用来负责分配IP地址;
• Meta插件:由CNI社区维护的内部插件。
macvlan就是属于Main插件,host-local属于Ipam插件,在这里我们也可以了解到,cni插件中至少要包含Main插件和Ipam插件。
• bridge:创建基于网桥的额外网络可让同一主机中的 Pod 相互通信,并与主机通信。
• host-device:创建 host-device 额外网络可让 Pod 访问主机系统上的物理以太网网络设备。
• macvlan:创建基于 macvlan 的额外网络可让主机上的 Pod通过使用物理网络接口与其他主机和那些主机上的 Pod 通信。附加到基于 macvlan 的额外网络的每个 Pod 都会获得一个唯一的 MAC 地址。
• ipvlan:创建基于 ipvlan 的额外网络可让主机上的 Pod 与其他主机和那些主机上的 Pod 通信,这类似于基于 macvlan 的额外网络。与基于 macvlan 的额外网络不同,每个 Pod 共享与父级物理网络接口相同的 MAC 地址。
• SR-IOV:创建基于 SR-IOV 的额外网络可让 Pod 附加到主机系统上支持 SR-IOV 的硬件的虚拟功能 (VF) 接口。
• dhcp插件,节点上需要有 DHCP server;
• host-local插件 ,是给定子网范围,在单个几点上基于这个子网进行ip地址分配;
• static插件,静态地址管理,直接指定ip地址使用的。
• calico-ipam ,是clalico cni自己的ip地址分配插件,是一种集中式ip分配插件;
• whereabouts ,也是一个集中式ip分配插件,用的比较少,使用了sriov设备才用到的,这个是k8snetworkplumbingwg社区开源的。
• flannel,这就是专门为 Flannel 项目提供的 CNI 插件;
• tunning,是一个通过 sysctl 调整网络设备参数的二进制文件;
• portmap ,是一个通过 iptables 配置端口映射的二进制文件;
• bandwidth ,是一个使用 Token Bucket Filter(TBF)来进行限流的二进制文件;
• calico,是专门为Calico项目提供的CNI插件。
你可以使用以下方法查看你创建kubectl的配置:
kubectl get network-attachment-definitions
你可以通过描述它们来获得更多详细信息:
kubectl describe network-attachment-definitions macvlan-conf
我们将创建一个 pod。这看起来就像你之前创建的任何 pod 一样熟悉,但是,我们将有一个特殊的annotations字段——一个名为k8s.v1.cni.cncf.io/networks的字段,它的值是我们在上面创建的NetworkAttachmentDefinition的名称,多个时候可以使用逗号分隔。
让我们继续使用以下命令创建一个 pod(它只会休眠很长时间):
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: samplepod
# 配置附加网络接口
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf
spec:
containers:
- name: samplepod
command: ["/bin/ash", "-c", "trap : TERM INT; sleep infinity & wait"]
image: alpine
EOF
你现在可以检查 pod 并查看附加了哪些接口,如下所示:
kubectl exec -it samplepod -- ip a
# 执行信息
➜ /fly kubectl exec -it samplepod -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if48: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1440 qdisc noqueue state UP
link/ether da:1c:f1:4b:7c:55 brd ff:ff:ff:ff:ff:ff
inet 10.233.70.28/32 brd 10.233.70.28 scope global eth0
valid_lft forever preferred_lft forever
5: net1@tunl0: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 52:4e:d4:62:5e:09 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.204/24 brd 192.168.1.255 scope global net1
valid_lft forever preferred_lft forever
你应该注意,有 3 个接口:
• lo:回环网络
• eth0:默认网络
• net1:macvlan 网络
如需进一步确认,请使用kubectl describe pod samplepod并且会有一个注释部分,类似于以下内容:
Annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf
k8s.v1.cni.cncf.io/network-status:
[{
"name": "cbr0",
"ips": [
"10.244.1.73"
],
"default": true,
"dns": {}
},{
"name": "macvlan-conf",
"interface": "net1",
"ips": [
"192.168.1.205"
],
"mac": "86:1d:96:ff:55:0d",
"dns": {}
}]
此数据告诉我们,我们有两个 CNI 插件成功运行。
你可以通过创建更多自定义资源然后在 pod 的注解中引用它们,来向 pod 添加更多网络接口。你还可以重用配置,例如,要将两个 macvlan 接口附加到一个 pod,你可以像这样创建一个 pod:
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: samplepod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf,macvlan-conf
spec:
containers:
- name: samplepod
command: ["/bin/ash", "-c", "trap : TERM INT; sleep infinity & wait"]
image: alpine
EOF
请注意,注释现在变为k8s.v1.cni.cncf.io/networks: macvlan-conf,macvlan-conf。 我们有两次使用相同的配置,用逗号分隔。

原文作者:云原生AI百宝箱;如有侵权,请联系管理员删除。