k8s(bgp-pool): set externalTrafficPolicy=Local on all BGP-routed LB services

Cilium BGPv2 advertises ETP=Local Service IPs only from nodes that host
a Ready endpoint, so the UDM stops ECMPing across non-backend nodes and
the in-cluster forwarding hop disappears. Multi-replica services
(traefik, kube-dns-lb, alloy-syslog, mosquitto-iot) keep one path per
replica-hosting node. Single-replica services (vscode, ftp-scanner,
syncthing-sync) lose nothing — they already had a single point of
failure on pod loss.

Stripped stale L2-era ETP=Cluster comments from ftp-scanner, mosquitto-iot,
and alloy-syslog. Those warnings cited the ARP-cache trap
(cilium-l2-etp-local-l3-routing-trap) and presumed an L2-announce
ingress topology; they don't apply to BGP-advertised VIPs because
Cilium's BGP control plane only originates the prefix from
endpoint-hosting nodes (verified against upstream docs for v1.19).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 18:53:44 +02:00
co-authored by Claude Opus 4.7
parent 65a5578143
commit 1146bc68d6
7 changed files with 7 additions and 20 deletions
@@ -22,13 +22,7 @@ metadata:
external-dns.alpha.kubernetes.io/hostname: mqtt.renner.uno
spec:
type: LoadBalancer
# ETP=Cluster (not Local) so the broker pod can be scheduled anywhere.
# Inbound IoT-VLAN traffic still arrives on k8s-edge (only node with
# v30iot), but kube-proxy/Cilium forwards to any backend cluster-wide.
# Source-IP preservation is lost — broker logs see a cluster node IP
# rather than the IoT device IP. Acceptable here: source-IP ACLs are
# not a security mechanism, mosquitto authn is via password/TLS.
externalTrafficPolicy: Cluster
externalTrafficPolicy: Local
selector:
app.kubernetes.io/name: mosquitto
ports:
@@ -13,6 +13,7 @@ metadata:
lb-pool: bgp
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app.kubernetes.io/name: home-assistant
ports:
@@ -235,11 +235,7 @@ metadata:
external-dns.alpha.kubernetes.io/hostname: syslog.k.renner.uno
spec:
type: LoadBalancer
# ETP=Cluster: matches the house pattern for the default 10.2.40.64-95
# services pool (ingress-nginx, kube-dns-lb). With L3 routing through the
# UDM gateway, ETP=Local would land traffic on a non-backend node and
# produce TCP RST / silent UDP drop — only L2-reachable clients would work.
externalTrafficPolicy: Cluster
externalTrafficPolicy: Local
selector:
app.kubernetes.io/name: alloy
app.kubernetes.io/instance: alloy-syslog
+1 -7
View File
@@ -123,13 +123,7 @@ metadata:
external-dns.alpha.kubernetes.io/hostname: ftp.paperless.renner.uno
spec:
type: LoadBalancer
# ETP=Cluster: scanner sits on a different VLAN, so the L2 lease can
# land on any node. ETP=Local would RST connections whenever the lease
# holder didn't host the pod (Cilium L2 lease ≠ pod-locality).
# See `cilium-l2-etp-local-l3-routing-trap` in the wiki. The
# allow-ftp-scanner CNP permits world+host+remote-node so masquerade
# vs. world identity doesn't matter here.
externalTrafficPolicy: Cluster
externalTrafficPolicy: Local
selector:
app.kubernetes.io/name: ftp-scanner
ports:
+1 -1
View File
@@ -148,7 +148,7 @@ metadata:
external-dns.alpha.kubernetes.io/hostname: syncthing.paperless.renner.uno
spec:
type: LoadBalancer
externalTrafficPolicy: Cluster
externalTrafficPolicy: Local
selector:
app.kubernetes.io/name: syncthing
ports:
@@ -11,6 +11,7 @@ metadata:
io.cilium/lb-ipam-ips: "10.2.99.83,fd99::83"
spec:
type: LoadBalancer
externalTrafficPolicy: Local
ipFamilyPolicy: PreferDualStack
ipFamilies:
- IPv4
@@ -88,6 +88,7 @@ spec:
enabled: true
spec:
type: LoadBalancer
externalTrafficPolicy: Local
ipFamilyPolicy: PreferDualStack
ipFamilies:
- IPv4