Guide: Containers

Kubernetes Essentials

The core objects and how they relate, probes, requests and limits, rollouts, and an order for debugging a Pod that will not run.

Core objects and how they relate

Kubernetes stores objects that describe the state you want, and controllers work to make the cluster match it. Seven objects cover most of a small application.

ObjectWhat it is and how it relates
PodOne or more containers with shared storage and network resources. Pods are generally not created directly: workload resources such as a Deployment create them.
ReplicaSetMaintains a stable set of replica Pods. A Deployment manages ReplicaSets, so you rarely touch one.
DeploymentDeclarative updates for Pods and ReplicaSets. The configuration of each revision is stored in its ReplicaSets.
ServiceExposes an application running as one or more Pods, chosen with a label selector. The default type, ClusterIP, is reachable only inside the cluster.
ConfigMapNon-confidential key-value data. Pods can read it as environment variables, command-line arguments or files in a volume.
SecretA small amount of sensitive data such as a password, token or key. Stored unencrypted in etcd by default.
NamespaceIsolates groups of resources within one cluster. Names must be unique inside a namespace, not across namespaces. Nodes and PersistentVolumes are not namespaced.

Add -n shop to commands, or save the namespace in your context with kubectl config set-context --current --namespace=shop. A forgotten namespace shows an empty list.

Labels are the glue. A Deployment’s .spec.selector must match .spec.template.metadata.labels or the API rejects it, and the selector is immutable after creation in apps/v1. Do not create other Pods whose labels match its selector: Kubernetes does not stop you, and the Deployment then thinks it created them.

A compact manifest set

One file, five objects. The image name is a placeholder.

shop.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: shop
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
  namespace: shop
data:
  LOG_LEVEL: info
---
apiVersion: v1
kind: Secret
metadata:
  name: web-secret
  namespace: shop
stringData:
  DB_PASSWORD: change-me
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: shop
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  minReadySeconds: 10
  progressDeadlineSeconds: 300
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: registry.example.com/shop/web:1.4.2
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: web-config
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: web-secret
                  key: DB_PASSWORD
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 256Mi
          startupProbe:
            httpGet:
              path: /healthz
              port: 8080
            periodSeconds: 5
            failureThreshold: 30
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            periodSeconds: 5
            failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: shop
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
  • The image has a specific tag. The documentation advises avoiding :latest in production because it is harder to track which version runs and to roll back.
  • stringData takes plain strings, which are merged into data. Never commit a real secret value to git. Base64 is obscured, not encrypted.
  • The Service listens on port 80 and forwards to the container’s targetPort 8080.

Without a cluster, yamllint shop.yaml checks syntax and style only, and kubectl create deployment web --image=... --replicas=3 --dry-run=client -o yaml prints a starting manifest. Against a real cluster, kubectl apply --dry-run=server -f shop.yaml submits the request without persisting it, and the default --validate=strict checks the schema. Client-side kubectl apply --dry-run=client still contacts the API server, so it fails when no cluster is reachable.

Probes: startup, liveness, readiness

Probes are checks the kubelet runs against a container. Each answers a different question, so a failure has a different consequence.

ProbeQuestionWhen it fails
startupProbeHas the application finished starting?Until it succeeds, liveness and readiness probes are not run. If it fails, the kubelet kills the container and the restart policy applies.
livenessProbeShould this container be restarted?The kubelet restarts the container. It is meant to catch a deadlock, where the application runs but cannot make progress.
readinessProbeCan this container take traffic now?The EndpointSlice controller removes the Pod’s IP address from the EndpointSlices of all matching Services, so it stops receiving Service traffic. Readiness probes run for the whole life of the container.

The mechanisms are exec, grpc, httpGet and tcpSocket. Documented defaults: initialDelaySeconds 0, periodSeconds 10, timeoutSeconds 1, successThreshold 1 (it must be 1 for liveness and startup) and failureThreshold 3. Check the documentation for your version.

  • Startup. The documentation suggests one when the container usually needs more than initialDelaySeconds + failureThreshold × periodSeconds to start. In the manifest, periodSeconds: 5 times failureThreshold: 30 allows about 150 seconds.
  • Liveness. The documentation says to use it with caution: it must truly indicate unrecoverable failure such as a deadlock, and a wrong setup can cause cascading failures. Reasoning, not from the docs: a check that fails because a database is down cannot be fixed by restarting this container, so it is a poor liveness check.
  • Readiness is not only for startup. The documentation notes it is also useful later, for example when recovering from temporary faults or overloads.

Requests versus limits

Requests and limits are set per container. Different components use them, and that is the whole difference.

FieldUsed byEffect
requestskube-schedulerThe sum of the requests of the containers on a node must be less than the node’s capacity. A Pod whose requests fit nowhere stays Pending.
limits.cpukubelet, runtime, kernelEnforced by CPU throttling.
limits.memorykubelet, runtime, kernelEnforced by out of memory (OOM) kills. The kernel may terminate a container that exceeds its limit, and the kubelet restarts it if it can be restarted.

CPU is measured in CPU units: 0.1 equals 100m. Memory is measured in bytes, with suffixes such as M or the power-of-two Mi. If you set a limit but no request, and nothing applies a default request, Kubernetes uses the limit as the request. The manifest requests 100m CPU and 128Mi memory and limits memory to 256Mi. It sets no CPU limit, which is a policy choice.

  • Choose numbers from measurement. kubectl top pod shows current usage but needs the metrics-server service.
  • The failures look different. Too-large requests give Pending with a FailedScheduling event. Too-small memory limits give restarts and an OOMKilled reason.

Rollouts and rollbacks

A Deployment’s rollout is triggered if and only if its Pod template (.spec.template) changes, for example a new container image or new labels. Scaling does not trigger a rollout.

$ kubectl set image deployment/web web=registry.example.com/shop/web:1.4.3 -n shop
$ kubectl rollout status deployment/web -n shop
$ kubectl rollout history deployment/web -n shop
$ kubectl rollout history deployment/web -n shop --revision=2
# back to the previous revision, or to a specific one
$ kubectl rollout undo deployment/web -n shop
$ kubectl rollout undo deployment/web -n shop --to-revision=2

kubectl rollout status exits with 0 when the rollout completed and non-zero when the Deployment exceeded its progress deadline, so it works as a CI gate. It also accepts --timeout and --watch=false.

FieldMeaningDefault
strategy.typeRollingUpdate or Recreate.RollingUpdate
rollingUpdate.maxUnavailableMaximum number of Pods that can be unavailable during the update. A number or a percentage, rounded down.25%
rollingUpdate.maxSurgeMaximum number of Pods that can be created over the desired number. A number or a percentage, rounded up.25%
minReadySecondsHow long a new Pod must be ready, with no container crashing, before it counts as available.0
progressDeadlineSecondsHow long to wait for progress before Progressing becomes False with reason ProgressDeadlineExceeded.600
revisionHistoryLimitOld ReplicaSets kept for rollback.10

Those are the documented defaults, so check the documentation for your version. maxUnavailable and maxSurge cannot both be 0. The manifest uses maxSurge: 1 and maxUnavailable: 0: with 3 replicas, at most 4 Pods exist during the update and the number of available Pods never drops below 3, at the price of room for one extra Pod. “Available” depends on readiness, so a missing readiness probe makes rollouts less safe.

Undo is limited by history. Each revision lives in an old ReplicaSet, and revisionHistoryLimit caps how many are kept. Advice, not from the docs: use undo as a fast mitigation, then fix the manifest in git, because applying the old bad manifest again brings the bad template back.

Debugging order

The Kubernetes documentation starts with the same first step: look at the Pod, then at its recent events. Follow one order and stop at the first clear answer.

  1. kubectl get pods -n shop shows the status of every Pod. Add -w to watch changes.
  2. kubectl describe pod NAME -n shop shows the Pod’s current state and recent events, including the scheduler’s reasons. For the last termination reason and exit code, read lastState.terminated under status.containerStatuses in kubectl get pod NAME -n shop -o yaml.
  3. kubectl logs NAME -n shop, and --previous for the previous container’s crash log. Add -c CONTAINER for a multi-container Pod.
  4. kubectl events --for pod/NAME -n shop lists events for that Pod only. The docs also show kubectl get events.
  5. Only then get inside. kubectl exec -it NAME -n shop -- sh works when the image includes a shell. kubectl debug -it NAME -n shop --image=busybox:1.28 --target=CONTAINER adds an ephemeral debug container (stable since v1.25, per the docs). kubectl debug NAME -it --image=ubuntu --share-processes --copy-to=NAME-debug debugs a copy, and --copy-to=NAME-debug --container=CONTAINER -- sh replaces the command of a crashing container in the copy.
StatusWhat it means and where to lookTypical causes and fixes
PendingNot scheduled onto a node. Read the scheduler’s messages in the Events of describe.Not enough CPU or memory in the cluster, or a hostPort. Adjust requests, add nodes or free capacity. Docs example: FailedScheduling with Insufficient memory.
ImagePullBackOffThe image could not be pulled. Kubernetes retries with an increasing delay, capped at 300 seconds. Check the Events, image name and tag.Wrong name or tag, image not pushed, or a private registry without an imagePullSecret, which must be in the Pod’s namespace and of type kubernetes.io/dockerconfigjson or kubernetes.io/dockercfg. Try pulling by hand.
CrashLoopBackOffThe container keeps crashing and the kubelet restarts it with delays of 10s, 20s, 40s and so on, capped at 300s. The timer resets after the container runs successfully for 10 minutes. Read logs --previous and lastState.terminated.Commonly an application error at start, bad config, insufficient resources or a failing probe. Fix the cause, because waiting does not.
OOMKilledTerminated for exceeding the memory limit. Shown as lastState.terminated.reason in get pod -o yaml. The docs example shows exit code 137 and the container alternating between OOMKilled and Running as the kubelet restarts it.Raise the memory limit if the application really needs it, or fix the leak or the load.

For a full walk-through of one case, see the CrashLoopBackOff tutorial in the tutorials section.

Cheat sheet and checklist

TaskCommand
Apply a filekubectl apply -f shop.yaml
List Pods (and watch)kubectl get pods -n shop (-w)
Pod state and eventskubectl describe pod NAME -n shop
Logs, current and crashedkubectl logs NAME, kubectl logs NAME --previous, -c CONTAINER
Events for one Podkubectl events --for pod/NAME -n shop
Shell in a containerkubectl exec -it NAME -- sh
Debug container / copykubectl debug -it NAME --image=busybox:1.28 --target=CONTAINER, --copy-to=NAME-debug
Change the imagekubectl set image deployment/web web=IMAGE:TAG
Wait for a rolloutkubectl rollout status deployment/web
Revisionskubectl rollout history deployment/web (--revision=N)
Roll backkubectl rollout undo deployment/web (--to-revision=N)
Resource usagekubectl top pod -n shop (needs metrics-server)

Before you ship

  • The image tag is pinned. No :latest.
  • Every container has requests. Memory limits come from measurement.
  • A readiness probe exists, slow starters have a startup probe, and liveness only reports failures a restart can fix.
  • Secret values are not in git. Secrets are unencrypted in etcd by default, so restrict access and read the Secrets documentation.
  • CI runs kubectl rollout status and treats a non-zero exit as a failed deploy.

Official documentation

Keep going

How this guide was checked: the manifest passed yamllint and every Deployment field survived kubectl’s decoding into its built-in types (kubectl client v1.37.0, no cluster). Flags and examples were compared with the built-in help of that client. Definitions, defaults and behaviours come from the kubernetes.io pages listed above, read in their source Markdown in the kubernetes/website repository. There is no cluster output in this guide because none was available, and messages and defaults can change between Kubernetes versions, so check the documentation for yours.

benmabrouk.fr: free DevOps and SRE learning resources, written from production experience.

Scroll to Top