Compare commits

...
Author SHA1 Message Date
claude-botandClaude Fable 5.1 7a6cbc4e2e feat(talos): move the node IP and etcd to pod0 (#576)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Pod east-west has to leave the management VLAN, and the node IP is the
only thing that controls where it goes. Three tests established that,
rather than argument:

  - --ipv4-node / --ipv6-node, applied via CiliumNodeConfig: the agent
    logged receiving both and still published its mgmt0 address.
  - directRoutingDevice=pod0: the agent logged "Direct routing device
    detected direct-routing-device=pod0" and still installed
    10.244.2.0/24 via 10.2.40.138 dev mgmt0.
  - Both routing modes, both address families, always keyed to the
    peer's Kubernetes InternalIP.

etcd is pinned to the same segment. Left unset it advertises "the first
routable address of the node", which is not deterministic once the node
is re-addressed, so it has to be stated either way. pod0 is a closed 10G
L2 between the three nodes; the management VLAN is about to become a
single 1G NIC shared with everything else management does, which is the
wrong link for quorum traffic. listenSubnets defaults to this, so peer
and client listeners move together.

This does move the etcd peer URLs off 10.2.40.x, so unlike the node IP
it is not a no-op: one node at a time, waiting for a healthy quorum
between each.

The API VIP stays on mgmt0; it is a Layer2VIPConfig and does not follow
the node IP.

Apply one node at a time. Each node re-IPs, so per the note in
k8s-1.yaml (#635) kube-apiserver keeps advertising the old address until
its static pod restarts: after each node, check that
`kubectl get endpoints kubernetes -n default` agrees with
`kubectl get nodes -o wide` and reboot the node if it does not. The
cilium agent reads the node IP only at startup, so it needs recreating
on each node as well.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 16:54:57 +02:00
k.renner ee2d5d1d25 Merge pull request 'feat(cilium): peer BGP over the ingress VLAN (#576)' (#836) from feat/bgp-peer-ingress into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
ci/woodpecker/cron/issue-fix Pipeline was successful
Reviewed-on: k.renner/homelab#836
2026-09-08 13:22:05 +00:00
claude-botandClaude Fable 5.1 60b4286910 feat(cilium): peer BGP over the ingress VLAN (#576)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Phase B step 2 of the traffic-class split. The eBGP session source
address is what the UDM installs as the next-hop for every PodCIDR and
LoadBalancer prefix the cluster advertises. Peering over the management
LAN therefore pinned all world->pod return traffic to the 1G mgmt link,
which is the thing #576 exists to fix. Moving the peers onto VLAN 47
puts that traffic on the 10G ingress link instead.

No localAddress or sourceInterface override is needed: the kernel
already picks the ingress0 address for both families.

  ip -6 route get fd02:47::1
  fd02:47::1 dev ingress0 proto kernel src fd02:47::5a47:caff:fe74:7027

Both peer addresses were verified reachable from the cluster before
this change -- 10.2.47.1 and fd02:47::1 each answer HTTP with a 301,
and the UDM's neighbour entry on ingress0 is REACHABLE.

The UDM already accepts sessions from this segment: PR #835 added the
10.2.47.0/24 and fd02:47::/64 listen ranges and is uploaded. Its VLAN 40
ranges stay in place for now and get dropped in step 3, once every
session is confirmed established on 47.

gracefulRestart (120s) covers the peer replacement, so the UDM holds the
learned routes across the flap rather than withdrawing the LB VIPs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:20:13 +02:00
k.renner cc52181352 Merge pull request 'feat(bgp): let the UDM accept sessions from the ingress VLAN too' (#835) from feat/bgp-listen-ingress into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#835
2026-09-08 13:00:53 +00:00
2 changed files with 34 additions and 18 deletions
@@ -25,13 +25,13 @@ spec:
stop: "fd99::ff"
---
# Cilium BGPv2 control-plane config peering each node with the UDM over the
# management LAN — never over v30iot, which is the untrusted IoT segment.
# ingress VLAN 47 — never over v30iot, which is the untrusted IoT segment.
# The source address comes from the kernel routing decision, so no explicit
# localAddress override is needed.
#
# Two parallel sessions, one per address family:
# - v4 session: peer 10.2.40.1, family IPv4 unicast only
# - v6 session: peer fd40::1, family IPv6 unicast only
# - v4 session: peer 10.2.47.1, family IPv4 unicast only
# - v6 session: peer fd02:47::1, family IPv6 unicast only
# This avoids the "v6 NLRIs over v4 transport" path, which requires
# bilateral extended-nexthop capability negotiation (RFC 5549/8950).
# Cilium 1.19 BGPv2 advertises the capability only for the IPv4 Unicast
@@ -74,14 +74,14 @@ spec:
peers:
- name: udm-v6
peerASN: 64512
# ULA — stable across ISP renumber events. UDM's br40 has
# fd40::1/64; nodes get fd40::<eui-64> via SLAAC on eth0.
peerAddress: fd40::1
# ULA — stable across ISP renumber events. UDM's br47 has
# fd02:47::1/64; nodes get fd02:47::<eui-64> via SLAAC on ingress0.
peerAddress: fd02:47::1
peerConfigRef:
name: bgp-peer-udm-v6
- name: udm-v4
peerASN: 64512
peerAddress: 10.2.40.1
peerAddress: 10.2.47.1
peerConfigRef:
name: bgp-peer-udm-v4
---
+27 -11
View File
@@ -7,16 +7,32 @@
# part of the config that could not be true anywhere but here.
machine:
kubelet:
# Constrain kubelet's auto-picked node IP to the mgmt LAN. Without this,
# kubelet picks the first non-loopback IP, which could land on the v30iot
# NIC (ens19, 10.2.30.0/24). Control-plane traffic and hostNetwork pod IPs
# must stay on mgmt LAN.
# fd40::/64 is the mgmt LAN ULA, and it is listed so the Node object carries
# an IPv6 InternalIP at all. Without one, Cilium falls back to detecting the
# node's IPv6 from mgmt0 and picks the SLAAC address from the dynamic
# provider prefix, which it then probes until the agent restarts (#789).
# The GUA is not a control-plane address: BGP peers on fd40::/64 too.
# Pins the node IP to pod0 (VLAN 48), which is what carries pod east-west.
# Cilium reaches a peer's pods at that peer's Kubernetes InternalIP and
# offers no way to point it elsewhere: --ipv4-node/--ipv6-node are accepted
# and then overwritten from the Node object, and directRoutingDevice only
# selects the NodePort/DSR device. So the node IP is the only control over
# which segment east-west uses, in both tunnel and native routing mode.
#
# Unpinned, kubelet takes the first non-loopback address, which could land
# on the v30iot NIC (ens19, 10.2.30.0/24).
#
# fd02:48::/64 is listed so the Node object carries an IPv6 InternalIP at
# all. Without one, Cilium detects the node's IPv6 itself and picks the
# SLAAC address from the dynamic provider prefix, then probes it until the
# agent restarts (#789).
nodeIP:
validSubnets:
- 10.2.40.0/24
- fd40::/64
- 10.2.48.0/24
- fd02:48::/64
cluster:
etcd:
# Pins etcd to pod0 with the node IP. Unset, etcd advertises "the first
# routable address of the node", which is not deterministic across a
# re-address. listenSubnets defaults to this.
#
# pod0 is a closed 10G L2 between the three nodes. The management VLAN is
# about to become a single 1G NIC shared with everything else management
# does, which is the wrong link for quorum traffic.
advertisedSubnets:
- 10.2.48.0/24