Pod anatomy — apiVersion, kind, spec, the atomic unit of scheduling
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
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 withnodeSelector: {workload: high-memory}runs only on Nodes labelledworkload=high-memory.affinity: more expressive than nodeSelector.nodeAffinitymatches Node labels with required or preferred rules.podAffinity/podAntiAffinitymatches 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 havedefault-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 ofspecsince 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.hostnameandsubdomain: 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-Foundationscovers Linux namespaces (man 7 namespaces); the Pod spec’sshareProcessNamespace,hostNetwork, andhostPIDsettings map directly onto these primitives. - The Docker course part
XXXI-Docker-Networkingcovers container networking; Pod-level networking is the cluster-level equivalent. - The Ansible course part
XXXV-Ansible-Scriptingcovers configuration management; the Pod spec is the same idea for workloads.
Quiz
Knowledge check · 4 questions
Q1. What is the atomic unit of scheduling in Kubernetes?
Q2. A Pod is durable: once scheduled, it stays on its node until manually deleted.
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.
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: gvisorfor 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=falseto see the full manifest the cluster has.