How does a Kubernetes LimitRange merge its defaults and min, max and maxLimitRequestRatio bounds into a partially specified container, and what gets the Pod rejected?
answer
- per key, never overwrite
- limit-only copies into request
- request above injected limit
- max quietly becomes default
- ratio needs both values
basics
~20 sDefaults fill only the request or limit keys a container left empty. A limit with no request becomes the request first. The Pod is rejected if a request exceeds its injected limit or any value breaks min, max or the ratio.
solid answer
~50 sThe merge is per resource key and never overwrites what the container declared. If the container declares nothing, it gets `defaultRequest` and `default`. If it sets only a limit, ordinary Pod defaulting copies that limit into the request before admission, so `defaultRequest` is not used. If it sets only a request, the limit comes from `default`, and if the request is larger than that limit, Pod validation rejects the Pod. After merging, `LimitRanger` checks `min` and `max` against the request and limit, and `maxLimitRequestRatio` against limit ÷ request, which also needs both values set and non-zero. The LimitRange itself is defaulted too: a missing `default` takes `max`, and a missing `defaultRequest` takes `default`, or `min` if there is no default. A `Pod` item checks only the summed totals and may not carry defaults. A `PersistentVolumeClaim` item only checks storage size.
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
Recall that LimitRange defaults only fill empty fields, and that min and max can refuse a Pod when it is created.
Walk through the merge key by key. Cover the limit-only case, where the request copies the limit, and the request-only case, where an injected default limit smaller than the request makes the Pod invalid.
Watch for the LimitRange's own defaulting, where max quietly becomes default and defaultRequest, and for multiple Container LimitRanges whose defaults depend on list order.
Decide how tight to make the bounds. A strict ratio and a low max protect node packing, but every team with bursty or memory-heavy work then needs an exception process, so the bounds are a policy tradeoff, not just a technical setting.
## The five fields of a Container item A LimitRange item of `type: Container` applies to every container and init container of each new Pod in its namespace. Each field is a map from a resource name (for example `cpu`, `memory` or `ephemeral-storage`) to a quantity: | Field | Role | When it acts | |---|---|---| | `defaultRequest` | request written into a container that has none for that key | mutating admission | | `default` | limit written into a container that has none for that key | mutating admission | | `min` | the declared request and limit must be at least this | validating admission | | `max` | the declared request and limit must be at most this | validating admission | | `maxLimitRequestRatio` | limit ÷ request must be at most this; both must be set and non-zero | validating admission | The work is done by the **LimitRanger** admission plugin in kube-apiserver. It fills gaps first and checks bounds afterwards, on the final Pod. ## How the LimitRange itself gets defaulted Before any Pod is involved, the API server fills in missing fields of a `Container` item when the LimitRange is stored. These steps run in order, per resource key: 1. If `max` is set and `default` is not, `default` becomes `max`. 2. If `default` is set (by you or by step 1) and `defaultRequest` is not, `defaultRequest` becomes `default`. 3. If `defaultRequest` is still unset and `min` is set, `defaultRequest` becomes `min`. The surprise is that a LimitRange with **only** `max.memory: 768Mi` gives every undeclared container a memory request **and** limit of 768Mi. That reserves far more memory than intended. Validation also keeps the item consistent: `min <= defaultRequest <= default <= max`, the ratio must be at least 1, and if `min` and `max` are both set the ratio cannot exceed `max/min`. ## Merging into a partially specified container The merge is **per resource key and per field**, and it never overwrites what the container declared: - **Nothing declared**: the container gets `defaultRequest` as its request and `default` as its limit. - **Only a limit declared**: ordinary Pod defaulting runs *before* admission and copies the limit into the request. `defaultRequest` is therefore not used, and request equals limit. - **Only a request declared**: the limit comes from `default`. If the declared request is **larger** than that default limit, Pod validation rejects the Pod (`must be less than or equal to cpu limit of 300m`). This is the classic trap. - **Both declared**: nothing is filled in, and only the bounds are checked. After merging, the bound checks run against the resulting values. `min` fails if the request or limit is below it, or if the container has no request for that key. `max` fails if the limit or request is above it, or if the container has no limit for that key. `maxLimitRequestRatio` fails if the limit divided by the request is above the ratio, or if either one is missing or zero. ## Worked example ```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} min: {cpu: 50m, memory: 64Mi} max: {cpu: 1500m, memory: 768Mi} maxLimitRequestRatio: {cpu: "4"} ``` | Container declares | After defaulting (cpu, memory) | Result | |---|---|---| | nothing | req 120m/384Mi, lim 300m/512Mi | admitted (cpu ratio 2.5) | | `limits: {cpu: 600m, memory: 700Mi}` | req 600m/700Mi, lim 600m/700Mi | admitted (requests copied from limits) | | `requests: {cpu: 350m}` | req 350m/384Mi, lim 300m/512Mi | rejected: cpu request exceeds the defaulted limit | | `requests: {cpu: 100m}`, `limits: {cpu: 900m}` | ratio 9 | rejected: `cpu max limit to request ratio per Container is 4, but provided ratio is 9.000000` | | `limits: {memory: 1Gi}` | lim 1Gi | rejected: `maximum memory usage per Container is 768Mi, but limit is 1Gi` | ## The Pod and PersistentVolumeClaim types - **`type: Pod`** checks `min`, `max` and `maxLimitRequestRatio` against the Pod's **effective total** requests and limits rather than per container. It may not carry `default` or `defaultRequest`, and validation says so explicitly. - **`type: PersistentVolumeClaim`** needs `min` or `max` for `storage` and checks only the claim's `spec.resources.requests`. LimitRanger never injects a default size into a claim. - Each type may appear **at most once** in one LimitRange. A duplicate is rejected. ## Several LimitRanges in one namespace Every LimitRange's bounds must pass, so they combine like an intersection. Defaults are different: each one fills only keys that are still empty, so whichever LimitRange the plugin happens to process first wins. That depends on list order and is not something to design around. Keep one `Container` LimitRange per namespace.
- A Kubernetes LimitRange of type Container sets only max.memory 768Mi. What do containers that declare no memory get?When the LimitRange is stored, the API server copies `max` into `default` and then `default` into `defaultRequest`. Every container that declares no memory therefore gets a memory request and a limit of 768Mi. That usually reserves far more memory than intended and packs nodes poorly. Set `defaultRequest` and `default` explicitly whenever you set `max`.
- What happens when a Kubernetes namespace holds two LimitRanges that both have a Container item?Every LimitRange's `min`, `max` and ratio must pass, so the bounds combine like an intersection. Defaults are filled only into keys that are still empty, so whichever LimitRange the plugin processes first wins. That depends on list order, not on anything you can rely on. Keep one Container LimitRange per namespace. Inside a single LimitRange, a repeated `type` is rejected.
- Why can a Kubernetes LimitRange item of type Pod not set default or defaultRequest?A Pod-type item bounds the Pod's total requests and limits, and there is no sensible way to split a pod-wide default among its containers. Validation rejects those fields for `type: Pod`. Per-container defaults belong in a `Container` item, and the Pod item then only checks the summed values against `min`, `max` and `maxLimitRequestRatio`.
saying these in an interview costs you the question
- A container with only a limit gets its request from defaultRequest.
- LimitRange defaults overwrite values a container already declared.
- Setting only max leaves undeclared containers without any request.
- A PersistentVolumeClaim item injects a default storage size.
- maxLimitRequestRatio is satisfied when a container declares no request.
- A Pod-type item can set per-pod default requests and limits.