What a node offers and what a workload asks for
- Allocatable is what the node reports about itself: CPU cores, total memory and disk. The local node is read directly. A remote node’s agent reports it every 30 seconds.
- Requested is the sum of
resources.cpuandresources.memory(times replicas) of every app placed on the node. Databases carry no declared request today, so they count toward load but not toward requested totals. - Disk is measured, not requested: a node is refused when placing would leave less than the minimum free share of its disk.
How a node is chosen
- Nodes that are cordoned, offline, excluded (the node being drained) or missing a selector label are filtered out, as before.
- Nodes that cannot fit the workload’s CPU or memory request are filtered out, with the shortfall as the reason.
- Among the rest, the node with the most free share of its tightest resource after the placement wins. Ties go to the node with fewer workloads, then to the lowest node id, so the same inputs always give the same answer.
- When the control plane’s own node serves ingress for a new app with domains, it is preferred if it fits.
node_id or move target is always honored. If the pinned node would not have fit, the decision records a warning instead of refusing.
Unknown capacity
A node that has not reported a figure (an agent that has not connected yet, or an older agent) is treated as unknown, never as zero (nothing fits) and never as unlimited (everything fits).- A dimension the workload does not ask for is ignored.
- A node whose capacity cannot be verified for a requested dimension stays eligible, but ranks after every verified node, and the decision carries a warning.
- Set
APP_PLACEMENT_REJECT_UNKNOWN_CAPACITY=trueto filter such nodes out instead. - Disk is always checked, so a node with no disk reading is unverified until its first report.
When nothing fits
A create with nonode_id fails with 409 and lists each node’s shortfall, for example node-a: insufficient capacity: memory: needs 2.0 GiB, 1.0 GiB free of 1.0 GiB allocatable (short by 1.0 GiB). Free capacity, add a node, raise the overcommit setting, or pass node_id to place it on a node anyway. A drain reports the same per-resource error and leaves that resource where it is.
If no node is registered, or every node is cordoned or offline, the create still falls back to the local node as before.
The overcommit policy
Operator settings on the control plane, read at start:
The default never overcommits. Values above
100 allow it; CPU is compressible and usually safe to raise, memory is not. Invalid values are logged and the safe default is used.
Seeing why
Dashboard. The node page has a Capacity card with requested versus allocatable bars. The app and database Placement card shows the recorded decision (the nodes considered, which were refused and why, the winner) and a fresh evaluation of every node right now. CLI.GET /api/v1/apps/{name}/placement/decisionandGET /api/v1/databases/{name}/placement/decisionreturnrecorded(trigger, time, and the decision) andpreview.GET /api/v1/nodes/allocationsreturns every node’s dimensions and the policy in force.