在 Kubernetes 中,Pod 默认通常只有一张网络接口,也就是常见的 eth0。这张接口承担的是集群默认网络职责,用于 Pod 之间的基础互通,以及 Pod 访问 Service、DNS 等集群能力。Multus 的作用并不是替代这张默认接口,而是在此基础上继续为 Pod 附加额外网络,使 Pod 成为一个 multi-homed pod。这也是 Multus 官方对自己的核心定位。
对于 AI 算力平台来说,这类能力并不只是“多一张网卡”这么简单。很多场景下,默认网络更适合承载 Kubernetes 基础通信,而训练、推理、存储访问或特定业务流量,则希望走独立网络平面。这样做的价值在于:不同用途的流量可以被显式拆开,并拥有独立的接口、地址和路由语义。
1. Multus 解决的是什么问题
Multus 本身不是某一种底层网络实现,而是一个 meta-plugin。它的职责,是在默认网络之外,再调用其他 CNI 插件,为 Pod 附加额外网络接口。也就是说:
- 默认网络仍然存在;
eth0仍然负责集群基础通信;- Multus 负责把第二张、第三张网卡接进 Pod。
在具体实现上,额外网络通常通过 NetworkAttachmentDefinition 来描述。Pod 通过注解引用这些定义,Multus 再调用诸如 macvlan、host-device、bridge 等底层 CNI,把对应网络真正附加到 Pod。所以,Multus 的关键价值不是“替代 Flannel/Calico”,而是:
- 保留默认网络作为基础入口;
- 让附加网络具备标准化定义方式;
- 让 Pod 可以同时接入多个网络平面。
2. Multus 的安装
官方 README 说明,自 4.0 起引入了基于 multus-daemon 和 multus-shim 的 thick plugin;相比 thin plugin,它带来了额外能力,但资源开销也会更高。但官方同时建议大多数环境优先使用 thick plugin。所以,默认用 thick plugin,除非你的环境资源非常紧张。
# 下载yaml文件
wget <https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml>
# 应用yaml
kubectl apply -f multus-daemonset-thick.yml
# 检查
kubectl -n kube-system get pods -o wide | grep multus
3. 附加的网络定义
Multus 只负责“把额外网络挂进 Pod”,但“额外网络长什么样”要通过 NetworkAttachmentDefinition 定义。这个对象的本质,就是把某个附加网络的 CNI 配置保存为 Kubernetes 资源,供 Pod 通过注解引用。在本文的样例中,第二张、第三张网络都是基于 macvlan 构建的。这也是一种非常典型的做法,因为 macvlan 的职责就是创建一个新的虚拟网卡,并把它连接到宿主机某个已有物理接口之上。官方文档也明确说明:macvlan 会为每个新虚拟接口分配独立 MAC 地址,宿主机接口则作为上联 master。从配置角度看,一个 NAD 里最关键的通常是这几项:
type:底层 CNI 类型,例如macvlanmaster:绑定到哪张宿主机物理网卡mode:例如bridgeipam:地址从哪里来、如何分配
通常来说AI算力网络的计算平面是独立出来的,如果你有多个平面分别绑定到不同物理网卡,那么你可以将**一个 NAD 对应一个附加网络平面。**这样后面无论是 YAML、测试还是抓包,逻辑都会很清楚。
4. IP 地址分配的三种方式:
在这一小节里,真正决定附加网络“怎么活起来”的,不只是 Multus 和底层 CNI,还有 IPAM。
4.1 host-local:适合简单测试
host-local 是 CNI 官方提供的本地 IPAM 插件。它会从指定地址范围中分配地址,并把状态保存在本地文件系统中,因此只能保证单个节点内地址唯一,不具备集群级协调能力。所以它适合:
- 快速验证
- 小规模测试
- 地址冲突边界可控的场景
但如果多个节点上的 Pod 需要共享同一地址池,它就不再理想。
A平面子网
________________________________________________________
# 配置pod使用到的第2张网卡的定义,计算A平面
cat <<EOF | kubectl apply -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: compute-a-net
namespace: default
spec:
config: '{
"cniVersion": "0.3.1",
"type": "macvlan",
"master": "ens18np0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "10.8.10.0/24",
"rangeStart": "10.8.10.150",
"rangeEnd": "10.8.10.199",
"routes": [],
"gateway": ""
}
}'
EOF
B平面子网
_________________________________________________________
# 配置pod使用到的第3张网卡的定义,计算B平面
cat <<EOF | kubectl apply -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: compute-b-net
namespace: default
spec:
config: '{
"cniVersion": "0.3.1",
"type": "macvlan",
"master": "ens20np0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "10.8.15.0/24",
"rangeStart": "10.8.15.150",
"rangeEnd": "10.8.15.199",
"routes": [],
"gateway": ""
}
}'
EOF
4.2 Whereabouts:适合集群级地址分配
Whereabouts 的定位正是为了解决 host-local 只能感知单节点的问题。
它官方将自己定义为一个 cluster-wide IPAM plugin,用于在集群范围内分配地址,并追踪这些地址在 Pod 生命周期中的占用情况。所以,如果你的附加网络需要:
- 多节点共享地址池
- 避免跨节点地址冲突
- 在集群范围管理地址生命周期
那么 Whereabouts 更适合长期使用。
# 通过helm下载安装chart
helm pull oci://ghcr.io/k8snetworkplumbingwg/whereabouts-chart \\
--version v0.9.2 \\
--untar
helm install whereabouts ./ -n kube-system
# 检查
kubectl get pods -n kube-system | grep whereabouts
whereabouts-whereabouts-chart-2wt4p 1/1 Running 0 28s
whereabouts-whereabouts-chart-48g46 1/1 Running 0 28s
whereabouts-whereabouts-chart-4wwc2 1/1 Running 0 28s
whereabouts-whereabouts-chart-5n9m9 1/1 Running 0 28s
whereabouts-whereabouts-chart-9q2td 1/1 Running 0 28s
whereabouts-whereabouts-chart-c9xpv 1/1 Running 0 28s
whereabouts-whereabouts-chart-controller-7f655f9f4d-dxl4h 1/1 Running 0 28s
whereabouts-whereabouts-chart-wxlrm 1/1 Running 0 28s
whereabouts-whereabouts-chart-xzrxv 1/1 Running 0 28s
kubectl get crd | grep whereabouts
ippools.whereabouts.cni.cncf.io 2025-11-27T13:47:42Z
nodeslicepools.whereabouts.cni.cncf.io 2025-11-27T13:47:42Z
overlappingrangeipreservations.whereabouts.cni.cncf.io 2025-11-27T13:47:42Z
kubectl create ns nad
A平面子网1
________________________________________________________
cat <<EOF | kubectl apply -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: compute-a-net
namespace: nad
spec:
config: '{
"cniVersion": "0.3.1",
"type": "macvlan",
"master": "ens2np0",
"mode": "bridge",
"ipam": {
"type": "whereabouts",
"range": "10.50.10.0/24",
"range_start": "10.50.10.70",
"range_end": "10.50.10.199",
"routes": [
{ "dst": "10.50.20.0/24", "gw": "10.50.10.254" }
]
}
}'
EOF
A平面子网2
_________________________________________________________
cat <<EOF | kubectl apply -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: compute-a-net
namespace: nad
spec:
config: '{
"cniVersion": "0.3.1",
"type": "macvlan",
"master": "ens2np0",
"mode": "bridge",
"ipam": {
"type": "whereabouts",
"range": "10.50.20.0/24",
"range_start": "10.50.20.70",
"range_end": "10.50.20.199",
"routes": [
{ "dst": "10.50.10.0/24", "gw": "10.50.20.254" }
]
}
}'
EOF
kubectl get network-attachment-definition.k8s.cni.cncf.io -A
kubectl get network-attachment-definition.k8s.cni.cncf.io compute-a-net -n default -o yaml
4.3 DHCP:适合接入既有地址体系
DHCP 方式并不只是“把地址交给外部网络去分”。CNI 官方文档明确指出,DHCP 插件依赖后台 daemon,因为租约需要续租。换句话说,它不是一次性拿到地址就结束,而是要持续维护租约状态。因此,DHCP 更适合以下场景:
- 现有网络体系本来就依赖 DHCP
- 希望 Pod 的附加网络融入既有地址管理体系
- 能接受额外维护 DHCP daemon
它不是最省事的方案,但在某些接入现网的场景里反而最自然。
**1.确认 dhcp二进制在哪
# 路径写进变量**
for p in /opt/cni/bin/dhcp /usr/lib/cni/dhcp; do
[ -x "$p" ] && echo "dhcp_bin=$p" && DHCP_BIN="$p" && break
done
[ -n "$DHCP_BIN" ] || { echo "ERROR: dhcp binary not found"; exit 1; }
**2.确认有flock**
command -v flock && flock --version | head -n 1 || echo "NO flock"
**3.写入tmpfiles规则**
#**保证重启后自动有/run/cni目录**
sudo tee /etc/tmpfiles.d/cni-dhcp.conf >/dev/null <<'EOF'
d /run/cni 0755 root root -
EOF
#检查
sudo cat /etc/tmpfiles.d/cni-dhcp.conf
**4.写systemd 服务文件**
**# 使用$DHCP_BIN**
sudo tee /etc/systemd/system/cni-dhcp-daemon.service >/dev/null <<EOF
[Unit]
Description=CNI DHCP Daemon (provides /run/cni/dhcp.sock)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStartPre=/usr/bin/install -d -m 0755 /run/cni
ExecStart=/usr/bin/flock -n /run/cni/dhcp-daemon.lock ${DHCP_BIN} daemon
Restart=always
RestartSec=2
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
EOF
# 检查
grep -nE 'ExecStart=|ExecStartPre=' /etc/systemd/system/cni-dhcp-daemon.service
8:ExecStartPre=/usr/bin/install -d -m 0755 /run/cni
9:ExecStart=/usr/bin/flock -n /run/cni/dhcp-daemon.lock /opt/cni/bin/dhcp daemon
**5.加载 systemd 配置、设置开机自启并启动服务**
sudo systemctl daemon-reload
sudo systemctl enable --now cni-dhcp-daemon
# 检查
sudo systemctl is-enabled cni-dhcp-daemon
enabled
sudo systemctl is-active cni-dhcp-daemon
active
sudo ss -xlpn | grep '/run/cni/dhcp.sock'
u_str LISTEN 0 4096 /run/cni/dhcp.sock 73184466 * 0 users:(("dhcp",pid=195537,fd=4))
/run/cni/dhcp.sock 73184466 * 0 users:(("dhcp",pid=195537,fd=4))
6.**最终确认“单实例锁”生效
# 防止重复启动**
sudo /usr/bin/flock -n /run/cni/dhcp-daemon.lock -c 'echo lock-acquired; sleep 1' && echo OK || echo "LOCK-BLOCKED (expected)"
5. 创建多网口 Pod 并验证
有了默认网络、Multus 和 NAD 之后,下一步就是把这些网络真正挂到 Pod 上。这里的重点不是 Pod 能不能启动,而是:
- Pod 是否真的多出了
net1、net2 - 这些接口的地址是否来自预期地址池
- 路由是否进入了预期网络平面
- 实际流量是否真的走到了目标物理口
# 创建位于指定节点的POD1
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: multi-net-pod-4
namespace: demo
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name": "compute-a-net", "namespace": "nad" },
{ "name": "compute-b-net", "namespace": "nad" }
]'
spec:
nodeName: gpu-worker-4
containers:
- name: tester
image: x.x.x.x:xxxx/linux/alpine:3.19
command: ["/bin/sh", "-c", "sleep 36000"]
EOF
# 创建位于指定节点的POD2
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: multi-net-pod-5
namespace: demo
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name": "compute-a-net", "namespace": "nad" },
{ "name": "compute-b-net", "namespace": "nad" }
]'
spec:
nodeName: gpu-worker-5
containers:
- name: tester
image: x.x.x.x:xxxx/linux/alpine:3.19
command: ["/bin/sh", "-c", "sleep 36000"]
EOF
# 查看POD中的网络接口
kubectl exec -it multi-net-pod-4 -n demo -- 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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0@if38: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP qlen 1000
link/ether ee:24:c7:90:4e:2a brd ff:ff:ff:ff:ff:ff
inet 192.168.3.9/24 brd 192.168.3.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::ec24:c7ff:fe90:4e2a/64 scope link
valid_lft forever preferred_lft forever
3: net1@net2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc noqueue state UP qlen 1000
link/ether ba:ab:09:8f:43:7d brd ff:ff:ff:ff:ff:ff
inet 10.8.10.200/24 brd 10.8.10.255 scope global net1
valid_lft forever preferred_lft forever
inet6 fe80::b8ab:9ff:fe8f:437d/64 scope link
valid_lft forever preferred_lft forever
4: net2@if8: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 9000 qdisc noqueue state UP qlen 1000
link/ether 2e:13:0f:e7:d5:22 brd ff:ff:ff:ff:ff:ff
inet 10.8.15.200/24 brd 10.8.15.255 scope global net2
valid_lft forever preferred_lft forever
inet6 fe80::2c13:fff:fee7:d522/64 scope link
valid_lft forever preferred_lft forever
kubectl exec -it multi-net-pod-5 -n demo -- 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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0@if76: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP qlen 1000
link/ether 5e:92:17:05:e5:06 brd ff:ff:ff:ff:ff:ff
inet 192.168.4.16/24 brd 192.168.4.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5c92:17ff:fe05:e506/64 scope link
valid_lft forever preferred_lft forever
3: net1@net2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc noqueue state UP qlen 1000
link/ether be:14:82:ec:5f:4b brd ff:ff:ff:ff:ff:ff
inet 10.8.10.201/24 brd 10.8.10.255 scope global net1
valid_lft forever preferred_lft forever
inet6 fe80::bc14:82ff:feec:5f4b/64 scope link
valid_lft forever preferred_lft forever
4: net2@if8: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 9000 qdisc noqueue state UP qlen 1000
link/ether 02:ed:75:54:9b:4a brd ff:ff:ff:ff:ff:ff
inet 10.8.15.201/24 brd 10.8.15.255 scope global net2
valid_lft forever preferred_lft forever
inet6 fe80::ed:75ff:fe54:9b4a/64 scope link
valid_lft forever preferred_lft forever
# 验证网络通信
kubectl exec -it multi-net-pod-4 -n demo -- ping 10.8.10.5
PING 10.8.10.5 (10.8.10.5): 56 data bytes
64 bytes from 10.8.10.5: seq=0 ttl=63 time=0.435 ms
64 bytes from 10.8.10.5: seq=1 ttl=63 time=0.150 ms
kubectl exec -it multi-net-pod-5 -n default -- ping 10.8.10.200
PING 10.8.10.200 (10.8.10.200): 56 data bytes
64 bytes from 10.8.10.200: seq=0 ttl=64 time=0.405 ms
64 bytes from 10.8.10.200: seq=1 ttl=64 time=0.170 ms
kubectl exec -it multi-net-pod-4 -n demo -- ip route get 10.8.20.1
10.8.20.1 via 10.8.10.254 dev net1 src 10.8.10.200
kubectl exec -it multi-net-pod-5 -n demo -- ip route get 10.8.20.1
10.8.20.1 via 192.168.4.1 dev eth0 src 192.168.4.16
6. 生产环境里的注意点
在 thick plugin、Whereabouts、频繁创建/销毁多网 Pod 并发存在的情况下,默认资源限制可能偏小,导致网络组件反复重启。尤其是在日志较多、地址分配频繁变动的场景下,Multus 和 Whereabouts 都应该适当上调资源限制,避免因为 OOM 导致网络能力本身变得不稳定,这里一。
# 报错
kubectl -n kube-system get pod $POD -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}{"\\n"}'
OOMKilled
kubectl -n kube-system get pod $POD -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}{"\\n"}'
137
#1 例如修改multus组件的资源大小
kubectl -n kube-system patch ds kube-multus-ds --type='json' -p='[
{"op":"replace","path":"/spec/template/spec/containers/0/resources/limits/memory","value":"256Mi"},
{"op":"replace","path":"/spec/template/spec/containers/0/resources/requests/memory","value":"128Mi"}
]'
#2 例如修改**whereabouts组件的资源大小**
kubectl -n kube-system edit ds whereabouts-whereabouts-chart
- name: whereabouts-chart
image: 10.8.17.100:60066/whereabouts/whereabouts:v0.9.2
resources:
requests:
cpu: "50m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
7. 小结
对于 AI 算力平台的最佳实践来说,管理面、默认业务面以及额外的数据面网络通常会被显式拆开,并由 Pod 以多网口方式同时接入。
而在具体落地上,真正决定附加网络行为的,不只是 Multus 本身,还包括底层网络插件和 IPAM 模型
