Summary
- Use case
- Understanding Kubernetes Probes
- Implementing Graceful Shutdown
- Important Considerations
- Conclusion
Initially, I would like to provide a relevant scenario where it is relevant to have graceful shutdown implemented
Imagine the scenario...
You have a Node.js application that processes operations synchronously. It's running in a Kubernetes cluster, behind an Ingress.
Today is deployment day, just a simple update, nothing major.
You trigger the rolling update in Kubernetes.
Kubernetes selects a Pod and sends it a SIGTERM
, signaling it to begin shutting down...
However... your application doesn’t handle SIGTERM
.
It continues processing requests like nothing happened.
Meanwhile, Kubernetes still sends new traffic to this Pod — because the readinessProbe
is still passing.
So now you have a situation where:
- The Pod is shutting down.
- New requests are still coming in.
- Ongoing requests keep processing, without any special handling.
After 30 seconds, Kubernetes steps in:
"Shutting down now."
And it sends a SIGKILL
, forcefully terminating the Pod.
The result?
- Some in-progress transactions are cut off mid-operation.
- New client requests are dropped without a response.
- Errors, timeouts, or even data loss can occur.
Now, imagine the same scenario, but with a graceful shutdown in place.
Your application:
- Listens for the
SIGTERM
. - Stops accepting new requests by failing the readiness probe.
- Finishes processing current requests cleanly.
- Optionally performs cleanup tasks before exiting.
The result?
No dropped traffic. No broken operations. No panic.
In this post, we’ll explore how to implement graceful shutdown in your Node.js app using Kubernetes probes to manage health and traffic flow, ensuring safe and zero-downtime deployments.
Understanding Kubernetes Probes
Kubernetes provides probes and grace periods that we can use effectively.
A probe is a diagnostic mechanism used by Kubernetes to determine the health of a container. Below you can see the difference between each one.
Probe | Purpose | Behavior |
---|---|---|
readinessProbe | Is the app ready to receive traffic? | If it fails, Kubernetes removes the Pod from the Service. |
livenessProbe | Is the app alive? | If it fails, Kubernetes restarts the Pod. |
startupProbe | Is the app still starting? | Delays liveness/readiness checks until startup finishes. |
Key Point: Readiness failures do not kill the Pod. Liveness failures restart the Pod.
Now, let's implementing Graceful Shutdown in Node.js
1. Handle SIGTERM in your application
let isShuttingDown = false;
process.on('SIGTERM', async () => {
console.log('SIGTERM received. Starting graceful shutdown...');
isShuttingDown = true;
// Wait for in-flight requests to complete
await new Promise((resolve) => setTimeout(resolve, 30000));
console.log('Graceful shutdown completed. Exiting process.');
process.exit(0);
});
2. Create separate health endpoints
const express = require('express');
const app = express();
app.get('/health/ready', (req, res) => {
if (isShuttingDown) {
return res.status(500).send('Shutting down');
}
res.status(200).send('Ready');
});
app.get('/health/live', (req, res) => {
res.status(200).send('Alive');
});
app.listen(3000, () => console.log('App listening on port 3000'));
/health/ready
checks if the application is ready to serve traffic./health/live
checks if the application is alive.
During shutdown, only readiness fails. Liveness remains healthy!
Kubernetes Configuration Example
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-node-app
spec:
replicas: 3
selector:
matchLabels:
app: my-node-app
template:
metadata:
labels:
app: my-node-app
spec:
terminationGracePeriodSeconds: 30
containers:
- name: my-node-app
image: eopires/node-graceful-shutdown:1.0.0
env:
- name: VERSION
value: "V1"
- name: GRACEFUL_TIMEOUT
value: "30"
ports:
- containerPort: 3000
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
Timeline during a Rolling Update:
- Kubernetes sends
SIGTERM
to the Pod. - Node.js application sets
isShuttingDown = true
. - readinessProbe fails. Kubernetes removes Pod from Service.
- Pod has 30 seconds (
terminationGracePeriodSeconds
) to finish requests. - If still running after grace period, Kubernetes sends
SIGKILL
.
Important Considerations
- Separate logic for readiness and liveness probes to avoid Pod restarts during shutdown.
- Adjust terminationGracePeriodSeconds to allow enough time to process in-flight requests.
- Configure startupProbe if your app needs extra time to initialize.
Example of startupProbe:
startupProbe:
httpGet:
path: /health/ready
port: 3000
failureThreshold: 6
periodSeconds: 10
Kubernetes will wait up to 60s before considering the app failed during startup.
Conclusion
Implementing graceful shutdown properly ensures that:
- No client request is interrupted.
- Rolling updates happen smoothly.
- Your application behaves reliably even during failures or upgrades.
By combining Node.js signal handling and Kubernetes probes, you can deliver true production-grade resilience!