Pod lifecycle, restart policy, and termination
What you'll learn
- Trace the Pod lifecycle from Pending through termination
- Explain restartPolicy: Always, OnFailure, Never
- Reason about graceful termination and the role of preStop hooks
- Distinguish between controller-managed Pods and direct Pods
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’s lifecycle is the choreography between the kubelet, the CRI, the application process, and the controller that manages it. This lesson walks through that lifecycle from creation to termination, explains the restart policy options, and covers the discipline around graceful shutdown.
The Pod lifecycle states
A Pod goes through five high-level states (status.phase):
- Pending: accepted by the cluster, but not yet running. The scheduler is choosing a node; the kubelet is pulling images and starting the sandbox.
- Running: bound to a node; at least one container is running, starting, or restarting.
- Succeeded: all containers have terminated successfully (exit code 0) and will not be restarted.
- Failed: at least one container has terminated in failure (non-zero exit code) and will not be restarted (or has not yet been restarted).
- Unknown: the state cannot be obtained, typically because the kubelet cannot communicate with the API server (network partition, node failure).
stateDiagram-v2
[*] --> Pending
Pending --> Running: bound to node, containers start
Running --> Succeeded: all containers exit 0, restartPolicy != Always
Running --> Failed: container exits non-zero, restartPolicy = Never
Running --> Running: container restarts (restartPolicy)
Running --> Unknown: kubelet unreachable
Unknown --> Running: kubelet recovers
Pending --> Failed: admission rejection, image pull failure
The transitions are not all possible. A Pending Pod
typically goes to Running once bound. A Running Pod goes
to Succeeded only if restartPolicy: Never or
OnFailure and all containers exit 0. A Running Pod goes
to Failed only if a container exits with a non-zero code
and restartPolicy: Never is set.
Part IX covers Pod phases in depth; this lesson focuses on restart policy and termination.
restartPolicy
spec:
restartPolicy: Always # default for Deployments, StatefulSets, DaemonSets
Three values:
Always(default for Pods managed by Deployments, StatefulSets, DaemonSets): every container exit triggers a restart, regardless of exit code. The Pod stays inRunningstate.OnFailure(default for Jobs): a container is restarted only if it exits with a non-zero code. Success (exit 0) leaves the Pod inSucceeded.Never: containers are never restarted. The Pod terminates when the last container exits. Used for Jobs that should run once to completion.
The restart applies per-container. In a multi-container Pod:
Always: every container that exits is restarted (each on its own restart timer; the kubelet backs off failed restarts with exponential delay).OnFailure: each container is restarted independently on non-zero exit.Never: no container is restarted.
The termination sequence
When a Pod is deleted, the kubelet orchestrates a graceful shutdown:
- API server marks the Pod for deletion.
metadata.deletionTimestampis set; the Pod is excluded from controllers’ selectors (with foreground propagation, the API server holds the request until dependents are gone). - Pre-stop hook runs (if defined). The kubelet executes
the
lifecycle.preStophandler. The hook is a chance for the application to do graceful work (deregister from service discovery, flush in-memory state, finish in-flight requests). - SIGTERM sent to containers. Each container’s PID 1 receives SIGTERM. The application should catch it and start shutdown.
- Grace period countdown. The kubelet waits
terminationGracePeriodSeconds(default 30s) for the containers to exit cleanly. - SIGKILL sent. If containers are still running after the grace period, the kubelet sends SIGKILL. The Pod is then marked as terminated.
sequenceDiagram
participant API as API server
participant Kubelet
participant App as Application<br/>(PID 1)
API->>Kubelet: DELETE Pod
Kubelet->>App: run preStop hook (if defined)
Kubelet->>App: SIGTERM
Note over App: graceful shutdown
alt exits within grace period
App-->>Kubelet: exit 0
Kubelet-->>API: Pod terminated
else still running after grace
Kubelet->>App: SIGKILL
Kubelet-->>API: Pod terminated (forced)
end
The full grace period default is 30 seconds. This includes the preStop hook runtime. If your preStop hook needs 10 seconds and the application needs 25 seconds to drain, the total grace period must be at least 35 seconds.
Termination grace period
spec:
terminationGracePeriodSeconds: 60
The grace period is per-Pod and applies to all containers. Set it based on the slowest container’s shutdown needs:
- Web servers with connection draining: 30-60 seconds.
- Batch workers that flush state: 30-60 seconds.
- In-memory caches that dump on shutdown: 60-120 seconds.
- Stateless services with no cleanup: 5-10 seconds.
Production discipline: set the grace period explicitly. The 30-second default is fine for most stateless workloads; for stateful or slow-shutting-down workloads, raise it.
Pre-stop hooks
spec:
containers:
- name: nginx
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5 && nginx -s quit"]
preStop:
httpGet:
path: /admin/drain
port: 8080
Three hook types:
exec: run a command in the container.httpGet: send an HTTP request to the container.tcpSocket: open a TCP connection to a port.
The hook runs before SIGTERM. Use it for:
- Service deregistration: call the Service registry’s deregister endpoint before SIGTERM, so the Service stops routing traffic to the Pod.
- State flush: dump in-memory state to disk before SIGTERM.
- Sleep: a common pattern (
sleep 5) to give the Service time to deregister the Pod before SIGTERM hits the application.
Lifecycle hooks summary
| Hook | When | Use case |
|---|---|---|
postStart | After container starts, before readiness probe | Warm caches, generate config |
preStop | Before SIGTERM | Drain connections, deregister, flush state |
postStart runs after the container’s process starts but
before the kubelet considers the container Started. It runs
in parallel with the entrypoint — there is no ordering
guarantee. Use it for warm-up tasks that the entrypoint does
not handle.
preStop runs synchronously before SIGTERM. The kubelet
waits for the hook to complete (or timeout) before sending
SIGTERM.
The role of the controller
Directly-created Pods (kubectl run, kubectl apply -f pod.yaml) do not get recreated when they terminate. The
controller (Deployment, StatefulSet, DaemonSet, Job) is
responsible for recreating Pods to maintain the desired state.
flowchart LR
User[Operator] -->|apply| Manifest[Manifest]
Manifest --> Deployment[Deployment controller]
Deployment -->|manages| RS["ReplicaSet"]
RS -->|creates/manages| Pod1[Pod]
RS -->|creates/manages| Pod2[Pod]
Pod1 -.->|terminates| RS
RS -.->|recreates| Pod3[Pod]
The controller pattern:
- Operator applies a manifest (Deployment with replicas: 3).
- Deployment controller creates a ReplicaSet.
- ReplicaSet creates 3 Pods.
- One Pod terminates (liveness failure, drain, etc.).
- ReplicaSet sees one Pod is missing; creates a new one.
- The cluster converges to 3 Pods again.
Production discipline: never deploy Pods directly for
long-running workloads. Use a controller. Direct Pods are
for one-off tasks (a Job with restartPolicy: Never) and
debugging.
Cross-course references
- The Linux course part
VI-Linux-Processescovers POSIX signals and process lifecycle; SIGTERM and SIGKILL are the same primitives the kubelet uses. - The Docker course part
XXX-Docker-Lifecyclecovers container lifecycle and shutdown; Pod-level lifecycle is the cluster-level equivalent. - The Ansible course part
XXXV-Ansible-Scriptingcovers service shutdown discipline; preStop hooks are the cluster-level equivalent.
Quiz
Knowledge check · 4 questions
Q1. What is the default restartPolicy for a Pod created by a Deployment?
Q2. When a Pod is deleted, the kubelet sends SIGKILL first and only sends SIGTERM if the application catches SIGKILL.
Q3. A web server handles long-running requests (30-60 seconds). When the Pod is deleted, requests in-flight are cut off mid-response. Walk through the failure mode and the fix.
Pod `web-7c8` is part of a Deployment with 5 replicas. The application serves HTTP requests that take 30-60 seconds to complete. When the Pod is deleted (rolling update, node drain), the application receives SIGTERM but does not handle it gracefully; in-flight requests are cut off mid-response. Clients see 502 errors.
Q4. When is a `preStop` hook with `sleep 5` a reasonable pattern, and when is it the wrong choice?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Always implement SIGTERM handling in the application. The application should stop accepting new connections, drain in-flight requests, and exit. The orchestrator cannot know when the application is safe to kill.
- Set
terminationGracePeriodSecondsbased on the slowest container’s shutdown needs. The 30-second default is fine for stateless workloads; raise it for stateful or slow-shutting-down workloads. - Never deploy long-running Pods directly. Use a controller (Deployment, StatefulSet, DaemonSet) that recreates Pods as needed.
- Configure the readiness probe to fail during shutdown. This signals the Service to stop routing traffic earlier and reduces the number of cut-off requests.
- Test graceful shutdown under load. Trigger a rolling update or node drain with traffic running; verify that no requests are cut off mid-response.