Pods — The Atomic Unit
A Pod is the smallest deployable unit in Kubernetes. It represents a single instance of a running process in your cluster. A pod encapsulates one or more containers, shared storage (volumes), a unique network IP, and options that govern how the containers run. Understanding pods is the foundation for everything else in Kubernetes.
What Is a Pod?
While Docker containers are the unit you build and ship, pods are the unit Kubernetes schedules and manages. A pod always runs on a single node. All containers in a pod:
- Share the same network namespace — they can communicate via
localhostand share port space. - Share the same IPC namespace — they can use inter-process communication (semaphores, shared memory).
- Can share volumes — a volume mounted to the pod is accessible to all containers in it.
In practice, most pods contain a single container. Multi-container pods are used for tightly coupled helper processes (sidecar, ambassador, adapter patterns).
Pod Spec
Here is a minimal pod manifest:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
Key fields:
| Field | Description |
|---|---|
apiVersion: v1 | Pods are in the core API group |
metadata.name | Unique name within a namespace |
metadata.labels | Key-value pairs used by selectors |
spec.containers[].image | Container image (always pin a specific tag) |
spec.containers[].resources | CPU/memory requests and limits |
Pods created directly have no self-healing. If the node fails, the pod is lost. Always use a higher-level workload object: Deployment for stateless apps, StatefulSet for stateful apps, DaemonSet for per-node agents, or Job for batch tasks.
Multi-Container Patterns
When multiple containers in a pod are tightly coupled, they should live together. Three classical patterns:
Sidecar
A helper container that extends or enhances the main container without modifying it. Example: a Fluent Bit container that reads logs from a shared volume and ships them to Elasticsearch, alongside an nginx container.
Ambassador
A proxy container that handles outbound connections on behalf of the main container. Example: a proxy container that automatically retries failed API calls or handles service discovery, so the main app just talks to localhost:8080.
Adapter
Transforms output from the main container into a format expected by external systems. Example: a container that converts the main app's metrics from a custom format into Prometheus format.
Pod Lifecycle
A pod's status.phase field represents where it is in its lifecycle:
| Phase | Meaning |
|---|---|
Pending | Pod accepted by the cluster but not yet running. Containers are being scheduled or image is being pulled. |
Running | Pod bound to a node and at least one container is running (or starting/restarting). |
Succeeded | All containers in the pod have terminated successfully and will not be restarted. |
Failed | All containers have terminated, and at least one exited with a non-zero code or was killed by the system. |
Unknown | Pod state cannot be determined, usually due to a communication error with the node. |
Check pod status with:
kubectl get pod nginx-pod
kubectl describe pod nginx-pod # full status + events
Restart Policies
The spec.restartPolicy field controls what happens when a container in the pod exits. Options:
| Policy | Behaviour | Use case |
|---|---|---|
Always | Restart the container whenever it exits (default) | Long-running services (web servers, APIs) |
OnFailure | Restart only if container exits with non-zero code | Batch jobs that should retry on failure |
Never | Never restart regardless of exit code | One-shot tasks, debugging |
Restart attempts use exponential back-off: 10s, 20s, 40s, ... up to 5 minutes, then reset after 10 minutes of success. A pod that keeps restarting shows CrashLoopBackOff.
Resource Requests & Limits
Every container should declare its resource requirements. Kubernetes uses these values for two distinct purposes:
- Requests — the amount of CPU/memory the scheduler uses when deciding which node to place the pod on. The node must have at least this much available.
- Limits — the maximum CPU/memory the container is allowed to use. Exceeding memory limit causes the container to be OOM-killed. Exceeding CPU limit causes CPU throttling (no kill).
resources:
requests:
memory: "128Mi"
cpu: "250m" # 250 millicores = 0.25 of one CPU core
limits:
memory: "256Mi"
cpu: "500m"
CPU is compressible: exceeding the limit throttles the container but doesn't kill it. Memory is incompressible: exceeding the limit kills the container with OOMKilled. Set memory limits carefully — too low and your app gets killed unexpectedly; too high and you waste cluster resources.