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:
@@ -313,6 +313,97 @@ func TestParseAnnotations_Anycast_Mixed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations_Addresses_Mixed(t *testing.T) {
|
||||
// Plex's case: one v6 and one v4 supplied via addresses, both families
|
||||
// enabled (built-in defaults). Both IPs are recorded; conflict check
|
||||
// passes; later in handlers.Add they get peeled into primary slots.
|
||||
a, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "addresses": "2602:817:3000:c606::166, 142.202.202.166",
|
||||
}, BuiltinFamilyDefaults())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(a.Addresses) != 2 {
|
||||
t.Fatalf("addresses len=%d", len(a.Addresses))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations_Addresses_ConflictV4Disabled(t *testing.T) {
|
||||
// addresses contains a v4 but the pod has explicitly opted out of v4.
|
||||
// The IP would land on eth0 with no default v4 route, so reject at ADD.
|
||||
_, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "ipv4": "false",
|
||||
annotationPrefix + "addresses": "142.202.202.166",
|
||||
}, BuiltinFamilyDefaults())
|
||||
if err == nil {
|
||||
t.Fatal("want error for ipv4=false + addresses v4, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations_Addresses_ConflictV6Disabled(t *testing.T) {
|
||||
_, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "ipv6": "false",
|
||||
annotationPrefix + "ipv4": "true",
|
||||
annotationPrefix + "addresses": "2602:817:3000:c606::166",
|
||||
}, BuiltinFamilyDefaults())
|
||||
if err == nil {
|
||||
t.Fatal("want error for ipv6=false + addresses v6, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations_Anycast_ConflictV4Disabled(t *testing.T) {
|
||||
// Anycast on lo also requires the family enabled — replies need the
|
||||
// in-pod default v4 route off eth0, which only exists when v4 is on.
|
||||
_, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "ipv4": "false",
|
||||
annotationPrefix + "anycast": "172.25.255.1",
|
||||
}, BuiltinFamilyDefaults())
|
||||
if err == nil {
|
||||
t.Fatal("want error for ipv4=false + anycast v4, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations_Anycast_ConflictV6Disabled(t *testing.T) {
|
||||
_, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "ipv6": "false",
|
||||
annotationPrefix + "ipv4": "true",
|
||||
annotationPrefix + "anycast": "2602:817:3000:ac::1",
|
||||
}, BuiltinFamilyDefaults())
|
||||
if err == nil {
|
||||
t.Fatal("want error for ipv6=false + anycast v6, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations_Addresses_NodeDefaultV4Off(t *testing.T) {
|
||||
// NodeConfig default opts v4 off for the node, and the pod has no
|
||||
// explicit ipv4 annotation. addresses-v4 still conflicts because the
|
||||
// resolved WantV4 is false. Operator must add `ipv4: "true"` on the
|
||||
// pod to override the node default.
|
||||
defaults := FamilyDefaults{WantV6: true, WantV4: false}
|
||||
_, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "addresses": "142.202.202.166",
|
||||
}, defaults)
|
||||
if err == nil {
|
||||
t.Fatal("want error for NodeConfig v4=false + addresses v4, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations_Addresses_NodeDefaultV4Off_PodOptsBackIn(t *testing.T) {
|
||||
// Same as above but pod explicitly sets ipv4=true to override the node
|
||||
// default. Conflict resolved; parse succeeds.
|
||||
defaults := FamilyDefaults{WantV6: true, WantV4: false}
|
||||
a, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "ipv4": "true",
|
||||
annotationPrefix + "addresses": "142.202.202.166",
|
||||
}, defaults)
|
||||
if err != nil {
|
||||
t.Fatalf("expected ok, got %v", err)
|
||||
}
|
||||
if !a.WantV4 || len(a.Addresses) != 1 {
|
||||
t.Fatalf("unexpected: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCNIArgs(t *testing.T) {
|
||||
args := ParseCNIArgs("IgnoreUnknown=1;K8S_POD_NAMESPACE=mail;K8S_POD_NAME=stalwart-0;K8S_POD_INFRA_CONTAINER_ID=abc123")
|
||||
if args.PodNamespace != "mail" || args.PodName != "stalwart-0" || args.InfraID != "abc123" {
|
||||
|
||||
Reference in New Issue
Block a user