Merge pull request #13140 from fidencio/topic/fix-besteffort-sandbox-cpu-sizing

runtime: oci: Only derive sandbox CPUs from shares when quota is unconstrained
This commit is contained in:
Steve Horsman
2026-06-01 17:09:12 +01:00
committed by GitHub
2 changed files with 53 additions and 3 deletions

View File

@@ -1513,9 +1513,16 @@ func CalculateSandboxSizing(spec *specs.Spec) (numCPU float32, memSizeMB uint32)
numCPU, memSizeMB = calculateVMResources(period, quota, memory)
// When cpuManagerPolicy=static is in use, kubelet sets quota=-1
// (unconstrained) and assigns CPUs via cpuset instead. Fall back
// to deriving the CPU count from shares (1024 shares per CPU).
if numCPU == 0 && shares > 0 {
// (unconstrained) and assigns CPUs via cpuset instead. In that case
// we derive the CPU count from the CPU shares (1024 shares per CPU).
//
// We must gate this on quota being explicitly unconstrained (< 0)
// rather than on numCPU == 0: a quota of 0 (or absent) means a
// BestEffort sandbox with no CPU request, which has to contribute 0
// vCPUs. Such a sandbox still carries the cgroup-floor shares value
// (2), and deriving from it would inflate every sandbox by one vCPU
// (e.g. peer-pods would boot default_vcpus+1).
if quota < 0 && numCPU == 0 && shares > 0 {
numCPU = float32(math.Ceil(float64(shares) / 1024.0))
}

View File

@@ -1263,6 +1263,13 @@ func makeSizingAnnotations(memory, quota, period string) *specs.Spec {
return &spec
}
func makeSizingAnnotationsWithShares(memory, quota, period, shares string) *specs.Spec {
spec := makeSizingAnnotations(memory, quota, period)
spec.Annotations[ctrAnnotations.SandboxCPUShares] = shares
return spec
}
func TestCalculateContainerSizing(t *testing.T) {
assert := assert.New(t)
@@ -1376,6 +1383,42 @@ func TestCalculateSandboxSizing(t *testing.T) {
expectedCPU: 4,
expectedMem: 4096,
},
// cpuManagerPolicy=static: kubelet leaves the quota
// unconstrained (-1) and pins CPUs via cpuset, so the CPU
// count must be derived from the shares (1024 shares per CPU).
{
spec: makeSizingAnnotationsWithShares("1048576", "-1", "100", "2048"),
expectedCPU: 2,
expectedMem: 1,
},
// Shares that don't divide evenly are rounded up.
{
spec: makeSizingAnnotationsWithShares("0", "-1", "100", "1536"),
expectedCPU: 2,
expectedMem: 0,
},
// BestEffort sandbox: no CPU request means quota is 0/absent,
// but the cgroup still carries the floor shares value (2). This
// must contribute 0 vCPUs, otherwise every sandbox (e.g. a
// peer-pod) would be inflated by one vCPU.
{
spec: makeSizingAnnotationsWithShares("0", "0", "100", "2"),
expectedCPU: 0,
expectedMem: 0,
},
// An explicit quota always wins over shares: the shares-based
// fallback only applies when the quota is unconstrained.
{
spec: makeSizingAnnotationsWithShares("0", "200", "100", "8192"),
expectedCPU: 2,
expectedMem: 0,
},
// Unconstrained quota but no shares set: nothing to derive from.
{
spec: makeSizingAnnotationsWithShares("0", "-1", "100", "0"),
expectedCPU: 0,
expectedMem: 0,
},
}
for _, tt := range testCases {