文章目录
- 环境
- 现象
-
- [代表没有任何节点回应 ARP](#代表没有任何节点回应 ARP)
- 过程
-
- [第一步:确认 VIP 是否真的分配成功](#第一步:确认 VIP 是否真的分配成功)
- [第二步:确认 L2 Announcement Policy](#第二步:确认 L2 Announcement Policy)
- [第三步:查看 Lease](#第三步:查看 Lease)
- [第四步:确认 Cilium Agent 是否成功取得 Lease](#第四步:确认 Cilium Agent 是否成功取得 Lease)
- [第五步:检查 L2 Announcement 使用的网络接口](#第五步:检查 L2 Announcement 使用的网络接口)
- [第六步:建立专属的 L2 Announcement Policy 进行验证](#第六步:建立专属的 L2 Announcement Policy 进行验证)
- [第七步:重新验证 ARP 回应](#第七步:重新验证 ARP 回应)
- 结论
最近在 Kubernetes 集群中部署 Istio Gateway,并使用 Cilium 1.17 的 L2 Announcement 为 LoadBalancer Service 提供 VIP 时,遇到了一个非常有意思的问题。
Service 已经成功获得了 External IP,但是同网段主机始终无法访问,整个排查过程涉及了 Kubernetes、Cilium、ARP、Linux Neighbor Cache 等多个层面,记录下来,希望能帮助遇到类似问题的人。
环境
- Kubernetes v1.32.1
- Cilium v1.17.0
- 使用 Cilium LB IPAM
- 使用 Cilium L2 Announcement
- VMware 虚拟化环境
- Istio Gateway Service
- Service:
bash
type: LoadBalancer
EXTERNAL-IP:
172.20.107.230
现象
开发人员反馈,部署在 Kubernetes 集群上的系统无法正常访问。
为了确认网络连通性,我首先在客户端执行了连线测试:
bash
powershell tnc whs.lab.com -port 443
输出结果如下:
bash
RemoteAddress : 172.20.107.230
RemotePort : 443
InterfaceAlias : WLAN
SourceAddress : 172.20.106.143
TcpTestSucceeded : False
从测试结果可以确认:
- DNS 解析正常,whs.lab.com 已正确解析到 172.20.107.230。
- TCP 443 连接失败,说明客户端无法与 Kubernetes Service 建立连接。
确认 Service 是否已经成功取得 LoadBalancer IP:
bash
kubectl -n cn-lab-whs get svc cn-lab-whs-gateway-istio
输出如下:
text
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cn-lab-gateway-istio LoadBalancer 10.248.156.4 172.20.107.230 15021:31177/TCP,80:31897/TCP,443:30139/TCP 42m
可以确认:
- Service 类型为
LoadBalancer - Cilium 已经成功分配了 VIP
172.20.107.230
因此可以先排除 LB IPAM 未分配 IP 的问题。
查看 ARP:
bash
ip neigh show | grep 230
得到:
bash
172.20.107.230 FAILED
代表没有任何节点回应 ARP
过程
第一步:确认 VIP 是否真的分配成功
首先确认 Kubernetes Service 是否已经成功取得 LoadBalancer IP。
执行以下命令:
bash
kubectl -n cn-lab-whs get svc cn-lab-whs-gateway-istio
输出如下:
bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cn-lab-whs-gateway-istio LoadBalancer 10.248.156.4 172.20.107.230 15
从输出可以确认:
- Service 类型为 LoadBalancer。
- Cilium 已成功为该 Service 分配 External IP 172.20.107.230。
- Service 已建立 ClusterIP,且对外提供 80、443 及 15021 等埠。
这表示 LoadBalancer IPAM 已正常运作,VIP 已成功分配,因此可以先排除 IP Pool 配置错误或 IP 分配失败的可能性。
第二步:确认 L2 Announcement Policy
查看 Policy:
bash
kubectl get l2announcement l2announce-argo -o yaml
输出节选:
bash
spec:
externalIPs: true
loadBalancerIPs: true
interfaces:
- ens160
nodeSelector:
matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
表示:
- Worker 才会宣告 VIP
- 使用 ens160
Policy 看起来没有问题。
第三步:查看 Lease
Cilium L2 Announcement 使用 Kubernetes Lease 机制进行 Leader Election,同一个 LoadBalancer IP 在任一时间只会由一个节点负责宣告(ARP/NDP)。
因此,真正决定哪个 Node 宣告 VIP 的,不是 Service,而是 Lease。
查看:
bash
kubectl -n kube-system get lease | grep cn-lab-whs
输出入下:
bash
NAME HOLDER AGE
cilium-l2announce-cn-lab-whs-cn-lab-whs-gateway-istio cnk8sw01-d 53m
从输出可以确认:
- 已成功建立 cilium-l2announce-cn-lab-whs-cn-lab-whs-gateway-istio Lease。
Lease Holder 为 cnk8sw01-d。 - 这表示 Cilium 已完成 Leader Election,后续应由 cnk8sw01-d 负责回应 172.20.107.230 的 ARP 请求
第四步:确认 Cilium Agent 是否成功取得 Lease
虽然 Kubernetes 已完成 Leader Election,但仍需确认负责该节点的 Cilium Agent 是否真正取得 Lease,并开始负责 L2 Announcement。
首先查看运行在 cnk8sw01-d 节点上的 Cilium Pod:
bash
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
输出如下(节录):
text
NAME READY STATUS RESTARTS AGE IP NODE
cilium-7zs7g 1/1 Running 0 93m 172.20.107.56 cnk8sw01-d
接着查看该 Pod 的日志:
bash
kubectl -n kube-system logs cilium-7zs7g | grep "acquired lease"
输出如下:
text
time="2026-07-03T08:53:44.406990705Z" level=info msg="successfully acquired lease kube-system/cilium-l2announce-cn-lab-whs-cn-lab-whs-gateway-istio" subsys=klog
从日志可以确认,Cilium Agent 已成功取得 cn-lab-whs-gateway-istio 的 Lease,并开始负责该 Service 对应 VIP 的 L2 Announcement。
因此可以进一步确认:
- Kubernetes Leader Election 正常。
- Cilium Agent 已成功接管该 VIP 的宣告工作。
- 问题并非出在 Lease 竞争或 Cilium Agent 未取得 Leader 身份。
第五步:检查 L2 Announcement 使用的网络接口
再次查看 CiliumL2AnnouncementPolicy 后发现,L2 Announcement 被限制只能在 ens160 接口上进行宣告。
相关配置如下:
bash
kubectl get ciliuml2announcementpolicy policy -o yaml
输出(节录):
yaml
spec:
externalIPs: true
loadBalancerIPs: true
interfaces:
- ens160
nodeSelector:
matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
由于第三步已经确认 cnk8sw01-d 为 Lease Holder,因此接着检查该节点实际的网络接口:
bash
ip a | grep ens
输出如下:
text
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
inet 172.20.107.56/24 brd 172.20.107.255 scope global ens18
另一方面,控制节点 (cnk8sm01-d) 的网络接口则为:
bash
ip a | grep ens
输出如下:
text
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
inet 172.20.107.53/24 brd 172.20.107.255 scope global ens160
可以发现,不同节点使用了不同的网络接口名称:
| 节点 | 网络接口 |
|---|---|
cnk8sm01-d |
ens160 |
cnk8sw01-d |
ens18 |
由于 CiliumL2AnnouncementPolicy 指定只能使用 ens160,而实际负责宣告 VIP 的 Worker 节点只有 ens18,因此一度怀疑 Cilium 无法在正确的网络接口上发送 Gratuitous ARP,导致 VIP 无法被同网段设备学习。
这是排查过程中一个非常值得注意的线索,也是后续重点验证的方向。
第六步:建立专属的 L2 Announcement Policy 进行验证
前面发现 CiliumL2AnnouncementPolicy 中指定的网络接口与 Lease Holder 的实际接口名称不一致,因此开始怀疑是否为 L2 Announcement Policy 所造成的问题。
不过,由于现有的 CiliumL2AnnouncementPolicy 已被丛集中多个 LoadBalancer Service 使用,若直接修改既有 Policy,可能影响其他正式运行中的服务。
为了降低变更风险,我决定建立一个新的 CiliumL2AnnouncementPolicy,仅让 cn-lab-whs 的 LoadBalancer Service 套用,以验证是否真的是 Policy 配置所导致的问题。
建立新的 Policy:
bash
apiVersion: cilium.io/v2alpha1
kind: CiliumL2AnnouncementPolicy
metadata:
name: cn-lab-whs-l2announcement
spec:
serviceSelector:
matchExpressions:
- key: io.kubernetes.service.namespace
operator: In
values:
- cn-lab-whs
nodeSelector:
matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
interfaces:
- ens18
- ens160
externalIPs: true
loadBalancerIPs: true
套用设定:
bash
kubectl apply -f cn-lab-whs-l2announcement.yaml
输出如下:
bash
ciliuml2announcementpolicy.cilium.io/cn-lab-whs-l2announcement created
建立完成后,由于新的 Policy 仅匹配 cn-lab-whs Namespace 中的 Service,因此不会影响其他 LoadBalancer Service,可安全地验证此次的假设。
接下来重新测试 VIP 的 ARP 响应情况,确认新的 Policy 是否已生效。
第七步:重新验证 ARP 回应
套用新的 CiliumL2AnnouncementPolicy 后,再次查看 Neighbor Cache:
bash
ip neigh show | grep 172.20.107.230
输出如下:
bash
172.20.107.230 dev ens160 lladdr 00:0c:29:f1:89:68 STALE
虽然 Neighbor Cache 已经存在 172.20.107.230 对应的 MAC 地址,但 STALE 仅表示 Linux 保留了一笔邻居缓存记录,无法确认这笔资料是否为目前重新学习而来,或只是先前留下的缓存记录。
因此,为了重新验证 ARP 流程,先删除该笔 Neighbor Cache:
bash
ip neigh del 172.20.107.230 dev ens160
确认已成功删除:
bash
ip neigh show | grep 172.20.107.230
若没有任何输出,即表示 Neighbor Cache 已清除。
接着,主动发送 ARP Request:
bash
arping 172.20.107.230 -c 4
此时,Lease Holder (cnk8sw01-d) 应重新回复 ARP,Linux 也会重新建立 Neighbor Cache。
再次确认:
bash
ip neigh show | grep 172.20.107.230
以及:
bash
arp -an | grep 172.20.107.230
输出如下:
bash
(172.20.107.230) at 00:0c:29:f1:89:68 [ether] on ens160
最后,再到 Lease Holder 查询网络接口的 MAC:
bash
ip link show ens18
可以确认 00:0c:29:f1:89:68 正是 cnk8sw01-d 的 MAC 地址。
至此可以证明,172.20.107.230 的 ARP 响应确实来自 Lease Holder,而非旧的 Neighbor Cache,表示 Cilium L2 Announcement 已正常运作。
结论
这次故障排查一开始以为是 Cilium L2 Announcement 没有正常运作,但随着一步步验证,逐渐排除了多个可能性。
整个排查过程依序确认了:
• LoadBalancer Service 已成功取得 VIP,表示 LB IPAM 正常运作。
• Kubernetes Lease 已成功建立,Leader Election 正常完成。
• Cilium Agent 成功取得 Lease,代表 VIP 已指派由特定节点负责宣告。
• 透过 tcpdump、arping、ip neigh 及 arp -an 验证后,确认 Lease Holder 已能正常回应 ARP,VIP 的 L2 Announcement 功能运作正常。
其中,最容易让人误判的是 ip neigh 显示的 STALE 状态。STALE 并不代表故障,而是表示 Linux Neighbor Cache 中已存在对应的 MAC 地址。若要确认是否真的由目前的 Lease Holder 响应 ARP,建议先删除 Neighbor Cache,再利用 arping 主动触发 ARP Resolution,重新建立 Neighbor Cache,才能确认目前的 ARP 回应来源。
此外,在排查过程中并未直接修改既有的 CiliumL2AnnouncementPolicy,而是建立一组仅作用于 cn-lab-whs Service 的新 Policy 进行验证。这种方式能有效降低对既有 LoadBalancer Service 的影响,也是一种值得采用的变更策略。
回头来看,这次最大的收获并不是单纯解决了一个 VIP 无法存取的问题,而是厘清了 Cilium L2 Announcement 的完整运作流程:
bash
LoadBalancer Service
│
▼
LB IPAM 分配 VIP
│
▼
Leader Election(Lease)
│
▼
Lease Holder 回应 ARP
│
▼
Client 建立 Neighbor Cache
│
▼
流量进入 Kubernetes Node
│
▼
Cilium BPF 转发
│
▼
Backend Pod
当未来再次遇到 LoadBalancer VIP 无法联机的问题时,只要依照 VIP 分配 → Lease → Cilium Agent → ARP → Neighbor Cache → Service → Endpoint 的顺序逐层验证,就能快速缩小问题范围,而不是一开始就怀疑整个 Cilium 或 Kubernetes 网络。