cilium/cilium · error
cidr %s has already been allocated
Error message
cidr %s has already been allocated
What it means
occupyCIDR walks the pool's CIDRAllocators, finds the one whose range contains the given prefix, and refuses to occupy it if IsAllocated reports the prefix already claimed. This guards the multi-pool allocator against double-allocation of a pod CIDR within a pool.
Source
Thrown at operator/pkg/ipam/allocator/multipool/pool.go:41
}
return alloc.AllocateNext()
}
return netip.Prefix{}, errPoolEmpty
}
func occupyCIDR(allocators []cidralloc.CIDRAllocator, cidr netip.Prefix) error {
for _, alloc := range allocators {
if !alloc.InRange(cidr) {
continue
}
allocated, err := alloc.IsAllocated(cidr)
if err != nil {
return err
}
if allocated {
return fmt.Errorf("cidr %s has already been allocated", cidr)
}
return alloc.Occupy(cidr)
}
return fmt.Errorf("cidr %s is not part of the requested pool", cidr)
}
func releaseCIDR(allocators []cidralloc.CIDRAllocator, cidr netip.Prefix) error {
for _, alloc := range allocators {
if !alloc.InRange(cidr) {
continue
}
allocated, err := alloc.IsAllocated(cidr)
if err != nil {
return err
}View on GitHub (pinned to ac7b90affa)
Solutions
- Determine which node currently holds the CIDR (check CiliumNode Status.IPAM / pool allocations) and reconcile the stale node instead of re-occupying.
- Ensure only one operator instance runs (leader election enabled) — duplicate allocators cause exactly this double-occupy.
- Delete/restart the stale CiliumNode object so the agent re-requests a fresh CIDR from the current pool state.
- If it comes from unorphanCIDR, verify the orphaned CIDR is still actually used by that node (check the node's PodCIDRs/pools) before marking it allocated.
Example fix
// before: blindly occupy on node upsert
if err := p.occupyCIDR(node.Name, pool, cidr); err != nil { ... }
// after: check current owner first
if alloc, err := alloc.IsAllocated(cidr); err == nil && alloc {
// skip: CIDR already owned; let node reconcile release or re-request
continue
}
return p.occupyCIDR(node.Name, pool, cidr) Defensive patterns
Strategy: validation
Validate before calling
// before occupying, check current ownership
occupied, err := alloc.IsAllocated(cidr)
if err != nil { return err }
if occupied {
// resolve owner first; do not force occupy
return fmt.Errorf("cidr %s owned by another node", cidr)
} Type guard
func cidrInPool(allocators []cidralloc.CIDRAllocator, cidr netip.Prefix) bool {
for _, a := range allocators {
if a.InRange(cidr) { return true }
}
return false
} Try / catch
if err := p.OccupyCIDR(node, pool, cidr); err != nil {
if strings.Contains(err.Error(), "has already been allocated") {
// reconcile stale node instead of retrying occupy
releaseStaleNodeAllocations(node)
return nil
}
return err
} Prevention
- Run a single operator instance with leader election.
- Before re-occupying, check which node currently holds the CIDR in CiliumNode specs.
- Clear stale PodCIDRs/pool allocations on node replacement.
When it happens
Trigger: PoolAllocator.occupyCIDR(node, pool, cidr) is called with a prefix that is inside a pool allocator's range but already occupied — e.g. unorphanCIDR reclaiming an orphaned CIDR that another node has since been allocated, or a CiliumNode re-registration racing a fresh allocation of the same CIDR.
Common situations: Stale node state: a deleted node's CIDRs were released and re-allocated to another node, then the old node's status (still listing the CIDR) triggers occupy again; two operator replicas running simultaneously; restoring state from an old snapshot.
Related errors
- not ready
- no cilium agent pods found
- unable to detect minimum Cilium version
- unable to initialize IPv4 allocator: %w
- unable to initialize IPv6 allocator: %w
AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31).
Data as JSON: /api/errors/74481a208584daf8.
Report an issue: GitHub.