关键词:MetalLB、L2、ARP、IPVS、Calico、kube-proxy、rp_filter、arp_ignore
适用环境:裸金属 / 虚拟机 Kubernetes 集群,无云厂商 LB
一、问题现象
在 Kubernetes 集群中部署 MetalLB(L2 模式),为 nginx-ingress 创建 Service type=LoadBalancer:
- Service 已正常分配 External IP(如
10.0.3.20) - Pod、Service、Endpoints 全部正常
- MetalLB speaker 无报错
- 但局域网其他机器访问 External IP 失败
arping 10.0.3.20无任何回复
⚠️ 特殊现象:
ip addr add 10.0.3.20/32 dev ens33
手动把 IP 绑到网卡后,访问立刻恢复正常。
二、环境信息
| 项目 | 值 |
|---|---|
| Kubernetes | kubeadm 集群 v1.34.3 |
| CNI | Calico(vxlan)v3.31.3 |
| kube-proxy | IPVS 模式 |
| MetalLB | v0.15.2+(CRD 模式) |
| Node 网卡 | ens33 |
| Service | nginx-ingress (LoadBalancer) |
metallb部署:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml
[root@k8smaster01 ~]# cat metallb-config.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: metallb-ippool
namespace: metallb-system
spec:
addresses:
- 10.0.3.20-10.0.3.50
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: metallb-pcauto-adver
namespace: metallb-system
spec:
ipAddressPools:
- metallb-ippool
interfaces:
- ens33
三、关键排查过程
1️⃣ MetalLB 状态确认
kubectl get pods -n metallb-system
- controller / speaker 均 Running
- Service 被正常 reconcile
kubectl logs -n metallb-system -l component=speaker | grep announce
speaker 日志显示:
created ARP responder for interface ens33
👉 MetalLB 确实在监听 ARP
2️⃣ External IP 实际落点
ip addr show kube-ipvs0 | grep 10.0.3.20
inet 10.0.3.20/32 scope global kube-ipvs0
📌 关键点:
- External IP 并不在 ens33 上
- 而是在
kube-ipvs0虚拟接口
3️⃣ ARP 抓包分析(核心)
在节点执行:
tcpdump -i ens33 arp
现象:
- 可以看到局域网发来的 ARP Request
- 完全没有 ARP Reply(10.0.3.20)
arping 10.0.3.20
# 0 response
👉 说明 内核没有对 ARP 请求做出响应
四、真正根因分析(重点)
❌ 问题不是 MetalLB
MetalLB L2 模式的工作方式是:
通过 speaker 使用内核 ARP 栈应答 ARP
它 不自己构造 ARP 包,而是依赖 Linux 内核。
❌ Linux 默认 ARP / rp_filter 策略导致丢包
在 IPVS + kube-proxy 场景下:
- External IP 位于
kube-ipvs0 - ARP 请求从
ens33进入
但 Linux 默认策略是:
rp_filter = 1(严格反向路径校验)arp_ignore = 1arp_announce = 2
📛 结果:
内核认为:
“这个 IP 不在 ens33 上,我不能从 ens33 回 ARP”
➡️ ARP 被直接丢弃
五、解决方案(最终生效)
✅ 1️⃣ 关闭 rp_filter(关键)
sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.default.rp_filter=0
sysctl -w net.ipv4.conf.ens33.rp_filter=0
✅ 2️⃣ 调整 ARP 行为
sysctl -w net.ipv4.conf.all.arp_ignore=0
sysctl -w net.ipv4.conf.all.arp_announce=0
sysctl -w net.ipv4.conf.default.arp_ignore=0
sysctl -w net.ipv4.conf.default.arp_announce=0
sysctl -w net.ipv4.conf.kube-ipvs0.arp_ignore=0
sysctl -w net.ipv4.conf.kube-ipvs0.arp_announce=0
✅ 3️⃣ 刷新 ARP 并重建 Service
ip neigh flush all
kubectl delete -f loadbalancer.yaml
kubectl apply -f loadbalancer.yaml
六、验证结果
ARP 正常响应
arping 10.0.3.20
Unicast reply from 10.0.3.20
tcpdump 可见 ARP Reply
ARP, Reply 10.0.3.20 is-at xx:xx:xx:xx:xx
外部访问成功
curl http://10.0.3.20
🎉 问题彻底解决
七、为什么“手动绑 IP 就能好”?
ip addr add 10.0.3.20/32 dev ens33
此操作等价于:
- 告诉内核:
“这个 IP 属于 ens33,可以直接 ARP 回复”
⚠️ 但这是 错误用法,会破坏 MetalLB 调度
八、生产环境建议
✅ sysctl 持久化(推荐)
# /etc/sysctl.d/99-metallb.conf
net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.all.arp_ignore=0
net.ipv4.conf.all.arp_announce=0
sysctl --system
🚀 更优方案
| 场景 | 建议 |
|---|---|
| 二层网络可控 | MetalLB L2 |
| 三层 / 大规模 | MetalLB BGP |
| Ingress | externalTrafficPolicy: Local |
九、核心经验总结
✅ MetalLB L2 100% 依赖 Linux ARP 行为
✅ IPVS 会把 External IP 放到 kube-ipvs0
✅ 默认内核参数 不适合 LB 场景
📌 一句话结论:
MetalLB L2 不通,80% 是 Linux ARP / rp_filter 的锅,不是 MetalLB 配置问题
如果你在:
- 裸金属 K8s
- 家庭实验室
- ESXi / Proxmox 虚拟化
这篇文章基本可以帮你 一次解决同类问题。
