Skip to main content
RunBook Academy

KubernetesVIII · PodsPods

Pod anatomy — apiVersion, kind, spec, the atomic unit of scheduling

Intermediate⏱ ~18 minkubectl

What you'll learn

  • Identify the top-level fields of a Pod manifest
  • Explain why a Pod is the atomic unit of scheduling
  • Trace the spec fields that influence scheduling and lifecycle
  • Reason about Pods as the smallest unit Kubernetes can create, schedule, and restart

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

A Pod is the smallest deployable unit in Kubernetes. It is not a container; it is the envelope around one or more containers that share namespaces and lifecycle. This lesson dissects the Pod object, walks every field that production operators care about, and explains why the Pod abstraction exists at all.

Why Pods exist

Kubernetes does not schedule containers. It schedules Pods. A Pod wraps one or more containers that need to share resources (network namespace, IPC namespace, optionally PID namespace, volumes). The Pod is:

  • The atomic unit of scheduling: the scheduler binds a Pod to a node, never a single container.
  • The atomic unit of lifecycle: containers in a Pod start, stop, and restart together (with rare exceptions like native sidecars).
  • The atomic unit of network identity: every Pod has one IP address, one set of ports; all containers in the Pod share that IP.
  • The atomic unit of state: ephemeral by default, but StatefulSets use stable Pod identities to attach stable storage and ordinal names.
flowchart LR
    Node --> Pod
    Pod --> Container1["Container A<br/>(main)"]
    Pod --> Container2["Container B<br/>(sidecar)"]
    Pod --> NetNS["Shared network namespace"]
    Pod --> Vol["Shared volumes"]

If your workload needs two processes to share network listeners (e.g., a main process and a sidecar that scrapes metrics), they belong in the same Pod. If they don’t, they belong in separate Pods.

A complete Pod manifest

apiVersion: v1
kind: Pod
metadata:
  name: web
  namespace: team-a-prod
  labels:
    app: web
    tier: frontend
  annotations:
    description: "Production web tier"
spec:
  # Scheduling
  nodeSelector:
    workload: high-memory
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          topologyKey: kubernetes.io/hostname
          labelSelector:
            matchLabels:
              app: web
  tolerations:
  - key: dedicated
    operator: Equal
    value: web
    effect: NoSchedule
  schedulerName: default-scheduler
  priorityClassName: high-priority
  runtimeClassName: gvisor

  # Containers
  containers:
  - name: nginx
    image: nginx:1.27.2
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 8080
      protocol: TCP
    env:
    - name: LOG_LEVEL
      value: info
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 512Mi
    volumeMounts:
    - name: data
      mountPath: /var/www/html
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
    startupProbe:
      exec:
        command: ["sh", "-c", "curl -f http://localhost:8080/healthz"]
      failureThreshold: 30
      periodSeconds: 5
    lifecycle:
      preStop:
        exec:
          command: ["sh", "-c", "sleep 5"]
    securityContext:
      runAsNonRoot: true
      runAsUser: 1000
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
      readOnlyRootFilesystem: true

  # Init containers
  initContainers:
  - name: init-db
    image: busybox:1.36
    command: ["sh", "-c", "until nc -z db 5432; do sleep 1; done"]

  # Pod-level configuration
  restartPolicy: Always
  dnsPolicy: ClusterFirst
  serviceAccountName: web
  hostNetwork: false
  hostname: web
  subdomain: web
  terminationGracePeriodSeconds: 30
  activeDeadlineSeconds: 3600
  volumes:
  - name: data
    emptyDir: {}

  # Sidecar / native sidecar (1.28+)
  ephemeralContainers: []

This is more verbose than a typical Pod manifest but every field here is something production workloads touch. The lesson sections below explain what each does.

Scheduling fields

These fields determine where the Pod runs:

  • nodeSelector: a label-selector-style map that matches Node labels. A Pod with nodeSelector: {workload: high-memory} runs only on Nodes labelled workload=high-memory.
  • affinity: more expressive than nodeSelector. nodeAffinity matches Node labels with required or preferred rules. podAffinity / podAntiAffinity matches other Pods’ labels and topology keys (kubernetes.io/hostname, topology.kubernetes.io/zone, custom).
  • tolerations: allow the Pod to be scheduled on Nodes with matching taints. Without a toleration, the Pod is rejected from tainted Nodes.
  • schedulerName: which scheduler to use. Most clusters have default-scheduler. Custom schedulers can be registered for specialised workloads (batch, GPU, etc.).
  • priorityClassName: a named priority. Higher-priority Pods can preempt lower-priority Pods during scheduling.
  • runtimeClassName: which container runtime to use (runc, gvisor, kata-containers). Used for sandboxed workloads.
  • topologySpreadConstraints: spread Pods across zones or hosts. Part of spec since 1.22.

These fields are evaluated by the scheduler when the Pod is created. Once bound, the Pod runs on the chosen Node; the scheduler does not move it.

Lifecycle fields

These fields control how the Pod behaves over time:

  • restartPolicy: Always (default for Deployments), OnFailure (default for Jobs), Never. Determines what happens when a container exits.
  • dnsPolicy: ClusterFirst (default) — use the cluster’s DNS service first. Default — use the node’s DNS. ClusterFirstWithHostNet — for hostNetwork Pods.
  • serviceAccountName: which ServiceAccount the Pod runs as. Tokens are mounted at /var/run/secrets/kubernetes.io/serviceaccount.
  • terminationGracePeriodSeconds: how long to wait after SIGTERM before SIGKILL. Default 30.
  • activeDeadlineSeconds: max time the Pod can run before being killed. Default unset (no deadline). Used by Jobs.
  • hostname and subdomain: the Pod’s hostname inside the cluster. Used for headless Service discovery.
  • hostNetwork: use the node’s network namespace directly. Pod IP = node IP.

Containers

The spec.containers list is where the workload actually lives. Each container has its own image, ports, env, probes, and security context. Pod-level configuration (dnsPolicy, serviceAccountName) applies to all containers unless overridden per-container.

A Pod can have at most one restartPolicy: Always constraint per container, but multiple containers can share the same restart policy. A Pod with two containers and restartPolicy: Always will have both containers restarted on exit; a Pod with restartPolicy: OnFailure will only restart exited containers that failed.

How Pods are addressed

A Pod is addressed by:

  • API group + version + kind: v1/Pod
  • namespace + name: team-a-prod/web-7c8
  • optional UID: the kubelet assigns a UID at creation

URLs:

/api/v1/namespaces/<namespace>/pods
/api/v1/namespaces/<namespace>/pods/<name>

The API server enforces namespace uniqueness on Pod names within a namespace, exactly as for any namespaced object.

Init containers and ephemeral containers

Two special container lists in the Pod spec:

  • initContainers: run sequentially before the main containers. Each must succeed before the next starts. Used for setup, migration, waiting-for-dependencies.
  • ephemeralContainers: added to a running Pod via the API server’s pod subresource. Used for debugging (kubectl debug).

Part XI covers init containers in depth; Part VI-04 covered ephemeral containers.

Cross-course references

  • The Linux course part I-Linux-Foundations covers Linux namespaces (man 7 namespaces); the Pod spec’s shareProcessNamespace, hostNetwork, and hostPID settings map directly onto these primitives.
  • The Docker course part XXXI-Docker-Networking covers container networking; Pod-level networking is the cluster-level equivalent.
  • The Ansible course part XXXV-Ansible-Scripting covers configuration management; the Pod spec is the same idea for workloads.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the atomic unit of scheduling in Kubernetes?

  2. Q2. A Pod is durable: once scheduled, it stays on its node until manually deleted.

  3. Q3. A workload needs two containers that share a network namespace so the sidecar can intercept traffic on localhost. The two containers should restart together. Where do these containers belong?

    Container A is the main app listening on port 8080. Container B is a sidecar that proxies traffic to port 8080 over localhost. Both containers should be scheduled together and share network identity. The sidecar needs access to the main container's localhost.

  4. Q4. Name three spec fields that influence Pod scheduling and explain what each does.

Passing score: 75%. Answers are checked in this browser.

Production discipline

  • Pods are not durable. Manage them with controllers (Deployment, StatefulSet, DaemonSet), not directly.
  • Co-locate containers in one Pod only when they share namespaces. Network or IPC sharing is the legitimate reason; otherwise they belong in separate Pods with a Service connecting them.
  • Set every scheduling field explicitly in production. Relying on defaults (e.g., no priorityClassName) means the Pod can be preempted by anything.
  • Use runtimeClassName: gvisor for untrusted code. A Pod sandboxed with gvisor or kata is the difference between a kernel-level compromise and a sandboxed one.
  • Inspect the full Pod spec before debugging. Use kubectl get pod <name> -o yaml --show-managed-fields=false to see the full manifest the cluster has.