Problem
deallocateIPAM creates a fresh PoolAllocator and calls Deallocate on it. Since each CNI invocation is a separate process, the allocations from cmdAdd are never visible to cmdDel. The Deallocate call is a no-op — subnets are never returned to the pool.
Partial fix applied (2026-06-30): CRD annotation tracking is now in place:
publishBGPState (cni.go:624-627) writes the allocated subnet to a BGPAdvertisement annotation during ADD.
getAllocatedSubnetFromCRD (cni.go:1111-1138) reads the annotation during DEL to know which subnet to deallocate.
What is still broken: The core architectural bug. deallocateIPAM (cni.go:1104) still creates a fresh PoolAllocator, and Deallocate (ipam.go:89) only removes from that in-memory sync.Map. The annotation tracking ensures the correct subnet CIDR is known at deallocation time, but the deallocation itself still operates on a blank slate.
Files: internal/cni/cni.go:1080-1108 (partial fix), ipam.go:27-33,89 (root cause)
Impact
On nodes with sustained pod churn, the in-memory pool will eventually exhaust, causing new pod attachments to fail with "pool exhausted" errors. This is a production stability risk on any node that sees more than ~2^32 / 2^32 = 1 pod churn cycle (i.e., practically any node with more than ~65K pods over its lifetime).
Recommended Fix
Persist allocations in the BGPAdvertisement CRD annotation/status and have deallocateIPAM mark them as released via a shared backend:
// Option A: CRD-based deallocation
func deallocateIPAM(ctx context.Context, k8sClient client.Client, vpcAttachmentID string, subnetCIDR string) error {
// Read BGPAdvertisement CRD to find the annotation
// Update the CRD annotation to mark the subnet as released
// Alternatively, use a dedicated IPAM CRD for state
return nil
}
// Option B: Shared singleton backed by a distributed store (e.g., etcd/Redis)
// Less ideal for a CNI plugin — adds external dependency
The simplest approach: during DEL, update the BGPAdvertisement CRD annotation to indicate the subnet is released, and maintain a separate "released" set that Allocate() checks before handing out subnets.
Acceptance Criteria
Problem
deallocateIPAMcreates a freshPoolAllocatorand callsDeallocateon it. Since each CNI invocation is a separate process, the allocations fromcmdAddare never visible tocmdDel. TheDeallocatecall is a no-op — subnets are never returned to the pool.Partial fix applied (2026-06-30): CRD annotation tracking is now in place:
publishBGPState(cni.go:624-627) writes the allocated subnet to a BGPAdvertisement annotation during ADD.getAllocatedSubnetFromCRD(cni.go:1111-1138) reads the annotation during DEL to know which subnet to deallocate.What is still broken: The core architectural bug.
deallocateIPAM(cni.go:1104) still creates a freshPoolAllocator, andDeallocate(ipam.go:89) only removes from that in-memorysync.Map. The annotation tracking ensures the correct subnet CIDR is known at deallocation time, but the deallocation itself still operates on a blank slate.Files:
internal/cni/cni.go:1080-1108(partial fix),ipam.go:27-33,89(root cause)Impact
On nodes with sustained pod churn, the in-memory pool will eventually exhaust, causing new pod attachments to fail with "pool exhausted" errors. This is a production stability risk on any node that sees more than ~2^32 / 2^32 = 1 pod churn cycle (i.e., practically any node with more than ~65K pods over its lifetime).
Recommended Fix
Persist allocations in the BGPAdvertisement CRD annotation/status and have
deallocateIPAMmark them as released via a shared backend:The simplest approach: during DEL, update the BGPAdvertisement CRD annotation to indicate the subnet is released, and maintain a separate "released" set that
Allocate()checks before handing out subnets.Acceptance Criteria
deallocateIPAMupdates a persistent store (CRD annotation) to mark subnet as releasedcmdAddon the same node can see released subnets and reuse themdeallocateIPAMis tested with a mock K8s client that simulates the CRD annotation flow