Skip to content

在 K8s/K3s 中为特定 Pod 分配独立物理 IP (Multus + Macvlan + VLAN)

🌱 创建: 2026/05/06 ⏱️ 更新: 2026/05/22

This content is not available in your language yet.

在构建 HomeLab 或企业内网时,我们经常会将服务部署在 K8s/K3s 集群中。默认情况下,集群内的出站流量会经过 CNI(如 Flannel 或 Cilium)的 SNAT 转换,源 IP 会变成宿主机的 Node IP。

但在某些场景下(例如部署 qBittorrent 等 P2P 下载工具、需要被独立监控的服务,或者需要绕过网关透明代理的流量),我们需要让 Pod 拥有一个真实的局域网独立 IP,并且可能还需要将其划分到**特定的 VLAN(如 VLAN 10)**中。

本文将介绍如何使用 Multus CNI 配合 Macvlan,为特定的 Pod “插上一根直通物理局域网的网线”,并配置 OPNsense 网关实现流量直连。

本方案完美兼容硬核 HomeLab 常用的 Cilium CNI + kube-vip 环境。各组件分工明确,互不冲突:

  • Cilium (主 CNI & Service LB):负责集群内 eBPF 流量管理与 Service LB IP 分配,为 Pod 提供基础的 eth0 网卡。
  • kube-vip:负责控制平面 API Server 的 VIP 漂移与高可用。
  • Multus + Macvlan (旁路直通):作为一个旁路插件,专门为指定的 Pod 塞入第二块物理网卡 net1

⚠️ 核心避坑:IP 地址池必须严格隔离! 必须确保 Macvlan 使用的静态 IP(本文以 10.0.10.101 为例),与 Cilium LB IPAM 池、kube-vip 的地址、以及 OPNsense 的 DHCP 池完全错开,避免产生 IP 冲突。


Multus 作为一个“元 CNI”,允许 Pod 挂载多块网卡。如果你的集群尚未安装,可以通过官方提供的 DaemonSet 一键安装(Thick 模式兼容性最好):

Terminal window
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml
kubectl set resources daemonset kube-multus-ds -n kube-system -c kube-multus --limits=memory=512Mi --requests=memory=100Mi

等待所有 kube-system 命名空间下的 multus Pod 处于 Running 状态即可。

步骤二:创建网络附件定义 (NetworkAttachmentDefinition)

Section titled “步骤二:创建网络附件定义 (NetworkAttachmentDefinition)”

我们需要创建一个 CRD 资源,告诉 K8s 如何划分这个直通的局域网。

创建一个名为 macvlan-network.yaml 的文件。注意:该资源必须与目标 Pod 处于同一个 Namespace。

💡 关于 VLAN 10 的特殊说明:

  • 情况 A(推荐 PVE 用户): 如果你在 PVE 虚拟机的硬件设置中,为该网卡配置了 VLAN Tag: 10,那么 K3s 系统内部的物理网卡(如 eth0ens18)已经是解包后的 VLAN 10 网络了。下方配置中的 master 直接填 eth0 即可。
  • 情况 B(Trunk 模式): 如果 PVE 传递的是 Trunk 口,你需要在 K3s 宿主机系统层面(如 Netplan 或 NetworkManager)先创建一个 VLAN 子接口 eth0.10,然后下方的 master 必须填入 eth0.10
macvlan-network.yaml
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-direct
namespace: default # 必须替换为你的应用所在的 namespace
spec:
# ⚠️ 核心提醒:下方 config 内的字符串会被解析为纯 JSON,绝对不能包含 '#' 注释。
# 所有的关键备注已经为你提取到此处,请根据这些提示修改下方 JSON 对应的值:
#
# 1. master: 根据上方 VLAN 说明,替换为 eth0 或 eth0.10
# 2. subnet: 10.0.10.0/24 (VLAN 10 的真实网段)
# 3. rangeStart: 10.0.10.101 (为 Pod 分配的静态独立 IP)
# 4. rangeEnd: 10.0.10.101 (与 rangeStart 保持一致)
# 5. gateway: 10.0.10.1 (VLAN 10 的网关)
# 6. routes: 必须添加 0.0.0.0/0 的默认路由,否则无法访问外网 (Tracker 会报错)
config: |
{
"cniVersion": "0.3.1",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "10.0.10.0/24",
"rangeStart": "10.0.10.101",
"rangeEnd": "10.0.10.101",
"gateway": "10.0.10.1",
"routes": [
{ "dst": "0.0.0.0/0", "gw": "10.0.10.1" }
]
}
}

应用此配置:kubectl apply -f macvlan-network.yaml

步骤三:修改应用的 Deployment 配置

Section titled “步骤三:修改应用的 Deployment 配置”

在应用的 template.metadata.annotations 中声明使用刚刚创建的网络。同时,为了避免 DNS 流量黑洞,我们需要让该 Pod 绕过集群内部的 CoreDNS,直接使用公网 DNS。

以部署 qBittorrent 为例:

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: qbittorrent
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: qbittorrent
template:
metadata:
labels:
app: qbittorrent
annotations:
# 绑定前面创建的 Macvlan 网络
k8s.v1.cni.cncf.io/networks: macvlan-direct
spec:
# 跳过 K8s CoreDNS,直接使用公网 DNS 解析 Tracker
dnsPolicy: "None"
dnsConfig:
nameservers:
- 223.5.5.5
- 114.114.114.114
# 必须固定节点,防止漂移导致物理网卡名称不匹配
nodeSelector:
kubernetes.io/hostname: k3s-worker-02
containers:
- name: qbittorrent
image: lscr.io/linuxserver/qbittorrent:latest
# ... 其他配置保持不变

重新部署后,进入 Pod 内部执行 ip a,你会发现它拥有了两张网卡:eth0 (集群内部 IP) 和 net1 (直连 VLAN 10 的 10.0.10.101)。

关键一步: 必须在应用的内部设置(如 qBittorrent 的高级设置 -> 网络接口)中,强制将监听网络接口绑定为 net1,确保流量不走 Cilium 的默认路由,而是真正从 Macvlan 发出。

步骤四:在 OPNsense 中设置 NAT 豁免 (Do not NAT)

Section titled “步骤四:在 OPNsense 中设置 NAT 豁免 (Do not NAT)”

当 qB 绑定 net1 向外发送数据时,流量会到达 OPNsense 网关。由于 OPNsense 默认会对出站流量进行 NAT 伪装,我们需要对这个特定的 IP 进行“豁免”,让最外层的设备(如 OpenClash)能看到它的真实源 IP。

  1. 进入设置菜单: 登录 OPNsense,导航至 防火墙 (Firewall) -> NAT -> 出站 (Outbound)
  2. 修改模式: 将出站 NAT 模式修改为 混合出站 NAT 规则生成 (Hybrid outbound NAT rule generation),点击保存。
  3. 添加豁免规则: 点击右上角的 + 号添加一条新规则。
  4. 关键配置项:
    • 不进行 NAT (Do not NAT): 必须勾选页面最上方这个复选框。
    • 接口 (Interface): 选择流量离开 OPNsense 去往上层网络(如光猫或旁路由)的接口,通常是 WAN
    • TCP/IP 版本: IPv4
    • 协议 (Protocol): any
    • 源地址 (Source address): 下拉选择 单主机或网络 (Single host or Network),并输入分配给 Pod 的独立 IP 10.0.10.101,掩码选择 /32
    • 转换/目标 (Translation / target): 保持默认(勾选了不进行 NAT 后,此项会失效)。
  5. 调整规则优先级: 保存规则后,回到列表页。将这条新创建的豁免规则拖动到所有规则的最顶部(确保它在常规的子网 NAT 规则之前生效),然后点击 应用更改 (Apply Changes)

步骤五:配置透明代理直连 (OpenClash)

Section titled “步骤五:配置透明代理直连 (OpenClash)”

完成 NAT 豁免后,外部网络(包括旁路代理)就能直接识别到 10.0.10.101 这个源 IP 了。

进入你的代理软件(如 OpenClash),在 访问控制 (Access Control) -> 不走代理的局域网设备 (Not Proxy LAN IPs) 中,将 10.0.10.101 加入列表。至此,你的 P2P 下载流量将完美绕过集群的网络虚拟化层和全局科学代理。

步骤六:访问方式变更与联动服务更新 (*arr)

Section titled “步骤六:访问方式变更与联动服务更新 (*arr)”

当你为 Pod 加上了默认物理路由并绑定了网卡后,它已经彻底变成了一台插在局域网上的“实体机”。这会带来两个必须处理的变更:

  1. 集群 Ingress / 域名访问失效: 由于流量现在是从集群的 eth0 进,却从直连的 net1 出,这会导致严重的“异步路由”(Asymmetric Routing),使得原有的 Nginx/Traefik Ingress 域名访问或 NodePort 访问不断转圈超时。
    • 解决方案: 放弃域名和 NodePort 转发,以后请直接在浏览器输入分配的独立 IP 与端口(如 http://10.0.10.101:8080)来访问后台 WebUI。
  2. 更新 Radarr / Sonarr 下载客户端: 如果你使用了 Radarr、Sonarr 或 Prowlarr 等自动化追剧工具,由于 qBittorrent 的访问入口已经改变,你必须前往这些工具的 设置 -> 下载客户端 (Download Clients) 中,将 qBittorrent 的主机地址更新为新的独立 IP(10.0.10.101)。如果不更新,这些联动组件将无法把种子推送到下载器中。

如果在配置过程中遇到 Pod 无法获取独立 IP 的情况,通常是由以下三个核心问题导致的:

如果你的 K3s 节点是运行在 Proxmox VE (PVE) 等虚拟化平台上的虚拟机,请务必注意以下两点限制:

  • 混杂模式与 MAC 地址过滤: 进入 PVE 的虚拟机设置,找到 硬件 (Hardware) -> 网络设备 (Network Device),双击打开编辑,关闭“防火墙 (Firewall)”选项。如果开启该选项,PVE 会拦截 Macvlan 自动生成的非原生 MAC 地址,导致 Pod 无法联网。
  • VLAN 隔离问题: 如果你在 Macvlan 层面使用的是子接口(如 eth0.10),请确保 PVE 分配给该虚拟机的网桥(Bridge)开启了 VLAN 感知 (VLAN aware),否则宿主机将会直接丢弃带有 VLAN Tag 的数据包。

2. Cilium 独占模式导致 Multus 失效

Section titled “2. Cilium 独占模式导致 Multus 失效”

如果你发现配置完全正确,但 Pod 依然只分配了 Cilium 的默认内部 IP,请登录宿主机检查 /etc/cni/net.d/ 目录。如果你看到 00-multus.conf 被系统自动重命名为了 00-multus.conf.cilium_bak,这说明 Cilium 开启了“独占模式”,强行禁用了 Multus。

解决办法: 关闭 Cilium 的独占模式并重启相关组件抢回控制权:

Terminal window
# 1. 修改 ConfigMap,关闭独占模式
kubectl patch configmap cilium-config -n kube-system --type merge -p '{"data":{"cni-exclusive":"false"}}'
# 2. 重启 Cilium 代理以应用新配置
kubectl rollout restart daemonset cilium -n kube-system
# 3. 等待 Cilium 启动后,重启 Multus 使其重新生成 00-multus.conf
kubectl rollout restart daemonset kube-multus-ds -n kube-system

3. K3s 节点缺失官方 CNI 基础插件

Section titled “3. K3s 节点缺失官方 CNI 基础插件”

K3s 是一个极致精简的轻量级发行版,特别是配合 Cilium 使用时,宿主机的 /opt/cni/bin 目录下可能并没有预装 macvlan 等基础官方插件。此时 Multus 虽能正常工作,但在为 Pod 创建网络沙盒时会抛出如下报错: failed to find plugin "macvlan" in path [/opt/cni/bin]

解决办法: SSH 登录到运行该 Pod 的 K3s 宿主机节点,手动下载并解压官方的标准 CNI 插件包:

Terminal window
# 确保目录存在
sudo mkdir -p /opt/cni/bin
# 下载官方 CNI 插件包 (此处以稳定的 v1.4.1 为例)
wget https://github.com/containernetworking/plugins/releases/download/v1.4.1/cni-plugins-linux-amd64-v1.4.1.tgz
# 将压缩包解压到 /opt/cni/bin 目录下
sudo tar -C /opt/cni/bin -xzf cni-plugins-linux-amd64-v1.4.1.tgz
# 重建失败的 Pod 即可正常挂载

Last updated: