In Kubernetes, when and how does a namespace's LimitRange apply its default and defaultRequest values, and what happens to Pods that already exist?
answer
- admission time, not runtime
- LimitRanger plugin in kube-apiserver
- fills only empty keys
- kubernetes.io/limit-ranger annotation
- running Pods keep old values
basics
~10 sThe LimitRanger admission plugin in kube-apiserver copies defaultRequest and default into any container missing a request or limit at Pod creation, then rejects Pods outside min and max. Running Pods are never changed.
solid answer
~40 sA LimitRange acts only at admission. When a Pod is created, kube-apiserver's built-in `LimitRanger` plugin looks at every LimitRange in the namespace. For each container and init container, it fills in `defaultRequest` as the request and `default` as the limit, but only for resources the container left empty. Then it rejects the Pod with `403 Forbidden` if anything breaks `min`, `max` or `maxLimitRequestRatio`. The stamped values are saved in the Pod spec, and the `kubernetes.io/limit-ranger` annotation says what was set. Nothing is enforced at runtime, and it is not retroactive: Pods that already exist keep their old values. Pod templates in Deployments are never changed. Only Pods created later, including replacements after an eviction, get the new defaults and bounds.
code
yaml · 22 linesapiVersion: v1
kind: LimitRange
metadata:
name: container-defaults
namespace: telemetry-ingest
spec:
limits:
- type: Container
defaultRequest:
cpu: 120m
memory: 384Mi
default:
cpu: 300m
memory: 512Mi
min:
cpu: 50m
memory: 64Mi
max:
cpu: 1500m
memory: 768Mi
maxLimitRequestRatio:
cpu: "4"go deeper
Remember that a LimitRange acts only when a Pod is created. It fills in missing requests and limits, rejects values outside min and max, and never touches Pods that are already running.
Explain the two admission phases: LimitRanger fills gaps key by key during mutation, then checks bounds on the final Pod during validation. Show the kubernetes.io/limit-ranger annotation as proof of what was injected.
Point out that it is not retroactive. A new bound silently puts running Pods out of policy, and they get refused on their next recreation, which is exactly what a drain or rollout causes.
Frame it as a platform contract. Defaults let teams skip boilerplate, but the injected values become the scheduler's view of every undeclared workload, so choosing them is a capacity decision, not a formality.
## What a LimitRange is A **LimitRange** is a namespaced object in the core `v1` API group (its `kubectl` short name is `limits`). It holds a list of items under `spec.limits`, and each item has a `type` of `Container`, `Pod` or `PersistentVolumeClaim`. A `Container` item can carry five resource maps: - `defaultRequest` — the request written into a container that declares none for that resource. - `default` — the limit written into a container that declares none for that resource. - `min` and `max` — the smallest and largest value a container may declare. - `maxLimitRequestRatio` — the largest allowed limit divided by request. A LimitRange does nothing at runtime. Nothing reads it while the container runs, the kubelet never looks at it, and it throttles nothing. Its whole effect happens when an object is written to the API server. ## When the defaults are applied The work is done by **LimitRanger**, a built-in admission plugin inside kube-apiserver that is enabled by default. It acts in both admission phases: 1. A client (you, or the ReplicaSet controller acting for a Deployment) sends a create request for a Pod. 2. The API server decodes the Pod and applies ordinary Pod defaulting. One rule matters here: if a container sets a limit but no request for a resource, the request is set equal to the limit. 3. In the **mutating** phase, LimitRanger lists every LimitRange in the Pod's namespace and, for each container and init container, fills in `defaultRequest` and `default` values **only for keys the container left empty**. It never overwrites a value the container declared. 4. The API server validates the Pod. For example, a request larger than its limit is rejected at this step. 5. In the **validating** phase, LimitRanger checks the final Pod against every LimitRange's `min`, `max` and `maxLimitRequestRatio`, and rejects it with `403 Forbidden` if any bound fails. 6. The Pod is stored. It now carries the stamped values in its spec, as if the author had written them. When LimitRanger filled something in, it records that in the `kubernetes.io/limit-ranger` annotation, for example `LimitRanger plugin set: cpu, memory request for container gateway; cpu, memory limit for container gateway`. ## What happens to Pods that already exist LimitRanger ignores ordinary Pod updates, because a Pod's container list and resources cannot be changed through them, so there is nothing to re-default. As a result: | Situation | Effect of a new or edited LimitRange | |---|---| | Pod already running | Untouched: keeps its old requests and limits, even if they now break `max` | | Pod created afterwards | Gets the current defaults and must pass the current bounds | | Deployment / StatefulSet pod template | Never changed: LimitRanger only defaults Pods | | Deleted Pod replaced by its controller | Treated as a new Pod: new defaults and new bounds apply | | In-place resize via the Pod `resize` subresource | Checked against the current bounds | So a LimitRange is **not retroactive**. A platform team that adds one should expect old Pods to drift from policy until they are recreated by a rollout, an eviction or a node drain. At that point a Pod that used to be admitted can be refused. ## What gets defaulted and what does not - Only **Pods** are defaulted: their `containers` and `initContainers`. - A `PersistentVolumeClaim` item is only **checked** (`min`/`max` on the requested storage). No default size is ever injected, because a claim must state its size anyway. - A `Pod` item cannot carry `default` or `defaultRequest` at all. Validation rejects them, because a pod-wide total cannot be split among containers. - A controller's pod template is never touched, so the template still shows no resources while its Pods do. The defaults also change the Pod's scheduling request and its QoS class. How those work belongs with requests and limits, not with this object. ## Seeing it in practice ```yaml apiVersion: v1 kind: LimitRange metadata: name: container-defaults namespace: telemetry-ingest spec: limits: - type: Container defaultRequest: cpu: 120m memory: 384Mi default: cpu: 300m memory: 512Mi ``` A telemetry ingest gateway container created in `telemetry-ingest` with no `resources` block is stored with requests `120m`/`384Mi` and limits `300m`/`512Mi`. `kubectl describe namespace telemetry-ingest` and `kubectl describe limitrange container-defaults` show the table of values. `kubectl get pod <name> -o yaml` shows the stamped resources and the annotation. The Deployment's own YAML still shows no resources. ## Why platforms use it A LimitRange is how a platform makes sure resources are declared without relying on every team to remember. Containers that declare nothing still get a request the scheduler can plan around. Containers that ask for too much are refused at the door instead of being found later. It is also commonly paired with a quota that tracks CPU or memory. That quota refuses Pods that do not declare those resources, and the LimitRange defaults fill them in first.
- A Kubernetes Deployment's pod template has no resources block, yet its Pods show requests and limits. Why does the Deployment not show them?LimitRanger only defaults Pod objects. The ReplicaSet controller creates each Pod from the unchanged template, and every Pod gets the defaults at its own admission. The template never records them. If the LimitRange changes later, new Pods get the new values while the template still shows nothing, so the template alone does not tell you what the Pods will actually run with.
- Why is a Kubernetes LimitRange commonly paired with a ResourceQuota that tracks requests.cpu or limits.memory?Once a quota tracks a compute resource, Pods that do not declare that resource are refused. LimitRange defaults are written in the mutating admission phase, before the quota check, so a container that declared nothing still arrives with values and is admitted and counted. Without the LimitRange, every team would have to declare resources by hand or its Pods would fail.
- Is a Kubernetes in-place Pod resize checked against the namespace's LimitRange?Yes. Ordinary Pod updates are skipped because container resources cannot change through them. A resize goes through the Pod's `resize` subresource, and LimitRanger does handle that path, so the new values must still pass the current `min`, `max` and `maxLimitRequestRatio`. A resize cannot be used to get around a namespace's bounds.
It is like a form clerk who writes in a standard value on every blank line and refuses forms that ask for too much. Forms filed before the new rules were posted stay in the cabinet unchanged.
saying these in an interview costs you the question
- A LimitRange throttles or kills containers that use more than its max.
- Creating a LimitRange updates the resources of Pods already running.
- The kubelet reads the LimitRange when it starts each container.
- LimitRange defaults are written back into the Deployment's pod template.
- A LimitRange default replaces the values a container already declared.
- A LimitRange caps the total CPU the whole namespace can consume.