Skip to content

fix: keep DRA devices held by non-pod consumers when their pods delete - #3215

Open
thc1006 wants to merge 2 commits into
kubernetes-sigs:mainfrom
thc1006:fix-dra-non-pod-consumer-release
Open

fix: keep DRA devices held by non-pod consumers when their pods delete#3215
thc1006 wants to merge 2 commits into
kubernetes-sigs:mainfrom
thc1006:fix-dra-non-pod-consumer-release

Conversation

@thc1006

@thc1006 thc1006 commented Aug 6, 2026

Copy link
Copy Markdown

Karpenter's provisioning loop reconstructs the set of already-allocated DRA devices before each scheduling simulation, and filters out the ones whose consumers are all going away so their capacity can be reused. That filter treated "every pod consumer is deleting" as "the device is free", but a ResourceClaim can also be reserved by a non-pod consumer, and those are invisible to the deleting-pod check.

The problem

gatherAllocatedDevices freed a device as soon as allConsumersDeleting returned true for its pod UIDs. The device metadata already carries a Releasable flag that is false when any referencing claim has a non-pod consumer (or an empty ReservedFor), and the sibling "no live consumers" shortcut right above it already respects that flag. The all-consumers-deleting shortcut did not.

So a device reserved by both a deleting pod and a non-pod consumer, or a shared device split across a deleting-pod claim and a separate non-pod claim, was handed back to the allocator as free while something still held it. The scheduling simulation would then see it as available and could allocate it a second time.

The fix

Two changes, both mirroring the guard that was already present for the no-live-consumers case:

  • Gate the all-consumers-deleting shortcut with meta.Releasable, so a device still held by a non-pod consumer is never freed through the deleting-pod path.
  • Carry each claim's Releasable onto its ContributionMetadata, and skip subtracting a non-releasable contribution's share in effectiveConsumedCapacity. This covers the shared-device case where a single claim mixes a deleting pod with a non-pod consumer, so its share stays reserved even though the pod is gone.

The per-device classification that gatherAllocatedDevices runs is pulled out into a small pure helper, deviceReallocation, so the reallocation decision (free, keep as exclusive, or keep with the remaining shared capacity) can be unit-tested without standing up the whole controller.

Testing

Added unit tests for both paths, each confirmed to fail before the change and pass after: a non-releasable contribution keeps its share in effectiveConsumedCapacity, and deviceReallocation keeps a device held by a non-pod consumer even when its pods are all deleting, alongside the freed, exclusive, and shared-partial branches. The full provisioning and deviceallocation suites pass.

Scope

This addresses the non-pod-consumer release path described in #3212. The other half of that issue, that Shared is derived from the presence of ConsumedCapacity rather than from the claim's ShareID, is a separate and more structural change that I have left out of this PR.

gatherAllocatedDevices freed an allocated device for reallocation as soon
as all of its pod consumers were deleting, without checking whether a
non-pod consumer still held it. A device kept in use by a non-pod consumer
(its metadata Releasable flag is false) could be handed back to the
allocator and allocated a second time.

Guard the all-consumers-deleting shortcut with Releasable, matching the
sibling no-live-consumers shortcut, and propagate each claim's Releasable
status onto its ContributionMetadata so a shared device's non-releasable
share is not subtracted when its pod deletes. The per-device classification
is extracted into deviceReallocation so the decision is unit-tested
directly.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: thc1006
Once this PR has been reviewed and has the lgtm label, please assign jonathan-innis for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow kubernetes-prow Bot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Aug 6, 2026
…the seed state

deviceReallocation freed a shared device whenever its effective consumed
capacity map came out empty. An empty map does not prove the last share is
gone: a surviving share can legitimately consume zero capacity, and
subtracting a deleting claim's contribution can empty the map while a live
pod or a non-pod consumer still holds the device. Freeing it then drops the
device from the allocator seed state, so its fixed shared counters can be
handed back to the pool and the allocator can over-admit.

Make the two consumer-liveness checks the only whole-device release
conditions, and always return a surviving shared device's effective capacity
as a non-nil map that may be empty. gatherAllocatedDevices then keeps the
device's ConsumedCapacity entry, recording the shared occupancy regardless
of the capacity value.

Add regressions for a live and a non-pod zero-capacity survivor (asserting
the returned map is non-nil but empty), a controller test that a mixed
pod/non-pod claim propagates a non-releasable contribution, and derive the
test helper's device-level releasability from its contributions.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006

thc1006 commented Aug 7, 2026

Copy link
Copy Markdown
Author

Pushed a follow-up for the zero-capacity case: deviceReallocation now frees a shared device only through the two consumer-liveness checks and otherwise keeps a non-nil (possibly empty) capacity map, so a live or non-pod zero-capacity share no longer drops the device from the seed state — and when rebasing onto #3210, invalid-capacity blocking should stay ahead of deletion-based reallocation.

@thc1006

thc1006 commented Aug 8, 2026

Copy link
Copy Markdown
Author

/kind bug

@kubernetes-prow kubernetes-prow Bot added the kind/bug Categorizes issue or PR as related to a bug. label Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/bug Categorizes issue or PR as related to a bug. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant