Repository navigation
DRA: pin topology-aware containers to their claims - #823
Conversation
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation matches the stated behavior and includes focused unit and end-to-end coverage.
Review effort: Balanced
Findings: None
What changed in this PR
Pins topology-aware containers to DRA-claimed CPUs while preserving exclusive allocations.
Changes:
- Detects claims through CDI-provided environment variables.
- Excludes claiming containers from shared CPU updates.
- Adds unit and end-to-end coverage for pinning and overcommit behavior.
| File | Description |
|---|---|
cmd/plugins/topology-aware/policy/dra.go |
Resolves container claims to CPU sets. |
cmd/plugins/topology-aware/policy/pools.go |
Applies claimed CPU pinning. |
cmd/plugins/topology-aware/policy/dra_test.go |
Tests pinning and overcommit handling. |
test/e2e/.../code.var.sh |
Verifies claim CPU placement end to end. |
test/e2e/.../dra-pod.yaml.in |
Supports multiple claim-sharing containers. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
77ef911 to
dba82f1
Compare
bedd415 to
da35836
Compare
da35836 to
5d2dd87
Compare
7e3d59f to
12aa725
Compare
12aa725 to
7b5c553
Compare
7b5c553 to
e30009e
Compare
e30009e to
aec98d1
Compare
aec98d1 to
3ce2352
Compare
3ce2352 to
51a9974
Compare
8580877 to
7308e08
Compare
7308e08 to
cc8c21a
Compare
cc8c21a to
d399f51
Compare
d399f51 to
1ac62c4
Compare
| if cpuType == cpuPreserve { | ||
| log.Infof(" => preserving %s cpuset %s", container.PrettyName(), container.GetCpusetCpus()) | ||
| } else if !claimed.IsEmpty() { | ||
| // The claimed CPUs replace the shared or reserved ones. |
There was a problem hiding this comment.
Hmm, why do the claimed CPUs replace the shared set ? Is that the best/most natural choice ? At least in the original proto the claimed CPUs were added to the container-exclusive set.
So basically the proto semantics were:
- Claimed CPUs are always treated as exclusively allocated ones from the containers point of view
- However claimed CPUs are shared between multiple containers referencing the same claim.
- If a container both claims CPUs and asks for them using the normal CPU request/limit combo, then the effective CPU set of the container is the union of a traditional CPU allocation and the claimed CPUs.
@bart0sh @askervin @kad Is there a reason why we couldn't go with those ? Either some technical limitation or some other reason why different semantics make more sense ?
There was a problem hiding this comment.
I don't see any reason for changing the proto semantics. I implemented their balloons-equivalent semantics in #828.
Unlike in the initial balloons implementation, here a container could benefit from getting the information on which CPUs belong to shared claim(s). I'd assume that a container having a shared CPU claim and CPUs of its own might have different uses for those two CPU sets.
There was a problem hiding this comment.
No technical reason. I followed the community DRA CPU driver, where a container runs only on its claimed CPUs. But that driver has no shared pool, so your model definitely fits better.
So I would change the code this way:
- A container with no CPU request (the usual case) runs only on its claimed CPUs, as now.
- A container that also has a CPU request gets its normal allocation as well: exclusive + claimed CPUs, plus shared CPUs if it has a shared portion.
- A container with reserved CPUs gets reserved + claimed CPUs.
Does this sound correct?
There was a problem hiding this comment.
@klihub Does this look like a correct fix: https://github.com/containers/nri-plugins/compare/3b8593f36ec927993decfca838dda405cdd98a62..97309a507f33c031d2c74c5c307a2534d08cca7d ?
1ac62c4 to
3b8593f
Compare
3b8593f to
97309a5
Compare
97309a5 to
8e751b0
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Reserved and fractional CPU handling contradicts the documented claim-placement behavior.
2 open findings
1 resolved since last review
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
Signed-off-by: Ed Bartosh <eduard.bartosh@intel.com>
Signed-off-by: Ed Bartosh <eduard.bartosh@intel.com>
8e751b0 to
fb05d6a
Compare
There was a problem hiding this comment.
🟢 Approval recommended
The implementation is consistent across allocation paths and is supported by focused unit and end-to-end coverage.
0 open findings
2 resolved since last review
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.



Pinning containers to their claims
Seventh PR of the DRA series, on top of #821. Two commits: the
topology-aware policy pins a container holding claims to the claims' CPUs, and
the
test42-dra-devicese2e test checks the pinning. Nothing inpkg/resmgrchanges.
What a claiming container runs on
A container holding claims runs on the CPUs of its claims. A container with no
CPU request of its own, the usual case, runs on its claims only. A container
with a CPU request also gets its normal allocation: exclusive CPUs, and shared
CPUs if it has a shared portion. A container with reserved CPUs, such as one
in
kube-system, gets its reserved CPUs as well if it has a CPU request.Hiding hyperthreads applies to the exclusive CPUs only. The claimed CPUs are
pinned as they are, since they are exactly what the claim asked for. The
claimed CPUs must also follow a container's strict topology hints. The
scheduler picks the claim's CPUs and the policy cannot move them, so a claim
that fails a strict hint fails the allocation.
Where a claiming container goes
A claiming container with normal CPUs gets its grant from the pool of the
claimed CPUs, the same pool the claim grant itself came from, rather than from
the pool scoring would pick. So its memory comes from the claims' NUMA node,
and so do its own exclusive and shared CPUs.
A container with reserved CPUs stays in the root pool, like other reserved
containers, and its memory comes from there. The claim's pool usually has no
reserved CPUs, so the container would get normal CPUs there instead.
The CPU shares follow the rule for mixed allocations: they come from the
shared or reserved portion if there is one, otherwise from all the CPUs the
container runs on.
A besteffort container holding a claim is also not counted as a zero-request
shared container of its pool. Otherwise it would hold back a milli-CPU of
shared capacity there, and the last CPUs of its node could not be claimed.
A container's claims are fixed for its lifetime: the kubelet prepares them
before the container is created and unprepares them only after all of the
pod's containers have stopped, so no running container ever holds a released
claim.
updateSharedAllocationsleaves a claiming container with no sharedportion alone. One with a shared portion is updated like any other container,
and keeps its claimed CPUs. Containers with
cpuPreservekeep the cpuset theruntime gave them, as before.
How a container is linked to its claims
The policy reads the
DRA_CPUSET_<claim UID>variable #821 puts in thecontainer's environment. A container holds a claim if it has the variable for
a claim grant the policy holds, and its value names that grant's CPUs. The
value is compared as a cpuset, so
0-1and1,0name the same CPUs. Avariable for an unknown claim, or naming other CPUs, is ignored. No cache or
interface change is needed: the policy looks the variables up with the
existing
GetEnv.The claims are looked up once, when the container is allocated, and kept on
its grant. Placement, pinning, CPU shares and the shared-pool checks all read
them from there, so pool scoring does not look up every claim for every
container.
This is a temporary approach. The pod spec can set the variable too, so a
pod which knows a claim's UID and CPUs can run on them without holding the
claim. A container's CDI devices name its claims and are set by the runtime
alone, so they are the right link. But not every maintained runtime release
reports them to NRI yet: containerd does from v2.3, CRI-O does not yet. We
switch to CDI device names once every maintained release of both runtimes
reports them.
What is not here
These are known limitations, each lifted by a later PR in the series:
but the policy resets its saved state on start, so after the plugin
restarts their CPUs are free again and can be granted twice.
refreshes it when claimed CPUs leave the free supply.
DeviceClassships yet, so a claim needs one written by the user.The e2e test creates its own.
Verification
go test -race ./cmd/plugins/topology-aware/... ./pkg/resmgr/...passes,and golangci-lint finds no issues.
exclusive, exclusive and shared, reserved, and reserved with no CPU request,
a container holding two claims, a variable naming other CPUs, one naming an
unknown claim, and a claiming container keeping its claim while another
claim comes and goes. For a claiming container it also checks the pool, the
memory zone, the shared or reserved portion and the CPU shares. Each of
these changes fails it: dropping the claimed CPUs from
applyGrant, pinninga claiming container without its own CPUs, skipping every claiming container
in
updateSharedAllocations, placing a reserved container in the claim'spool, and zeroing the container's own CPU request in
newRequest.the rest of its node. It fails if the container is counted as a zero-request
shared container. A third test checks the claimed CPUs against a strict
topology hint, with an aligned and a misaligned claim.
container. A container holding a claim stays on it across a reconfiguration.
Comparing the value as a string, or not carrying the claims onto the grant
or through a reconfiguration, fails the tests.
test42-dra-devicespasses on n4c16 with Kubernetes 1.37, containerdv2.4.1, and the
DRAConsumableCapacityandDRANodeAllocatableResourcesgates. Every claiming container with no CPU request runs on exactly its
claim's CPUs and on memory of its claim's node: 12-14 on node 3, 4-5 and 6-7
on node 1, then 4-5 for the third claim once the first is released. A claim
on the node the besteffort container runs on, node 0 in this run, gets 0-1.
Two containers sharing one claim both run on its CPUs, 0-1, with node 0's
memory. The besteffort container still runs on no claimed CPU and gets
released CPUs back. A pod with a 100m CPU request of its own and a claim on
node 2 runs on CPUs 8-11, all CPUs of node 2: the claimed CPUs plus the
shared ones, with node 2's memory.