一次 Cilium L2 Announcement 导致 LoadBalancer VIP 无法访问的排查记录

文章目录

  • 环境
  • 现象
    • [代表没有任何节点回应 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 网络。