agent: addresses annotation replaces IPAM allocation
Build flock Image / build (push) Successful in 5m27s
Build flock Image / build (push) Successful in 5m27s
When flock.fritzlab.net/addresses provides a v6 or v4, the IP becomes the pod's primary IP for that family — bound to eth0, default route off it, on-link host route via setHostRoute, and a per-pod /128 or /32 in BGP. IPAM no longer allocates a private IP alongside it. The pod ends up with exactly the operator-supplied addresses on eth0 (plus any extras beyond the first-of-family, which keep the pre-existing layered behavior). This is the fix the original addresses-annotation work missed: bug #1 allocated a private IP next to the public one (so VPN-routed clients could land on the private path on Plex). Promoting addresses-supplied IPs into the IPAM-style routing slot keeps the public IP as the only primary IP visible from outside. Three pieces: - annotations.go: reject pods whose addresses/anycast IP family is disabled (ipv6/ipv4 annotation or NodeConfig default). Both annotation types rely on the family being enabled for return-path routing. - handlers.go: peel first v6 + first v4 from Addresses into res.IP6/IP4; suppress IPAM for those families; skip IPAM call entirely if both families are addresses-supplied. - anycast_linux.go: extend renderBird to advertise any IPAM IP that's outside the node's BGP aggregate as a per-pod /32 or /128. This is what makes 142.202.202.166 reachable when host004's pod CIDR is 172.25.214.0/24 — the addresses-promoted IP isn't covered by the aggregate. Tests: 7 new annotation tests covering the conflict cases (ipv4=false + addresses-v4, NodeConfig default + addresses-v4, etc.) plus 5 unit tests for the splitAddressesPrimary helper. README updated with the addresses-replaces-IPAM behavior, the addresses-vs-anycast comparison, the conflict rule, and a Plex-style example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+57
-7
@@ -140,21 +140,44 @@ func (h *PodHandler) Add(ctx context.Context, req flockcni.Request) (*current.Re
|
||||
}
|
||||
ipAlgo := ResolveIPAlgo(pod.Annotations, nodeAnn, h.Logger)
|
||||
|
||||
// addresses-annotation IPs replace IPAM allocation for any family they
|
||||
// cover. Plex needs its public IPv4 to be the pod's primary v4 (default
|
||||
// route source, on-link host route, /32 in BGP) — not just an extra IP
|
||||
// layered on top of a private IPAM allocation. Peel one v6 + one v4 out
|
||||
// of Addresses to use as the pod's primary IPs; anything beyond that
|
||||
// stays in addrExtras and gets the existing layered behavior.
|
||||
addrV6, addrV4, addrExtras := splitAddressesPrimary(parsed.Addresses)
|
||||
|
||||
allocReq := AllocRequest{
|
||||
ContainerID: req.ContainerID,
|
||||
Namespace: args.PodNamespace,
|
||||
Pod: args.PodName,
|
||||
App: deriveAppName(pod),
|
||||
WantV6: parsed.WantV6,
|
||||
WantV4: parsed.WantV4,
|
||||
WantV6: parsed.WantV6 && addrV6 == nil,
|
||||
WantV4: parsed.WantV4 && addrV4 == nil,
|
||||
AnnCIDR6: parsed.CIDR6,
|
||||
AnnCIDR4: parsed.CIDR4,
|
||||
IPAlgo: ipAlgo,
|
||||
Image: podImageRef(pod),
|
||||
}
|
||||
res, err := h.IPAM.Allocate(allocReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ipam: %w", err)
|
||||
var res AllocResult
|
||||
if allocReq.WantV6 || allocReq.WantV4 {
|
||||
var err error
|
||||
res, err = h.IPAM.Allocate(allocReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ipam: %w", err)
|
||||
}
|
||||
}
|
||||
// Promote the peeled addresses IPs into the primary slots. They get the
|
||||
// IPAM-style routing path: bound to eth0 in configurePodSide, default
|
||||
// route via fe80::1 / v4ProxyGW, on-link host route via setHostRoute.
|
||||
// BGP advertisement of the /32/128 is handled by the AnycastReconciler
|
||||
// via renderBird's outside-aggregate detection.
|
||||
if addrV6 != nil {
|
||||
res.IP6 = addrV6
|
||||
}
|
||||
if addrV4 != nil {
|
||||
res.IP4 = addrV4
|
||||
}
|
||||
|
||||
// Persist pending entry before any netlink work so a crash mid-ADD
|
||||
@@ -167,7 +190,7 @@ func (h *PodHandler) Add(ctx context.Context, req flockcni.Request) (*current.Re
|
||||
IP6: ipString(res.IP6),
|
||||
IP4: ipString(res.IP4),
|
||||
Anycast: anycastStrings(parsed.Anycast),
|
||||
Addresses: anycastStrings(parsed.Addresses),
|
||||
Addresses: anycastStrings(addrExtras),
|
||||
State: StatePending,
|
||||
AllocatedAt: time.Now().UTC(),
|
||||
}
|
||||
@@ -184,7 +207,7 @@ func (h *PodHandler) Add(ctx context.Context, req flockcni.Request) (*current.Re
|
||||
IP6: res.IP6,
|
||||
IP4: res.IP4,
|
||||
Anycast: parsed.Anycast,
|
||||
Addresses: parsed.Addresses,
|
||||
Addresses: addrExtras,
|
||||
}
|
||||
if err := h.SetupFunc(setup); err != nil {
|
||||
// Roll forward: leave pending entry in place so startup GC can clean
|
||||
@@ -270,6 +293,33 @@ func ipString(ip net.IP) string {
|
||||
return canonical(ip)
|
||||
}
|
||||
|
||||
// splitAddressesPrimary peels off the first IPv6 and first IPv4 from the
|
||||
// addresses list to use as the pod's primary IPs in place of an IPAM
|
||||
// allocation. The remaining entries (anything beyond the first of each
|
||||
// family) stay in extras for the existing layered eth0 binding via the
|
||||
// AnycastReconciler's via-route path.
|
||||
//
|
||||
// Order of the input is preserved in extras. Either of v6/v4 may be nil
|
||||
// when the addresses list contains no IP of that family — the caller falls
|
||||
// back to IPAM allocation in that case.
|
||||
func splitAddressesPrimary(ips []net.IP) (v6, v4 net.IP, extras []net.IP) {
|
||||
for _, ip := range ips {
|
||||
if ip.To4() != nil {
|
||||
if v4 == nil {
|
||||
v4 = ip.To4()
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if v6 == nil {
|
||||
v6 = ip.To16()
|
||||
continue
|
||||
}
|
||||
}
|
||||
extras = append(extras, ip)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func anycastStrings(ips []net.IP) []string {
|
||||
if len(ips) == 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user