User guide: live host resource limits (spec.resources.limits)
A host-side cap on an already-running Machine's own VMM process cgroup --
distinct from spec.resources.cpu/.memory (which describe what the
guest sees) and from CPU/memory hotplug (which
grows what the guest sees, one-way only).
What this is
apiVersion: kairon.zyvor.dev/v1alpha1
kind: Machine
metadata:
name: noisy-neighbor
spec:
image: {path: /var/lib/fluxvm/images/app.qcow2}
resources:
cpu: "4"
memory: 8Gi
limits:
cpuQuotaPercent: 300 # cap at 3 full cores' worth of CPU time
memoryMaxBytes: 9663676416 # 9Gi hard ceiling (some headroom over the guest's own 8Gi)
ioWeight: 50 # lower priority against other cgroups on the same disk
pidsMax: 4096
runtime: {backend: qemu}
powerState: Running
This wraps FluxVM's real POST /v1/vms/{id}/resources -- a partial patch
to the VMM process's own cgroup v2 controllers (cpu.max, memory.max,
io.weight, pids.max), enforced by the Linux kernel itself, not
something Kairon or FluxVM polices in userspace. Backend-agnostic:
unlike hugepages/numaNode/cpuSet (QEMU-only), this works for every
backend FluxVM supports, since it operates on the cgroup the VMM process
runs in, not a backend-specific device or API.
kairon-node reconciles this continuously (internal/agent/resourcelimits.go),
not just at creation -- editing spec.resources.limits on a Running
Machine takes effect on the next reconcile tick, and unlike hotplug it can
be raised or lowered freely at any time: a cgroup limit change has none
of hotplug's "can't unplug a vCPU" one-way asymmetry.
Watching live usage (status.resourceUsage)
Every reconcile tick, kairon-node also reads FluxVM's own
GET /v1/vms/{id}/stats (the same cgroup-derived, backend-agnostic
mechanism limits above enforces against) and projects it into
status.resourceUsage:
status:
resourceUsage:
cpuPercent: 42.5 # % of one core, averaged over the process's whole lifetime
memoryBytes: 2147483648
diskReadBytes: 10485760
diskWriteBytes: 5242880
The dashboard shows this inline next to each Machine's CPU/Memory
columns. It's a point-in-time snapshot refreshed every tick, not a time
series -- for historical graphs, use kairon_reconcile_duration_seconds-
style Prometheus metrics or your own external monitoring against the
guest itself, not this field.
kaironctl top machines/kaironctl top nodes gives the same data a
kubectl top-style terminal view instead of reading it off individual
Machine objects one at a time: top machines prints every Machine's own
status.resourceUsage row (--selector k=v, repeatable, narrows which
ones count, exactly like kaironctl get's own --selector); top nodes
rolls those same per-Machine samples up by spec.nodeName
(model.AggregateUsageByNode, internal/model/usage.go) into a
per-host CPU%/memory/Machine-count total -- the fleet-wide hotspot view
kaironctl get machines alone can't answer without an operator manually
grouping and summing rows. Deliberately no new metrics pipeline: both
verbs just render data every Machine already reports. top nodes is
cluster-wide by construction (-n/--namespace has no effect, the same
"unused for a cluster-scoped kind" precedent kaironctl get nodes
already sets); top machines defaults to -n/--namespace like get machines does. The dashboard's Nodes page shows the identical
per-node rollup in its Machines/CPU/Memory columns, via the same
model.AggregateUsageByNode call behind GET /api/v1/nodes/usage.
Why this exists
Every Machine here runs as a bare process on the host, not inside a Pod --
so unlike a KubeVirt VM (whose virt-launcher Pod gets Kubernetes' own
resource limits enforcement for free), a Kairon Machine had no host-side
enforcement of anything beyond the scheduler's own bin-packing count. This
closes that gap directly: a real, kernel-enforced ceiling on CPU/memory/IO/
process count, independent of whatever the guest itself believes it has.
What this deliberately doesn't include
No cpuSetCPUs. FluxVM's own ResourcePatch also supports pinning a
VM to specific host CPU cores (cpuset_cpus), but Kairon doesn't expose
it here -- allocating specific, non-overlapping host CPU numbers across
every Machine competing for them on one node is a real capacity-allocation
problem internal/scheduler's still pure count-based bin-packing doesn't
solve (the same reason spec.resources.cpuSet is a guest-visible hint
only, not real pinning -- see machine-cpu-numa.md's
"What this is not" section). Exposing cpuSetCPUs here would let two
Machines silently claim overlapping host cores with nothing to stop them;
that's a bigger, separate scheduler feature, not attempted in this cut.
Real limits today (first cut)
- No validation that limits are internally consistent with
spec.resources.cpu/.memory-- settingmemoryMaxBytesbelow what the guest was booted with, for instance, is accepted and forwarded to FluxVM as-is; whatever happens next (likely OOM-killing the VMM process) is between you and the kernel, not something Kairon checks first. - Requires the Machine to already be running. FluxVM's own error
("VM has no cgroup: not running, or cgroup setup failed at launch")
surfaces as-is if
spec.resources.limitsis set before the Machine ever reachesRunning-- retried automatically on the next tick once it does. - No way to explicitly clear a limit back to "unset." FluxVM's own
ResourcePatchonly ever touches fields present in the request; there's no "reset to no limit" call. Removingspec.resources.limits(or one of its fields) from the Machine leaves the last-applied cgroup value in place rather than lifting it --status.appliedResourceLimitsalways reflects what's actually enforced, not what the current spec asks for, precisely so this isn't a silent surprise. - No admission-time bounds checking -- an absurd value (e.g.
cpuQuotaPercent: 0) is rejected by FluxVM's own cgroup write, not caught earlier by Kairon.