Skip to main content
mastering ckad certified kubernetes application developer

LimitRange Defaults and Pod Disruption Budgets

9 min read Chapter 54 of 87
Summary

Covers LimitRange creation and behavior including default, defaultRequest,...

Covers LimitRange creation and behavior including default, defaultRequest, max, min, and maxLimitRequestRatio fields. Explains how LimitRanges interact with ResourceQuotas. Introduces Pod Disruption Budgets with minAvailable and maxUnavailable fields. Includes four exercises covering Chapters 16–18 material.

LimitRange Defaults and Pod Disruption Budgets

A ResourceQuota caps total namespace consumption. A LimitRange caps individual container consumption and — critically — provides default values for containers that don’t specify their own resources. Together, they form a complete resource governance story.

What Is a LimitRange?

A LimitRange is a namespaced admission controller that intercepts Pod creation requests and applies four types of constraints:

  1. default: Sets default resource limits for containers that don’t specify them.
  2. defaultRequest: Sets default resource requests for containers that don’t specify them.
  3. max: Sets the maximum allowed resource request/limit.
  4. min: Sets the minimum allowed resource request/limit.

When a Pod is created without resource specifications, the LimitRange mutates the Pod spec by injecting default values. When a Pod specifies values that violate min or max bounds, the API server rejects the request.

LimitRange YAML

apiVersion: v1
kind: LimitRange
metadata:
  name: container-limits
  namespace: dev
spec:
  limits:
    - type: Container
      default:
        cpu: "500m"
        memory: 256Mi
      defaultRequest:
        cpu: "100m"
        memory: 128Mi
      max:
        cpu: "2"
        memory: 1Gi
      min:
        cpu: "50m"
        memory: 64Mi
      maxLimitRequestRatio:
        cpu: "4"
        memory: "4"

Field Breakdown

FieldPurposeApplied When
defaultDefault limits injected into containersContainer has no resources.limits
defaultRequestDefault requests injected into containersContainer has no resources.requests
maxMaximum allowed value for both requests and limitsAlways — rejects Pods exceeding this
minMinimum allowed value for both requests and limitsAlways — rejects Pods below this
maxLimitRequestRatioMaximum ratio of limit/requestAlways — prevents excessive overcommit

How Defaults Are Applied

Consider a Pod created without resource specifications in a namespace with the above LimitRange:

apiVersion: v1
kind: Pod
metadata:
  name: no-resources
  namespace: dev
spec:
  containers:
    - name: app
      image: nginx:1.25

The LimitRange admission controller mutates this Pod before it is persisted. After mutation:

kubectl get pod no-resources -n dev -o yaml | grep -A 10 resources
resources:
  limits:
    cpu: 500m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

The container received default values for both requests and limits. This is the mechanism that prevents the enforcement rule problem described in the previous section: with a LimitRange providing defaults, Pods don’t need to explicitly specify resources to satisfy a ResourceQuota.

Max and Min Enforcement

If a Pod specifies resources outside the bounds:

kubectl run big-pod --image=nginx \
  --requests='cpu=3' --limits='cpu=5' \
  -n dev
Error from server (Forbidden): pods "big-pod" is forbidden: 
[maximum cpu usage per Container is 2, but limit is 5,
 maximum cpu usage per Container is 2, but request is 3]

Both the request (3) and limit (5) exceed the max (2). The API server rejects the Pod before it is scheduled.

Similarly, a Pod requesting below the minimum:

kubectl run tiny-pod --image=nginx \
  --requests='cpu=10m' --limits='cpu=50m' \
  -n dev
Error from server (Forbidden): pods "tiny-pod" is forbidden:
minimum cpu usage per Container is 50m, but request is 10m

maxLimitRequestRatio

This field constrains the overcommit ratio between limits and requests:

maxLimitRequestRatio:
  cpu: "4"

With a ratio of 4, a container requesting 100m CPU can set a limit of at most 400m. A container requesting 100m with a limit of 500m is rejected:

Error from server (Forbidden): pods "overcommit" is forbidden:
cpu max limit to request ratio per Container is 4, but provided ratio is 5.000000

This prevents scenarios where a container requests minimal resources (getting scheduled easily) but bursts to unreasonable limits, potentially destabilizing the node.

LimitRange Types

The type field in a LimitRange can target different resource consumers:

TypeApplies To
ContainerIndividual containers within a Pod
PodTotal resources across all containers in a Pod
PersistentVolumeClaimStorage requests for PVCs

A LimitRange can include multiple entries:

spec:
  limits:
    - type: Container
      default:
        cpu: "500m"
        memory: 256Mi
      max:
        cpu: "2"
        memory: 1Gi
    - type: Pod
      max:
        cpu: "4"
        memory: 2Gi
    - type: PersistentVolumeClaim
      max:
        storage: 10Gi
      min:
        storage: 1Gi

The Container-level limits apply per container. The Pod-level limits apply to the sum across all containers. A Pod with four containers, each requesting 500m CPU, would hit a Pod-level max of 2 (4 × 500m = 2).

LimitRange and ResourceQuota Interaction

LimitRanges and ResourceQuotas work together:

  1. A ResourceQuota on requests.cpu forces every Pod to specify CPU requests.
  2. A LimitRange with defaultRequest.cpu injects CPU requests into Pods that don’t specify them.
  3. Result: Pods without explicit resources get LimitRange defaults → defaults count against the ResourceQuota → no Pod is rejected for missing specifications.

The typical setup for a governed namespace:

# Create namespace
kubectl create namespace managed-ns

# Create LimitRange with defaults
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
  namespace: managed-ns
spec:
  limits:
    - type: Container
      default:
        cpu: "500m"
        memory: 256Mi
      defaultRequest:
        cpu: "100m"
        memory: 128Mi
      max:
        cpu: "2"
        memory: 1Gi
EOF

# Create ResourceQuota
kubectl create quota ns-quota \
  --hard=requests.cpu=4,requests.memory=8Gi,limits.cpu=8,limits.memory=16Gi,pods=20 \
  -n managed-ns

Now any Pod created in managed-ns either specifies resources (validated against LimitRange bounds) or receives defaults (validated against both LimitRange bounds and ResourceQuota totals).

Inspecting LimitRanges

kubectl describe limitrange container-limits -n dev
Name:                  container-limits
Namespace:             dev
Type                   Resource  Min   Max  Default Request  Default Limit  Max Limit/Request Ratio
----                   --------  ---   ---  ---------------  -------------  -----------------------
Container              cpu       50m   2    100m             500m           4
Container              memory    64Mi  1Gi  128Mi            256Mi          4

This tabular output gives a complete view of all constraints in a single glance.

Pod Disruption Budgets

A Pod Disruption Budget (PDB) protects application availability during voluntary disruptions — events initiated by a human or automation, not by hardware failure:

  • kubectl drain (evicts all Pods from a node)
  • Cluster autoscaler removing a node
  • Rolling updates triggered by Deployment changes
  • Manual kubectl delete pod (when the PDB controller is enforcing)

A PDB ensures that a minimum number of Pods remain available (or equivalently, a maximum number are unavailable) at any point during the disruption.

minAvailable

Specifies the minimum number of Pods that must remain running:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: app-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web

If the web application has 3 replicas and minAvailable: 2, Kubernetes allows at most 1 Pod to be evicted at a time. A kubectl drain on a node with one of these Pods succeeds. A drain attempting to evict 2 Pods from this set is blocked until one replacement Pod is ready.

minAvailable can also be a percentage:

spec:
  minAvailable: "50%"

With 4 replicas and minAvailable: "50%", at least 2 Pods must remain available.

maxUnavailable

The inverse of minAvailable — specifies the maximum number of Pods that can be down simultaneously:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: app-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: web

With 3 replicas and maxUnavailable: 1, at most 1 Pod can be disrupted at a time. This is functionally equivalent to minAvailable: 2 with 3 replicas, but maxUnavailable scales more naturally — maxUnavailable: 1 means the same thing regardless of replica count.

Creating a PDB Imperatively

kubectl create pdb app-pdb \
  --selector=app=web \
  --min-available=2

Or with maxUnavailable:

kubectl create pdb app-pdb \
  --selector=app=web \
  --max-unavailable=1

Checking PDB Status

kubectl get pdb app-pdb
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
app-pdb   2               N/A               1                     5m

ALLOWED DISRUPTIONS shows how many Pods can currently be evicted. If it shows 0, no voluntary disruptions are permitted — eviction requests are blocked.

PDB Constraints

  • You can specify either minAvailable or maxUnavailable, not both.
  • PDBs only protect against voluntary disruptions. An involuntary disruption (node crash, OOM kill) bypasses the PDB entirely.
  • A PDB with maxUnavailable: 0 blocks all voluntary disruptions and should be used with extreme caution — it can prevent node drains and cluster upgrades.
  • PDBs require a selector that matches the Pods they protect. The selector must match the same labels used by the Deployment, StatefulSet, or ReplicaSet managing those Pods.

Exam Awareness

PDBs appear on the CKAD exam rarely and at a basic level. Know how to create one with kubectl create pdb and understand the difference between minAvailable and maxUnavailable. The conceptual understanding — “PDBs ensure N Pods survive voluntary disruptions” — is sufficient.


Exercises

Work through these exercises on your Kind cluster. Complete YAML manifests and step-by-step solutions are provided in Chapter 19.

Exercise 1: ConfigMap as Environment Variables and Volume Mount

Create a file named app.properties with the following content:

db.host=postgres.prod.svc
db.port=5432
cache.ttl=300

Create a ConfigMap named app-settings from this file. Create a Deployment named config-app with 2 replicas running busybox (command: sleep 3600) that:

  • Mounts the ConfigMap as a volume at /etc/config
  • Also injects db.host from the ConfigMap as an environment variable named DATABASE_HOST using valueFrom

Verify that the file exists at /etc/config/app.properties and that the DATABASE_HOST environment variable is set. Then update the ConfigMap and verify the volume-mounted file updates while the environment variable does not.

Exercise 2: TLS Secret with Nginx

Generate a self-signed TLS certificate:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key -out tls.crt \
  -subj "/CN=myapp.example.com"

Create a TLS Secret named nginx-tls from the certificate and key. Create an nginx Pod named secure-nginx that mounts the TLS Secret at /etc/nginx/ssl with readOnly: true. Verify the certificate and key files exist inside the container.

Exercise 3: Locked-Down Security Context

Create a Pod named secure-pod running busybox (command: sleep 3600) with the following security constraints:

  • Run as user 1000, group 1000
  • Read-only root filesystem
  • No privilege escalation
  • Drop all Linux capabilities
  • Mount an emptyDir volume at /tmp for writable space

Verify:

  • id shows UID 1000, GID 1000
  • touch /test fails (read-only filesystem)
  • touch /tmp/test succeeds (writable emptyDir)

Exercise 4: ServiceAccount with RBAC

Create a namespace named rbac-lab. In that namespace:

  1. Create a ServiceAccount named pod-inspector
  2. Create a Role named pod-reader granting get and list on pods
  3. Create a RoleBinding named pod-reader-binding binding the Role to the ServiceAccount
  4. Create a Pod named inspector using the pod-inspector ServiceAccount, running curlimages/curl with command sleep 3600

Verify:

  • kubectl auth can-i get pods --as=system:serviceaccount:rbac-lab:pod-inspector -n rbac-lab returns yes
  • kubectl auth can-i delete pods --as=system:serviceaccount:rbac-lab:pod-inspector -n rbac-lab returns no
  • kubectl auth can-i get secrets --as=system:serviceaccount:rbac-lab:pod-inspector -n rbac-lab returns no