Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Andrei Kvapil
e01b4491bc
feat(tenants): add dedicatedCPU quota sugar for pinned-CPU workloads
Introduce an optional `dedicatedCPU` key in the cozy-lib resource sanitizer
(and therefore in tenant `resourceQuotas`). Its value is added 1:1 to both
`limits.cpu` and `requests.cpu`, bypassing the `cpuAllocationRatio` divisor.

This fixes the quota mismatch between regular VMs (where KubeVirt divides pod
`requests.cpu` by `cpuAllocationRatio`) and instance types with
`dedicatedCPUPlacement` (where pod `requests == limits`): previously, a single
dedicated-CPU VM could exhaust the tenant's `requests.cpu` quota while the
`limits.cpu` quota stayed largely unused, effectively shrinking the usable
tenant capacity for CX-series workloads to `cpu / cpuAllocationRatio`.

With the new key, operators can declare dedicated capacity explicitly:

    resourceQuotas:
      cpu: 10          # oversubscribable (burstable) CPU budget
      dedicatedCPU: 2  # plus 2 cores of dedicated physical CPU

This yields `limits.cpu=12, requests.cpu=3` at the default ratio of 10, so
both sides of the ResourceQuota stay in lockstep regardless of which
instance-type series a workload uses.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-04-21 13:01:29 +02:00
5 changed files with 99 additions and 1 deletions

View file

@ -92,7 +92,15 @@ tenant-u1
The `resourceQuotas` parameter allows you to limit resources available to the tenant. Supported keys include:
**Compute resources** (converted to `requests.X` and `limits.X`):
- `cpu` - Total CPU cores (e.g., `"4"` or `"500m"`)
- `cpu` - Total CPU cores (e.g., `"4"` or `"500m"`). `requests.cpu` is derived
as `cpu / cpuAllocationRatio` (default ratio is 10), matching KubeVirt's
behavior for regular VMs and burstable containers.
- `dedicatedCPU` - Additional CPU cores reserved for workloads that pin
physical cores and cannot be oversubscribed (KubeVirt instance types with
`dedicatedCPUPlacement`, pods with `Guaranteed` QoS). The value is added to
both `limits.cpu` and `requests.cpu` at a 1:1 ratio, so that one dedicated
core consumes one full unit of `requests.cpu` quota. Can be combined with
`cpu`.
- `memory` - Total memory (e.g., `"4Gi"` or `"512Mi"`)
- `ephemeral-storage` - Ephemeral storage limit (e.g., `"10Gi"`)
- `storage` - Total persistent storage (e.g., `"100Gi"`)
@ -115,3 +123,17 @@ resourceQuotas:
services.loadbalancers: "3"
pods: "50"
```
**Example with dedicated CPU** (for tenants that run VMs from the `cx*`
instance-type series or other `dedicatedCPUPlacement` workloads):
```yaml
resourceQuotas:
cpu: 10 # 10 cores of oversubscribable (burstable) CPU budget
dedicatedCPU: 2 # plus 2 cores of dedicated physical CPU
memory: 16Gi
```
With the default `cpuAllocationRatio=10` this produces a `ResourceQuota` of
`limits.cpu=12`, `requests.cpu=3` — enough for either 12 cores of regular VMs
(plus burst headroom) or 3 cores of dedicated VMs, or any mix in between.

View file

@ -67,6 +67,12 @@
{{ include "cozy-lib.resources.sanitize" list (.Values.resources $) }}
The optional **dedicatedCPU** key is sugar for workloads with
`dedicatedCPUPlacement` (KubeVirt CX-series, Guaranteed-QoS pods). Its value
is added to **both** `limits.cpu` and `requests.cpu` at a 1:1 ratio (the
CPU-allocation ratio does not apply), reflecting that such workloads pin
physical cores and cannot be oversubscribed.
Example input:
==============
cpu: "2"
@ -83,6 +89,18 @@
cpu: 200m # 2 / 10
memory: 256Mi # = limit
devices.com/nvidia: "1" # = limit
Example input with dedicatedCPU:
================================
cpu: "10"
dedicatedCPU: "2"
Example output (cpuAllocationRatio = 10):
=========================================
limits:
cpu: "12" # 10 + 2
requests:
cpu: "3" # 10/10 + 2
*/}}
{{- define "cozy-lib.resources.sanitize" }}
{{- $cpuAllocationRatio := include "cozy-lib.resources.cpuAllocationRatio" . | float64 }}
@ -99,6 +117,8 @@
{{- $cpuRequestF64 := divf $vcpuRequestF64 $cpuAllocationRatio }}
{{- $_ := set $output.requests $k ($cpuRequestF64 | toString) }}
{{- $_ := set $output.limits $k ($v | toString) }}
{{- else if eq $k "dedicatedCPU" }}
{{- /* merged into cpu after the loop */}}
{{- else if eq $k "memory" }}
{{- $vMemoryRequestF64 := (include "cozy-lib.resources.toFloat" $v) | float64 }}
{{- $memoryRequestF64 := divf $vMemoryRequestF64 $memoryAllocationRatio }}
@ -114,6 +134,13 @@
{{- $_ := set $output.limits $k $v }}
{{- end }}
{{- end }}
{{- if hasKey $args "dedicatedCPU" }}
{{- $dedicatedF := (include "cozy-lib.resources.toFloat" (index $args "dedicatedCPU")) | float64 }}
{{- $curLimitF := (include "cozy-lib.resources.toFloat" (dig "cpu" "0" $output.limits)) | float64 }}
{{- $curRequestF := (include "cozy-lib.resources.toFloat" (dig "cpu" "0" $output.requests)) | float64 }}
{{- $_ := set $output.limits "cpu" (addf $curLimitF $dedicatedF | toString) }}
{{- $_ := set $output.requests "cpu" (addf $curRequestF $dedicatedF | toString) }}
{{- end }}
{{- $output | toYaml }}
{{- end }}

View file

@ -48,3 +48,41 @@ tests:
- notExists:
path: spec.hard["requests.services.loadbalancers"]
- it: dedicatedCPU alone produces 1:1 cpu quota
values:
- quota_values_dedicated.yaml
release:
name: myrelease
namespace: default
revision: 1
isUpgrade: false
asserts:
- equal:
path: spec.hard["limits.cpu"]
value: "2"
- equal:
path: spec.hard["requests.cpu"]
value: "2"
- it: cpu and dedicatedCPU are summed with correct ratios
values:
- quota_values_combined.yaml
release:
name: myrelease
namespace: default
revision: 1
isUpgrade: false
asserts:
- equal:
path: spec.hard["limits.cpu"]
value: "12"
- equal:
path: spec.hard["requests.cpu"]
value: "3"

View file

@ -0,0 +1,6 @@
quota:
cpu: "10"
dedicatedCPU: "2"
_cluster: {}
_namespace: {}

View file

@ -0,0 +1,5 @@
quota:
dedicatedCPU: "2"
_cluster: {}
_namespace: {}