Dale Richardson created YUNIKORN-3374:
-----------------------------------------

             Summary: Phantom allocation when a node with an assumed pod is 
removed and re-added
                 Key: YUNIKORN-3374
                 URL: https://issues.apache.org/jira/browse/YUNIKORN-3374
             Project: Apache YuniKorn
          Issue Type: Bug
          Components: shim - kubernetes
            Reporter: Dale Richardson


When a node is removed while a pod is assumed on it (allocation made, bind not 
yet confirmed), the shim scheduler cache orphans the pod with the shim-invented 
{{Spec.NodeName}} still set. If the node re-appears (kubelet 
restart/re-registration, transient node object deletion and rejoin), the orphan 
adoption path re-assigns the never-bound pod to it and re-registers the task, 
and task recovery reports an existing allocation to the core for a pod that 
never ran: a phantom allocation that books queue and node capacity to nothing.

Found by the invariant property fuzzer of YUNIKORN-3373. Sibling of 
YUNIKORN-3355 (same phantom-allocation class, different trigger: a node flap 
instead of a failed bind).

*Mechanism* (all verified in code on master):
# {{SchedulerCache.removeNode}} ({{scheduler_cache.go}} ~198) iterates the 
NodeInfo's pods and orphans ALL of them: it deletes the {{assignedPods}} and 
{{assumedPods}} entries and puts the pod into {{orphanedPods}}. The 
assumed/bound distinction is erased, and an assumed pod's cached object keeps 
the {{Spec.NodeName}} stamped at assume time -- a value the shim invented, 
which the apiserver's pod object never had.
# {{updateNode}} on node (re-)add (~168) adopts any orphan whose 
{{Spec.NodeName}} matches the new node via {{updatePod}}: the phantom 
assignment is back.
# {{Context.addNode}} ({{context.go}} ~211-218) registers each adopted pod via 
{{updateYuniKornPod}} -> {{ensureAppAndTaskCreated}}. The original task is 
typically gone by then (the core released the node's allocations when the node 
was removed), so {{addTask}} re-creates it with metadata carrying the 
stale-NodeName pod, and the recovery path reports the pod as an existing 
allocation on the re-added node.
# The shim-cache half self-heals on the next informer update for the pod (it 
arrives unassigned), but the core-side allocation does not; it persists until 
released.

Note the asymmetry that makes this a bug: for a genuinely BOUND pod, 
orphan-and-adopt is correct -- its node name is real and the pod actually runs 
on the flapping node. Only the assumed-never-bound case adopts a fiction.

*Reachability*: requires a node delete + re-add around the assume-to-bind 
window of any pod. Node object flaps are routine at scale (kubelet 
re-registration, cloud node churn, node delete + rejoin), and the 
assume-to-bind window is always open somewhere on a busy cluster. In the 
YUNIKORN-3373 fuzzer, 50/50 seeds reach the state and observe the re-adoption 
(26,442 step observations over 250k steps; the fuzzer flaps nodes far more 
often than production, so this measures reachability, not frequency). A 
targeted regression test (assume, RemoveNode, UpdateNode, assert unassigned) 
fails on the unfixed code.

*Fix* (PR to follow): in {{removeNode}}, treat assumed pods the way the 
YUNIKORN-3355 fix treats them in {{forgetPod}}: revert instead of orphan -- 
drop the assumed/assigned state and re-store the pod unassigned (node name 
cleared on a copy). The pod is then simply rescheduled, which is the correct 
outcome as its target node is gone. Bound pods keep the existing orphan/adopt 
behaviour unchanged. The YUNIKORN-3373 fuzzer's invariants then guard the fix 
permanently.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to