# k8s/cilium/l2-announcement-policy.yaml # CiliumL2AnnouncementPolicy — without this, LB-IPAM (lb-ipam-pool.yaml) # assigns IPs to LoadBalancer Services but nothing ARPs for them on the LAN, # so they're unreachable from outside the cluster even though `kubectl get # svc` shows a real EXTERNAL-IP. Confirmed both forgejo's .165 and # shadowsocks' .166 were 100% packet loss / incomplete ARP before this. # # loadBalancerIPs: true makes Cilium announce every Service's LB-IPAM IP via # ARP from whichever node currently holds the lease for it (one node per IP, # decided by leaderElection — not all nodes simultaneously, which would # otherwise cause ARP flapping/duplicate-IP confusion on the LAN). # # externalIPs/loadBalancerIPs split exists because Cilium also supports # announcing Service externalIPs (a different field, unused in this repo); # we only need loadBalancerIPs since every exposed Service here is type # LoadBalancer via lb-ipam-pool.yaml. # # requires kube-proxy replacement (already the case — see # k8s/talos-iam or helmfile.yaml.gotmpl kubeProxyReplacement=true) and a # Cilium build with L2 announcements enabled (default since v1.14). # # Apply once after cluster bootstrap, alongside lb-ipam-pool.yaml: # kubectl apply -f k8s/cilium/l2-announcement-policy.yaml # # Verify: # kubectl get ciliuml2announcementpolicy # ping 192.168.1.165 && ping 192.168.1.166 # both should now respond # arp -a | grep 192.168.1.16 # should resolve to a real MAC apiVersion: cilium.io/v2alpha1 kind: CiliumL2AnnouncementPolicy metadata: name: homelab-l2-announce spec: loadBalancerIPs: true interfaces: - eno1 # No nodeSelector restriction — all 3 nodes already run workloads # (allowSchedulingOnControlPlanes: true in controlplane.yaml), and with # 3 zone-labeled nodes, redundancy for per-IP leader election is maintained.