Summary:
In stateless-CNI mode (ManageEndpointState=true), CNS handles CNI ADD and DEL as two independent, unsynchronized sequences that mutate two different stores in opposite order:
| Operation |
1st mutation |
2nd mutation |
ADD (requestipconfigs) |
in-memory IPAM (assign IP) |
endpoint statefile (write owner) |
DEL (releaseipconfigs) |
endpoint statefile (delete owner) |
in-memory IPAM (free IP) |
The CNI runtime may issue a cleanup DEL while an ADD for the same infra container is still in flight. When that happens, and when there is a delay in processing the original ADD, the two sequences may interleave and cross:
DEL: remove endpoint entry -> no entry yet, no-op
ADD: assign IP in memory -> IP = Assigned
ADD: write endpoint entry -> statefile now says "containerX owns 10.x.y.z"
DEL: release IP in memory -> IP = Available
CNS state is now corrupt: its memory IPAM pool says the IP is free, while its persisted endpoint statefile says the IP is owned by a container that no longer exists. CNS can later hands that same IP to a different pod, because it appears free in-memory, and it then writes a second endpoint entry for it to the statefile.
On the next CNS restart, endpointStateToPodInfoByIP() walks the statefile, sees the same IP owned by two container IDs, and returns ErrDuplicateIP. This is a terminal, unrecoverable error: CNS never finishes reconciliation and can never safely serve IPAM. Restarting CNS does not help, because the corruption is on disk.
Fixes: CNS should use Tombstone Deletes - processing a DEL should prevent a future ADD for the same containerid. CNS ADD/DEL should be synchronized and exclusive for the same containerid.
Mitigation / recovery for an already-broken node
Replace the node. Restarting is CNS or the Node is not sufficient.
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
# then delete/recreate the node via the node pool
⚠️ Do not simply delete the whole azure-endpoints.json. In stateless-CNI mode that file is the source of truth CNS uses to rebuild IPAM mappings on restart. Deleting it makes CNS believe every IP is free while running pods still hold them, which can cause genuine duplicate assignment and IP leaks.
Summary:
In stateless-CNI mode (
ManageEndpointState=true), CNS handles CNI ADD and DEL as two independent, unsynchronized sequences that mutate two different stores in opposite order:requestipconfigs)releaseipconfigs)The CNI runtime may issue a cleanup DEL while an ADD for the same infra container is still in flight. When that happens, and when there is a delay in processing the original ADD, the two sequences may interleave and cross:
CNS state is now corrupt: its memory IPAM pool says the IP is free, while its persisted endpoint statefile says the IP is owned by a container that no longer exists. CNS can later hands that same IP to a different pod, because it appears free in-memory, and it then writes a second endpoint entry for it to the statefile.
On the next CNS restart,
endpointStateToPodInfoByIP()walks the statefile, sees the same IP owned by two container IDs, and returnsErrDuplicateIP. This is a terminal, unrecoverable error: CNS never finishes reconciliation and can never safely serve IPAM. Restarting CNS does not help, because the corruption is on disk.Fixes: CNS should use Tombstone Deletes - processing a DEL should prevent a future ADD for the same containerid. CNS ADD/DEL should be synchronized and exclusive for the same containerid.
Mitigation / recovery for an already-broken node
Replace the node. Restarting is CNS or the Node is not sufficient.