M6: anycast — pod lo + Ready-gated /128/32 + BIRD export
Build flock Image / build (push) Has been cancelled

CNI ADD now adds anycast IPs to the pod's lo interface (NOT eth0 — design
doc rationale: avoid NDP/ARP DAD conflicts when N replicas share an IP).
Allocation persists the anycast list.

AnycastReconciler:
  desired = { ip → flock<8hex> } from
            committed allocations × pod.Status.PodReady=True
  diff against advertised, install/remove host /128 (v6) or /32 (v4)
  re-render bird.conf with the active set

Triggers: 2s tick, AfterCommit (per ADD/DEL), Pod informer Ready
transitions (PodCache.OnReadyChange callback).

The bird template already supported Anycast6/Anycast4 via the export
filter — this turn finally drives those slices from runtime.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Donavan Fritz
2026-04-25 07:36:47 -05:00
parent c7fb159632
commit 89a3502446
7 changed files with 352 additions and 15 deletions
+16 -10
View File
@@ -74,23 +74,31 @@ func (s *Server) configureRuntime(ctx context.Context) error {
// Calico is fenced off this node (Tigera Installation CR adds a
// nodeAffinity excluding flock.fritzlab.net/agent on
// calicoNodeDaemonSet). flock now owns BGP from this host.
if err := bird.Render(nc, nil, nil, routerIDFromNodeIP(s.restCfg)); err != nil {
routerID := routerIDFromNodeIP(s.restCfg)
if err := bird.Render(nc, nil, nil, routerID); err != nil {
s.Logger.Warn("initial bird render", "err", err)
}
// AnycastReconciler is the single owner of bird re-renders going
// forward. It runs every 2s + on Pod readiness changes + on each
// successful CNI ADD/DEL.
anycast := NewAnycastReconciler(s.Node, s.Store, pods, s.NodeConfig, bird, routerID, s.Logger)
pods.OnReadyChange(anycast.Trigger)
go anycast.Run(ctx)
// Background tick for SummaryRoutes (idempotent) in case the kernel
// blackhole disappears for any reason.
go func() {
t := time.NewTicker(15 * time.Second)
t := time.NewTicker(60 * time.Second)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
cur := s.NodeConfig.Load()
if cur == nil {
continue
if cur := s.NodeConfig.Load(); cur != nil {
_ = bird.SummaryRoutes(cur)
}
_ = bird.SummaryRoutes(cur)
_ = bird.Render(cur, nil, nil, routerIDFromNodeIP(s.restCfg))
}
}
}()
@@ -103,9 +111,7 @@ func (s *Server) configureRuntime(ctx context.Context) error {
NodeConfig: s.NodeConfig,
SetupFunc: Setup,
TeardownFunc: Teardown,
AfterCommit: func() {
// Future: collect anycast IPs from store snapshot, re-render bird.
},
AfterCommit: anycast.Trigger,
}
s.RPC.SetHandlers(handler.Add, handler.Del, handler.Check)
s.Logger.Info("runtime ready",