diff --git a/docs/operations/system-memory-limits.md b/docs/operations/system-memory-limits.md index 42d45e303..7a3175979 100644 --- a/docs/operations/system-memory-limits.md +++ b/docs/operations/system-memory-limits.md @@ -49,7 +49,7 @@ Two installer values, both empty by default so the operator's own defaults apply The limit is a ceiling, not a reservation, so it is deliberately set far above real usage: the point is that `memory.max` exists, not that it binds. Raising it is close to free; lowering it is where the risk lives. -**The limit must stay above the largest memory request declared in any system namespace.** A container whose own request is above the defaulted limit is rejected at admission, so before applying the operator scans the namespace's live pods and withholds the LimitRange where it finds a container requesting more than the limit and declaring no limit of its own, logging the workload, container, request and limit. Only a statically declared request reaches that state. `LimitRanger` is an in-tree admission plugin and runs well ahead of every mutating webhook in the API server's plugin order, so a webhook raising a request later sees a container that already carries the defaulted limit, and VPA in particular preserves the request-to-limit ratio it finds and so rescales that limit rather than leaving it behind. Lowering the limit too far therefore costs protection rather than availability, with one exception the live-pod scan cannot see: a workload with no pod running at scan time is invisible to it, and the LimitRange stays in place until that pod is finally created and rejected. The check below reads workload templates as well as live pods for exactly that reason, and it needs both halves, because a template is the only place a workload with no pods yet appears while a live pod is the only place a container still running from before the LimitRange was applied appears. The operator marks exactly the namespaces it treats as system with `cozystack.io/system=true`, the same condition under which it creates the LimitRange: +**The limit must stay above the largest memory request declared by a system-namespace container that carries no memory limit of its own.** A container whose own request is above the defaulted limit is rejected at admission, so before applying the operator scans the namespace's live pods and withholds the LimitRange where it finds a container requesting more than the limit and declaring no limit of its own, logging the workload, container, request and limit. Only a statically declared request reaches that state. `LimitRanger` is an in-tree admission plugin and runs well ahead of every mutating webhook in the API server's plugin order, so a webhook raising a request later sees a container that already carries the defaulted limit, and VPA in particular preserves the request-to-limit ratio it finds and so rescales that limit rather than leaving it behind. That last part holds under `controlledValues: RequestsAndLimits`, which is the upstream default and which no VPA in this repository overrides; a VPA set to `RequestsOnly` would raise the request and leave the defaulted limit where it was, which is the rejection this paragraph is otherwise ruling out. Lowering the limit too far therefore costs protection rather than availability, with one exception the live-pod scan cannot see: a workload with no pod running at scan time is invisible to it, and the LimitRange stays in place until that pod is finally created and rejected. The check below reads workload templates as well as live pods for exactly that reason, and it needs both halves, because a template is the only place a workload with no pods yet appears while a live pod is the only place a container still running from before the LimitRange was applied appears. The operator marks exactly the namespaces it treats as system with `cozystack.io/system=true`, the same condition under which it creates the LimitRange: ```bash (