背景 macvlan核心是在主机网卡上克隆出带不同mac和ip的mac网卡,交换机可以直接访问mac网卡,不需要通过宿主机转发(类似于esxi VCSA的vm的访问方式) 在中国xx这种规模的企业中,我们的k8s集群的业务pod使用macvlan网络,其目的一方面是降低封装与转发的性能开销,一方面方便CMDB管理、方便防火墙做ACL规则。我们分配给业务可以直接分配管理网段,然后通过macvlan进行ip的分配
1 2 3 4 5 6 7 8 9 10 11 12 13 K8s macvlan CNI 插件 ↓ 调用 Linux 内核的 macvlan 特性 ↓ 操作 宿主机的物理网卡 Linux版本(手动): ip link add mac0 link eth0 type macvlan mode bridge K8s 版本(CNI 插件自动做): Pod 创建 → CNI 插件收到调用 → 在宿主机上执行同样的操作 → 把 macvlan 接口放进 Pod 的网络 namespace
Linux版本单机macvlan 核心特点:macvlan 让一个物理网卡”分裂”出多个虚拟网卡,但宿主机自己反而看不见它们。
VLAN 子接口(eth0.10)
macvlan 子接口(mac0)
隔离依据
VLAN ID(802.1Q tag)
MAC 地址
子接口有自己的 MAC
不一定(通常共享)
一定有,独立 MAC
交换机看到什么
同一个 MAC,不同 VLAN tag
多个不同 MAC ,同一个口
需要交换机配置
端口配 trunk,允许 VLAN 通过
混杂模式或 MAC 白名单
目的
广播域隔离(安全/多租户)
一个口”假装”多台机器
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 物理网卡 eth0(MAC: aa:aa:aa:aa:aa:aa) │ ├── mac0(MAC: aa:aa:aa:aa:aa:01)──→ 192.168.1.100 ├── mac1(MAC: aa:aa:aa:aa:aa:02)──→ 192.168.1.101 └── mac2(MAC: aa:aa:aa:aa:aa:03)──→ 192.168.1.102 ip link add mac0 link eth0 type macvlan mode bridge ip addr add 10.163.2.199/24 dev mac0 ip link set mac0 up ip link add mac1 link eth0 type macvlan mode bridge ip addr add 10.163.2.198/24 dev mac1 ip link set mac1 up 测试1:mac0连网关(通 ping -c 2 -I mac0 10.163.2.200 PING 10.163.2.200 (10.163.2.200) from 10.163.2.199 mac0: 56(84) bytes of data. 64 bytes from 10.163.2.200: icmp_seq=1 ttl=64 time=0.217 ms 64 bytes from 10.163.2.200: icmp_seq=2 ttl=64 time=0.297 ms --- 10.163.2.200 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1032ms rtt min/avg/max/mdev = 0.217/0.257/0.297/0.040 ms 测试2:mac0连本机eth0(不通 ping -c 2 -I mac0 10.163.2.100 PING 10.163.2.100 (10.163.2.100) from 10.163.2.199 mac0: 56(84) bytes of data. From 10.163.2.199 icmp_seq=1 Destination Host Unreachable From 10.163.2.199 icmp_seq=2 Destination Host Unreachable --- 10.163.2.100 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1063ms pipe 2 测试3:mac1 → mac0(通 ping -c 2 -I mac1 10.163.2.199 PING 10.163.2.199 (10.163.2.199) from 10.163.2.198 mac1: 56(84) bytes of data. From 10.163.2.198 icmp_seq=1 Destination Host Unreachable From 10.163.2.198 icmp_seq=2 Destination Host Unreachable --- 10.163.2.199 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1007ms pipe 2
Multus概述 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 Multus(元 CNI,开源框架) │ │ 读 Pod 注解,决定调用哪些 CNI 插件 │ ├── 默认网络:Calico / Flannel / 任何 CNI └── 附加网络:macvlan / ipvlan / sriov / 任何 CNI 单网络: Pod 创建 ↓ kubelet 调用默认 CNI(如 Calico) ↓ Calico 创建 veth pair,分配 IP ↓ Pod 有一张网卡 eth0 多网络: Pod 创建 ↓ kubelet 调用 Multus(元 CNI,排在第一位) ↓ Multus 读 Pod 注解,发现要挂 macvlan ↓ Multus 按顺序执行 CNI 链: ① 默认网络(Calico)→ 创建 eth0 ② 附加网络(macvlan)→ 创建 net1 ↓ Pod 有两张网卡
Multus三种网络模式
Pod 注解
效果
实际效果
不带注解
走默认 CNI(Calico)
普通业务 Pod
k8s.v1.cni.cncf.io/networks: xxx
默认 CNI + 附加网络
双网卡 Pod
v1.multus-cni.io/default-network: xxx
替换 默认网络
macvlan Pod(eth0 直接是 macvlan)
Multus专属CRD NAD 1 2 3 4 5 NAD(NetworkAttachmentDefinition) - 是 Multus 的 CRD,相当于"网络说明书" - Pod 注解里的名字指向 NAD - Multus 读 NAD 的 config 字段,拿到 CNI 插件列表和参数 - 没有 NAD,Multus 不知道该调用哪些插件
whereabouts概述
host-local
dhcp
whereabouts
IP 记录在哪
节点本地文件
DHCP 服务器
K8s CRD(全局)
跨节点冲突
❌ 会撞
✅ 不撞(DHCP 管)
✅ 不撞(CRD 管)
外部依赖
无
DHCP 服务器
无(用 K8s API)
适用场景
单节点/测试
物理网络有 DHCP
多网络 + 跨节点
一个开源的 CNI IPAM 插件,专门为多网络场景 设计。它解决的问题就一个:跨节点分配IP不冲突
1 2 3 macvlan Pod 的 IP 是物理网络的真实 IP(188.108.x.x),这个池子是所有节点共享的。假设节点 A 和节点 B 同时给 Pod 分 IP: 节点 A 的 host-local:分了 188.108.38.160 节点 B 的 host-local:也分了 188.108.38.160 ← 撞了!
工作原理
1 2 3 4 5 6 7 Pod 需要 IP ↓ whereabouts(跑在节点上)→ 去 K8s API 查 IP 池 ↓ 找一个没被占用的 IP → 写入 CRD 记录("188.108.38.160 分给 Pod X 了" ) ↓ 返回 IP
Multus+whereabouts部署 环境3master 3worker 1controller-node
Calico/Flannel
macvlan
流量路径
Pod → veth → 宿主机网络栈 → NAT/隧道 → 物理网卡
Pod → macvlan → 物理网卡(直接出去)
宿主机做啥
转发、NAT、封装(CPU 开销)
不做任何处理 (零开销)
交换机看到
一个 MAC(宿主机)
多个 MAC (宿主机 + 每个 Pod)
部署 Multus 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 没有提供helm部署方式,直接使用manifests部署, 有两种部署模式,轻量版和完整版 轻量版 kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset.yml 完整版 kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml kubectl get pods -n kube-system -w| grep multus kube-multus-ds-22r25 1/1 Running 0 4m34s kube-multus-ds-2bs22 1/1 Running 0 4m34s kube-multus-ds-5cd2m 1/1 Running 0 4m34s kube-multus-ds-gpc7l 1/1 Running 0 4m34s kube-multus-ds-smwvs 1/1 Running 0 4m34s kube-multus-ds-sn6wx 1/1 Running 0 4m34s ls /etc/cni/net.d/ | grep multus00-multus.conf cat /etc/cni/net.d/00-multus.conf{"capabilities" :{"bandwidth" :true ,"portMappings" :true },"cniVersion" :"0.3.1" ,"logLevel" :"verbose" ,"logToStderr" :true ,"name" :"multus-cni-network" ,"clusterNetwork" :"/host/etc/cni/net.d/10-calico.conflist" ,"type" :"multus-shim" }
部署macvlan CNI 二进制 macvlan本质上也是二进制部署
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 32 33 34 每个节点都需要安装 CNI_VERSION=v1.5.1 wget https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION} /cni-plugins-linux-amd64-${CNI_VERSION} .tgz tar -xzf cni-plugins-linux-amd64-${CNI_VERSION} .tgz ./macvlan cp macvlan /opt/cni/bin/chmod +x /opt/cni/bin/macvlanls /opt/cni/bin/ | grep macvlan我直接用Ansible装了 ansible -i inventory/sample/inventory.ini all -b -m file \ -a "path=/opt/cni/bin state=directory owner=root group=root mode=0755" ansible -i inventory/sample/inventory.ini all -b -m get_url \ -a "url=https://github.com/containernetworking/plugins/releases/download/v1.5.1/cni-plugins-linux-amd64-v1.5.1.tgz dest=/tmp/cni-plugins-linux-amd64-v1.5.1.tgz mode=0644 timeout=120" ansible -i inventory/sample/inventory.ini all -b -m command \ -a "tar -xzf /tmp/cni-plugins-linux-amd64-v1.5.1.tgz -C /opt/cni/bin ./macvlan creates=/opt/cni/bin/macvlan" ansible -i inventory/sample/inventory.ini all -b -m file \ -a "path=/opt/cni/bin/macvlan state=file owner=root group=root mode=0755" ansible -i inventory/sample/inventory.ini all -m stat \ -a "path=/opt/cni/bin/macvlan get_checksum=false"
部署whereabouts 1 2 3 4 5 6 7 8 kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/master/doc/crds/whereabouts.cni.cncf.io_ippools.yaml kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/master/doc/crds/whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/master/doc/crds/daemonset-install.yaml
创建 macvlan NAD https://www.cni.dev/plugins/current/main/macvlan/ 把macvlan+whereabouts关联起来
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 节点网卡: eth0 节点网卡网段: 10.163.2.1-10.163.2.199 apiVersion: k8s.cni.cncf.io/v1 kind: NetworkAttachmentDefinition metadata: name: macvlan-net namespace: default spec: config: '{ "cniVersion": "0.3.1", "name": "macvlan-net", "type": "macvlan", "master": "eth0", "mode": "bridge", "ipam": { "type": "whereabouts", "range": "192.168.1.0/24", "range_start": "192.168.1.200", "range_end": "192.168.1.250" } }' kubectl apply -f macvlan-nad.yaml kubectl get network-attachment-definitions -n default NAME AGE macvlan-net 10s
测试:附加网卡(双网卡)模式 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 32 33 34 35 36 37 38 39 apiVersion: v1 kind: Pod metadata: name: macvlan-attach annotations: k8s.v1.cni.cncf.io/networks: macvlan-net spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 kubectl apply -f pod-attach.yaml kubectl get pod macvlan-attach -oyaml ... k8s.v1.cni.cncf.io/network-status: |- [{ "name" : "k8s-pod-network" , "ips" : [ "10.233.83.5" ], "default" : true , "dns" : {} },{ "name" : "default/macvlan-net" , "interface" : "net1" , "ips" : [ "192.168.1.200" ], "mac" : "72:9c:18:29:c2:6e" , "dns" : {} }] ... 可以看到获取到了第二个网卡
测试:替换默认网络模式 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 32 33 34 35 36 37 38 39 40 41 apiVersion: v1 kind: Pod metadata: name: macvlan-default annotations: v1.multus-cni.io/default-network: default/macvlan-net spec: containers: - name: busybox image: busybox:1.36 command : ["sleep" , "3600" ] kubectl apply -f pod-default.yaml kubectl exec -it macvlan-default -- ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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 inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue link /ether ee:52:f9:65:42:ed brd ff:ff:ff:ff:ff:ff inet 192.168.1.201/24 brd 192.168.1.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::ec52:f9ff:fe65:42ed/64 scope link valid_lft forever preferred_lft forever 可以看到这里只有一张网卡了 kubectl exec -it macvlan-default -- ping -c 2 192.168.1.200 PING 192.168.1.200 (192.168.1.200): 56 data bytes 64 bytes from 192.168.1.200: seq =0 ttl=64 time=0.800 ms 64 bytes from 192.168.1.200: seq =1 ttl=64 time=0.505 ms --- 192.168.1.200 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.505/0.652/0.800 ms 测试上一个IP ping通
测试:查看分配记录 1 2 3 4 5 6 7 8 kubectl get ippools.whereabouts.cni.cncf.io -A NAMESPACE NAME AGE kube-system 192.168.1.0-24 14m kubectl get overlappingrangeipreservations.whereabouts.cni.cncf.io -A NAMESPACE NAME AGE kube-system 192.168.1.200 12m kube-system 192.168.1.201 7m39s