Kubernetes Pod Lifecycle and Phases
A Pod in Kubernetes is not a static thing — it goes through a well-defined lifecycle from creation to termination. Understanding this lifecycle is essential because it directly affects how your application starts up, handles requests, and shuts down gracefully.
If you have ever seen a Pod stuck in Pending, wondered why a rolling update drops requests, or needed to drain in-flight HTTP connections before a Pod dies, the answer lies in the Pod lifecycle.
In this article, you will learn every phase a Pod can be in, the lifecycle hooks Kubernetes provides (postStart and preStop), the terminationGracePeriodSeconds setting, and how all of these work together to enable zero-downtime deployments — especially for .NET applications.
Core Concepts
Step 1: Pod Phases
Every Pod has a status.phase field that tells you where it is in its lifecycle. There are exactly five phases:
| Phase | Meaning |
|---|---|
Pending | The Pod has been accepted by the cluster, but one or more containers are not yet running. This includes time spent waiting for scheduling (finding a Node), downloading container images, and waiting for init containers to complete. |
Running | The Pod has been bound to a Node and all containers have been created. At least one container is running, starting, or restarting. |
Succeeded | All containers in the Pod have terminated successfully (exit code 0) and will not be restarted. This is typical for Jobs and batch workloads. |
Failed | All containers have terminated, and at least one container exited with a non-zero exit code or was killed by the system. |
Unknown | The state of the Pod cannot be determined — usually because communication with the Node where the Pod is running has been lost. |
Key point: A Pod in the Running phase is not necessarily ready to receive traffic. The Pod might still be booting up your .NET application. That is why Kubernetes uses separate readiness probes to determine when a Pod should be added to a Service's endpoint list.
Step 2: Container States
Within a running Pod, each container has its own state. You can see it with kubectl describe pod under the State field:
| State | Meaning |
|---|---|
Waiting | The container is not yet running. It might be pulling the image, waiting for a ConfigMap, or stuck in a CrashLoopBackOff. |
Running | The container is executing without issues. |
Terminated | The container has finished execution — either successfully or with an error. The exitCode field tells you which. |
Step 3: Pod Conditions
Pod phases give a high-level view, but Pod conditions give you more detail. You can see them with kubectl describe pod or kubectl get pod -o yaml:
| Condition | Meaning |
|---|---|
PodScheduled | The Pod has been assigned to a Node. |
Initialized | All init containers have completed successfully. |
ContainersReady | All containers in the Pod are ready. |
Ready | The Pod is ready to serve traffic and should be added to the endpoints of matching Services. This is the condition that Services actually check. |
Step 4: The Startup Sequence
When Kubernetes creates a Pod, the following happens in order:
- Scheduling: The kube-scheduler picks a Node for the Pod. The Pod is in
Pendingphase. - Image pull: The kubelet on the selected Node pulls the container image (if not already cached). Still
Pending. - Init containers: If the Pod has init containers, they run one by one in order. Each must succeed before the next starts. Still
Pending. - Main containers start: All main containers start simultaneously. The Pod moves to
Running. - postStart hook: If defined, the
postStarthandler runs immediately after each container starts. The container is not marked asRunninguntil the hook completes. - Startup probe: If defined, no other probes run until the startup probe succeeds. This protects slow-starting applications.
- Liveness and readiness probes: Once the startup probe passes (or if there is no startup probe), liveness and readiness probes begin.
- Ready: When the readiness probe passes, the Pod's
Readycondition becomesTrueand the Pod is added to Service endpoints. Traffic starts flowing.
Step 5: Lifecycle Hooks — postStart and preStop
Kubernetes provides two lifecycle hooks that let you run code at specific points:
| Hook | When It Runs | Common Use Cases |
|---|---|---|
postStart | Immediately after the container is created. Runs in parallel with the container's entrypoint — there is no guarantee it runs before the entrypoint. | Warm up caches, register the instance with a service registry, write a log entry. |
preStop | Immediately before the container is terminated. Runs before SIGTERM is sent to the container process. | Drain connections, deregister from load balancer, flush buffers, complete in-flight requests. |
Both hooks support two handler types:
exec— Run a command inside the container.httpGet— Send an HTTP GET request to a specified endpoint and port.
Important: If a postStart hook fails, the container is killed. If a preStop hook hangs, it is bounded by terminationGracePeriodSeconds.
Step 6: The Termination Sequence
When Kubernetes needs to terminate a Pod (due to a rolling update, scale-down, node drain, or manual deletion), the following happens in order:
- Pod marked for termination: The Pod's state is set to
Terminating. The Pod is removed from Service endpoints — no new traffic is routed to it. - preStop hook runs: If defined, the
preStophandler executes. This gives your application time to finish in-flight requests. - SIGTERM sent: After the
preStophook completes (or immediately if there is no hook), Kubernetes sends aSIGTERMsignal to the main process (PID 1) in each container. - Grace period countdown: The
terminationGracePeriodSecondstimer starts (default: 30 seconds). Your application should catch SIGTERM and shut down cleanly within this window. - SIGKILL: If the container is still running after the grace period expires, Kubernetes sends
SIGKILL— a forceful, immediate kill that cannot be caught or ignored.
For .NET developers: The ASP.NET Core hosting model already handles SIGTERM by default. When the process receives SIGTERM, IHostApplicationLifetime.ApplicationStopping is triggered and Kestrel stops accepting new connections while completing in-flight requests. The default ShutdownTimeout in Kestrel is 30 seconds, which aligns with Kubernetes' default grace period.
Step 7: terminationGracePeriodSeconds
This field controls how long Kubernetes waits between sending SIGTERM and sending SIGKILL. The default is 30 seconds.
How to choose the right value:
- Too short: Your application does not have time to drain connections → clients get connection resets → data loss in background tasks.
- Too long: Rolling updates and node drains take forever because Kubernetes waits for each Pod to terminate.
- Rule of thumb: Set it to your application's maximum expected request duration plus a small buffer. For a typical .NET web API with 10-second max requests,
30–45seconds is usually enough.
Step 8: The preStop + Sleep Pattern
There is a subtle timing issue during Pod termination: Kubernetes removes the Pod from Service endpoints and sends the preStop hook concurrently, but endpoint removal is eventually consistent. Some kube-proxy or Ingress controllers might still route traffic to the terminating Pod for a few seconds.
A common workaround is to add a short sleep in the preStop hook. This keeps the container alive long enough for endpoint updates to propagate:
This 10-second sleep gives kube-proxy, Ingress controllers, and DNS caches time to stop sending traffic before the application begins its shutdown sequence.
Step 9: Restart Policies
The restartPolicy field on a Pod spec determines what happens when a container exits:
| Policy | Behavior | Used By |
|---|---|---|
Always | Always restart the container, regardless of exit code. This is the default. | Deployments, ReplicaSets, DaemonSets |
OnFailure | Restart only if the container exits with a non-zero exit code. | Jobs, CronJobs |
Never | Never restart. Once the container exits, it stays terminated. | Debugging, one-time tasks |
When a container keeps crashing, Kubernetes applies exponential backoff: it waits 10s, 20s, 40s, ... up to 5 minutes before each restart attempt. This is the infamous CrashLoopBackOff state.
Hands-On: Kubernetes Commands
Check Pod Phase
View Pod Conditions
View Container States
Look for the State, Last State, and Restart Count fields under each container.
Watch Pod Status Changes in Real Time
Check Events for a Pod
Events show the sequence: Scheduled → Pulling → Pulled → Created → Started → (Killing if terminated).
See Why a Pod Failed
View Previous Container Logs (After Crash)
Force Delete a Stuck Pod
Warning: Force deletion skips the graceful shutdown sequence. Use it only when a Pod is stuck in Terminating and will not go away.
Step-by-Step Example
Scenario 1: Observing Pod Lifecycle Phases
We will create a single Pod running a .NET 10 Notification API and observe it through all lifecycle phases.
1. Create the Pod with Lifecycle Hooks
Apply the Pod:
2. Watch the Pod Transition Through Phases
In a separate terminal, watch the Pod in real time:
You will see it move from Pending → ContainerCreating → Running:
Notice the 0/1 to 1/1 transition — that is when the readiness probe passed and the Pod became Ready.
3. Inspect Pod Conditions
Expected output:
4. Verify the postStart Hook
You should see the timestamp logged by the postStart hook.
5. Delete the Pod and Observe Termination
If you are watching with --watch, you will see:
During the Terminating state, Kubernetes: (1) removed the Pod from Service endpoints, (2) called the preStop hook (HTTP GET to /shutdown), (3) sent SIGTERM, and (4) waited up to 45 seconds before SIGKILL.
Scenario 2: Graceful Shutdown in a Deployment
In production, you use Deployments rather than bare Pods. Here we add the preStop sleep pattern to ensure zero-downtime rolling updates.
6. Deploy with preStop Sleep
Apply the Deployment:
7. Trigger a Rolling Update
Simulate updating the image to trigger the termination sequence on old Pods:
Watch the rollout:
During the rolling update, each old Pod goes through the full termination sequence: endpoint removal → preStop sleep (10s) → SIGTERM → graceful shutdown. The 10-second sleep ensures that kube-proxy has time to update its iptables rules before the application starts shutting down. Meanwhile, the new Pod must pass its readiness probe before the next old Pod is terminated.
8. Verify the Timeline
You will see events showing the Deployment scaling up the new ReplicaSet and scaling down the old one, confirming the graceful transition.
Summary
- Pods go through five phases:
Pending,Running,Succeeded,Failed, andUnknown. - Within a Pod, each container has its own state:
Waiting,Running, orTerminated. - Pod conditions (
PodScheduled,Initialized,ContainersReady,Ready) provide granular status — theReadycondition determines when a Pod receives traffic. - postStart hook runs immediately after container creation (in parallel with the entrypoint). preStop hook runs before SIGTERM during termination.
terminationGracePeriodSecondsis the window between SIGTERM and SIGKILL. Default is 30 seconds. Set it based on your application's shutdown time.- The preStop + sleep pattern (e.g.,
sleep 10) protects against traffic being routed to a terminating Pod due to endpoint propagation delay. restartPolicycontrols container restart behavior:Always(default for Deployments),OnFailure(Jobs), orNever.- ASP.NET Core handles SIGTERM natively via
IHostApplicationLifetime.ApplicationStopping— Kestrel drains connections automatically within itsShutdownTimeout.