在 K8s/K3s 中为特定 Pod 分配独立物理 IP (Multus + Macvlan + VLAN)
在构建 HomeLab 或企业内网时,我们经常会将服务部署在 K8s/K3s 集群中。默认情况下,集群内的出站流量会经过 CNI(如 Flannel 或 Cilium)的 SNAT 转换,源 IP 会变成宿主机的 Node IP。
但在某些场景下(例如部署 qBittorrent 等 P2P 下载工具、需要被独立监控的服务,或者需要绕过网关透明代理的流量),我们需要让 Pod 拥有一个真实的局域网独立 IP,并且可能还需要将其划分到**特定的 VLAN(如 VLAN 10)**中。
本文将介绍如何使用 Multus CNI 配合 Macvlan,为特定的 Pod “插上一根直通物理局域网的网线”,并配置 OPNsense 网关实现流量直连。
架构思路与生态兼容性
Section titled “架构思路与生态兼容性”本方案完美兼容硬核 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
Section titled “步骤一:部署 Multus CNI”Multus 作为一个“元 CNI”,允许 Pod 挂载多块网卡。如果你的集群尚未安装,可以通过官方提供的 DaemonSet 一键安装(Thick 模式兼容性最好):
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.ymlkubectl 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 系统内部的物理网卡(如eth0或ens18)已经是解包后的 VLAN 10 网络了。下方配置中的master直接填eth0即可。- 情况 B(Trunk 模式): 如果 PVE 传递的是 Trunk 口,你需要在 K3s 宿主机系统层面(如 Netplan 或 NetworkManager)先创建一个 VLAN 子接口
eth0.10,然后下方的master必须填入eth0.10。
apiVersion: "k8s.cni.cncf.io/v1"kind: NetworkAttachmentDefinitionmetadata: name: macvlan-direct namespace: default # 必须替换为你的应用所在的 namespacespec: # ⚠️ 核心提醒:下方 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 为例:
apiVersion: apps/v1kind: Deploymentmetadata: name: qbittorrent namespace: defaultspec: 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。
- 进入设置菜单: 登录 OPNsense,导航至 防火墙 (Firewall) -> NAT -> 出站 (Outbound)。
- 修改模式: 将出站 NAT 模式修改为 混合出站 NAT 规则生成 (Hybrid outbound NAT rule generation),点击保存。
- 添加豁免规则: 点击右上角的
+号添加一条新规则。 - 关键配置项:
- 不进行 NAT (Do not NAT): 必须勾选页面最上方这个复选框。
- 接口 (Interface): 选择流量离开 OPNsense 去往上层网络(如光猫或旁路由)的接口,通常是
WAN。 - TCP/IP 版本:
IPv4。 - 协议 (Protocol):
any。 - 源地址 (Source address): 下拉选择
单主机或网络 (Single host or Network),并输入分配给 Pod 的独立 IP10.0.10.101,掩码选择/32。 - 转换/目标 (Translation / target): 保持默认(勾选了不进行 NAT 后,此项会失效)。
- 调整规则优先级: 保存规则后,回到列表页。将这条新创建的豁免规则拖动到所有规则的最顶部(确保它在常规的子网 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 加上了默认物理路由并绑定了网卡后,它已经彻底变成了一台插在局域网上的“实体机”。这会带来两个必须处理的变更:
- 集群 Ingress / 域名访问失效: 由于流量现在是从集群的
eth0进,却从直连的net1出,这会导致严重的“异步路由”(Asymmetric Routing),使得原有的 Nginx/Traefik Ingress 域名访问或 NodePort 访问不断转圈超时。- 解决方案: 放弃域名和 NodePort 转发,以后请直接在浏览器输入分配的独立 IP 与端口(如
http://10.0.10.101:8080)来访问后台 WebUI。
- 解决方案: 放弃域名和 NodePort 转发,以后请直接在浏览器输入分配的独立 IP 与端口(如
- 更新 Radarr / Sonarr 下载客户端: 如果你使用了 Radarr、Sonarr 或 Prowlarr 等自动化追剧工具,由于 qBittorrent 的访问入口已经改变,你必须前往这些工具的
设置 -> 下载客户端 (Download Clients)中,将 qBittorrent 的主机地址更新为新的独立 IP(10.0.10.101)。如果不更新,这些联动组件将无法把种子推送到下载器中。
避坑指南:疑难杂症排查
Section titled “避坑指南:疑难杂症排查”如果在配置过程中遇到 Pod 无法获取独立 IP 的情况,通常是由以下三个核心问题导致的:
1. PVE 虚拟化环境下的网卡限制
Section titled “1. PVE 虚拟化环境下的网卡限制”如果你的 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 的独占模式并重启相关组件抢回控制权:
# 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.confkubectl rollout restart daemonset kube-multus-ds -n kube-system3. 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 插件包:
# 确保目录存在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 即可正常挂载