⏳ Why Does Your Kubernetes Pod Always Take Exactly 30 Seconds to Die?
If you’ve ever watched kubectl get pods during a rollout and noticed every terminating pod hangs around for exactly 30 seconds — this…
⏳ Why Does Your Kubernetes Pod Always Take Exactly 30 Seconds to Die?
If you’ve ever watched kubectl get pods during a rollout and noticed every terminating pod hangs around for exactly 30 seconds — this article explains why, and why the fix depends on which language your app is written in.

🔄 The Pod Termination Lifecycle
When a pod is scheduled for removal (scale-down, rollout, eviction), it goes through a fixed sequence:

Lifecycle of pod termination
Two things worth calling out:
- ⚡ Steps 4’s two branches run in parallel, not in sequence. The pod stops receiving new traffic from the Service at roughly the same time SIGTERM is sent — but “roughly” matters, which is why in-flight requests can still fail (more on this in the testing section).
- ⏳ The grace period starts the moment the pod enters
Terminating, not when SIGTERM is sent.
🧩 Single vs Multiple Replicas
Single replica — healthy: Traffic flows normally through the Service to the one running pod.
Single replica — terminating: The pod is still attached to test-svc:80 but is in Terminating state — any request routed to it during this window is racing the shutdown.
Multiple replicas — healthy: The Service load-balances across all running pods.
Multiple replicas — one terminating: Traffic is expected to shift to the healthy replica once the terminating pod is pulled from endpoints — but when that pull happens relative to SIGTERM is exactly what causes failures under Istio, since Envoy’s endpoint update isn’t instant either.
🚦 The Two Signals That Matter
🛑 SIGTERM A graceful termination signal sent to a process, allowing it to perform cleanup before exiting. Can be intercepted and handled in code.
💀 SIGKILL An immediate, unconditional termination signal issued by the kernel. Cannot be intercepted, blocked, or ignored.
🎯 The Real Root Cause: PID 1 and Default Signal Handlers
This is the part most people miss.
For a normal process (PID > 1): Linux provides a default signal handler. SIGTERM’s default action is “terminate the process.” Even if you write zero signal-handling code, the process dies on SIGTERM.
For PID 1 specifically: Linux provides no default signal handler. SIGTERM arrives, but there’s no default action attached to it (reference). The process must explicitly handle the signal in code, or it simply ignores SIGTERM and keeps running.
And inside a container, your application is PID 1. ⚠️
So the actual step-by-step when your Node.js app doesn’t handle SIGTERM:

PID 1 and Default Signal Handlers
This is why unhandled apps always take the full grace period to die — Kubernetes isn’t slow, your process is just never actually told to stop in a way it understands. 🐌
🐹 Why Go Feels Different
This is where the language matters. Go’s runtime, by default, terminates the process on SIGTERM even when running as PID 1 — you get graceful-looking shutdown behavior with zero signal-handling code. Languages like Node.js and Python don’t install any such default; as PID 1, they will happily ignore SIGTERM forever unless you write the handler yourself.
This isn’t a Kubernetes quirk — it’s standard Linux PID 1 behavior. Any language/runtime is subject to the same rule; Go’s runtime just happens to opt into terminating by default, where Node.js and Python leave that decision to you.
🛠️ Fixing It
✏️ Option 1 — Handle SIGTERM in code Explicitly catch SIGTERM in your application and shut down cleanly (close connections, finish in-flight work, exit).
🧰 Option 2 — Use a real init system as PID 1 Run your process under tini (or dumb-init) instead of directly as PID 1:
ENTRYPOINT ["/sbin/tini", "--", "node", "server.js"]
tini becomes PID 1, forwards SIGTERM to your app, and reaps zombie processes — your app can now be a normal (non-PID-1) process from Linux's point of view. ✅
⚙️ Possible Mitigations at the Kubernetes Level
⏸️ preStop Hook Kubernetes waits for preStop to finish before sending SIGTERM. Useful for coordinating with mesh sidecars or delaying shutdown slightly to let endpoint removal propagate.
⏱️ terminationGracePeriodSeconds The total countdown from the moment the pod starts terminating. After it expires, Kubernetes sends SIGKILL.
DefaultProposedterminationGracePeriodSeconds30s45spreStop hook delay0s15s
💡 Key takeaway from the data
The “pod terminating → request” (Istio-injected) case fails consistently with “no healthy upstream” — regardless of language. The moment Envoy’s sidecar has already pulled the pod from its known endpoints, a new request has nowhere to go, even if the app itself would still respond within its grace period. ❌
Without the sidecar, the same scenario succeeds, because the request goes straight to the app without waiting on the mesh’s endpoint propagation. ✅
This means: language affects whether your app shuts down on SIGTERM at all — the mesh affects whether in-flight/late requests survive the shutdown window. Both need to be solved independently. ⚖️
📌 Summary
- 🎯 Every “why does my pod take 30s to terminate” investigation eventually traces back to PID 1 not having a default SIGTERM handler on Linux.
- 🐹 Go’s runtime terminates on SIGTERM by default, even as PID 1 — this is a runtime choice, not a Kubernetes special case.
- 🧰 Node.js and Python need either explicit SIGTERM handling in code, or an init process like
tinito behave the same way. - 🌊 Even with correct SIGTERM handling, Istio sidecar endpoint propagation timing can independently cause “no healthy upstream” failures for requests that arrive right as termination starts.
메타데이터
- post_id
- 523f7e8f29cf
- slug
- why-does-your-kubernetes-pod-always-take-exactly-30-seconds-to-die-523f7e8f29cf
- url
- https://medium.com/@yagnesh03122002/why-does-your-kubernetes-pod-always-take-exactly-30-seconds-to-die-523f7e8f29cf
- canonical_url
- https://medium.com/@yagnesh03122002/why-does-your-kubernetes-pod-always-take-exactly-30-seconds-to-die-523f7e8f29cf
- author_url
- https://medium.com/@yagnesh03122002
- status
- ok
- fetched_at
- 2026-07-24 04:40:37