Readiness vs Liveness Probes — Complete Implementation Guide
In this tutorial, you will learn about Readiness vs Liveness Probes. We cover key concepts, practical examples, and best practices to help you master this topic.
Readiness probes determine if a service can accept traffic, while liveness probes determine if a service needs restarting. Understanding the distinction is critical for reliable Kubernetes deployments.
What You'll Learn
By the end of this tutorial, you will know when to use readiness vs liveness probes, how to configure each, and how to avoid common anti-patterns that cause cascading failures.
Why It Matters
Confusing readiness and liveness is one of the most common Kubernetes configuration errors. A misconfigured probe can cause unnecessary restarts, prevent traffic from reaching healthy instances, or fail to detect actual failures.
Real-World Use
DodaTech's platform team mandates: readiness probes check dependencies (database, cache), liveness probes check only the Process health. This separation prevents dependency failures from triggering restarts.
Readiness vs Liveness Learning Path
flowchart LR
A[Health Check Intro] --> B[Readiness vs Liveness]
B --> C[Readiness Probes]
B --> D[Liveness Probes]
B --> E[Startup Probes]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Readiness Probes in Detail
Readiness probes control whether a pod receives traffic from Kubernetes services.
class ReadinessProbe {
constructor() {
this.ready = false;
this.dependencies = new Map();
}
addDependency(name, check) {
this.dependencies.set(name, check);
}
async check() {
const results = [];
for (const [name, check] of this.dependencies) {
try {
const healthy = await check();
results.push({ name, healthy });
if (!healthy) {
console.log(`Dependency ${name} unhealthy, marking not ready`);
}
} catch (err) {
results.push({ name, healthy: false, error: err.message });
console.log(`Dependency ${name} check failed: ${err.message}`);
}
}
this.ready = results.every(r => r.healthy);
return {
ready: this.ready,
dependencies: results,
timestamp: new Date().toISOString()
};
}
isReady() {
return this.ready;
}
}
const readiness = new ReadinessProbe();
readiness.addDependency("database", async () => true);
readiness.addDependency("redis", async () => true);
readiness.check().then(r => console.log("Ready:", r.ready));
// Ready: true
Liveness Probes in Detail
Liveness probes determine if the process is healthy and should continue running.
class LivenessProbe {
constructor() {
this.healthy = true;
this.lastActivity = Date.now();
this.goroutineCount = 0;
}
recordActivity() {
this.lastActivity = Date.now();
}
async check() {
const now = Date.now();
const stuckDuration = now - this.lastActivity;
if (stuckDuration > 60000) {
console.log(`No activity for ${stuckDuration}ms, marking unhealthy`);
this.healthy = false;
}
if (this.goroutineCount > 1000) {
console.log("Too many goroutines, marking unhealthy");
this.healthy = false;
}
return {
healthy: this.healthy,
lastActivityMs: stuckDuration,
timestamp: now
};
}
isAlive() {
return this.healthy;
}
}
const liveness = new LivenessProbe();
console.log("Alive:", liveness.isAlive());
liveness.lastActivity = Date.now() - 120000;
liveness.check().then(r => console.log("Alive after 2min idle:", r.healthy));
// Alive: true
// Alive after 2min idle: false
Startup Probes
Startup probes delay readiness and liveness checks until the service has finished initializing.
class StartupProbe {
constructor() {
this.started = false;
this.initializationTasks = [];
}
addTask(name, task) {
this.initializationTasks.push({ name, task });
}
async initialize() {
console.log(`Starting ${this.initializationTasks.length} initialization tasks`);
for (const { name, task } of this.initializationTasks) {
console.log(` Initializing: ${name}`);
await task();
console.log(` Initialized: ${name}`);
}
this.started = true;
console.log("Startup complete, probes will now pass");
}
async check() {
return {
started: this.started,
completedTasks: this.initializationTasks.length,
timestamp: new Date().toISOString()
};
}
}
const startup = new StartupProbe();
startup.addTask("connect-db", () => new Promise(r => setTimeout(r, 1000)));
startup.addTask("load-cache", () => new Promise(r => setTimeout(r, 500)));
startup.initialize();
// Starting 2 initialization tasks
// Initializing: connect-db
// Initialized: connect-db
// Initializing: load-cache
// Initialized: load-cache
// Startup complete, probes will now pass
Common Anti-Patterns
The most common Kubernetes probe configuration mistakes.
class ProbeAntiPatterns {
static list() {
return [
{
pattern: "Using readiness for liveness",
problem: "Readiness failures remove the pod from service but don't restart it. A genuinely stuck pod stays stuck.",
solution: "Use liveness to detect and restart stuck processes. Use readiness to handle dependency failures."
},
{
pattern: "Checking dependencies in liveness",
problem: "If the database is slow, all pods restart simultaneously, making the problem worse.",
solution: "Only check process-level health in liveness. Check dependencies in readiness."
},
{
pattern: "Using the same endpoint for both probes",
problem: "You lose the ability to differentiate between 'don't send traffic' and 'restart me'.",
solution: "Use separate endpoints: /readyz for readiness, /healthz for liveness."
},
{
pattern: "Probe timeout too short",
problem: "A brief spike in latency causes false failures, triggering unnecessary restarts.",
solution: "Set timeout to at least 5 seconds, failureThreshold to at least 3."
}
];
}
}
ProbeAntiPatterns.list().forEach(p => {
console.log(`Anti-pattern: ${p.pattern}`);
console.log(` Problem: ${p.problem}`);
console.log(` Solution: ${p.solution}\n`);
});
Kubernetes Probe Configuration
Proper configuration values for production deployments.
class ProbeConfiguration {
static recommended(type) {
const configs = {
readiness: {
initialDelaySeconds: 5,
periodSeconds: 10,
timeoutSeconds: 3,
failureThreshold: 3,
successThreshold: 1
},
liveness: {
initialDelaySeconds: 30,
periodSeconds: 15,
timeoutSeconds: 5,
failureThreshold: 3,
successThreshold: 1
},
startup: {
initialDelaySeconds: 0,
periodSeconds: 5,
timeoutSeconds: 5,
failureThreshold: 30,
successThreshold: 1
}
};
return configs[type] || configs.readiness;
}
static generateYaml(type, path, port) {
const config = this.recommended(type);
return {
[type + "Probe"]: {
httpGet: { path, port },
...config
}
};
}
}
console.log("Readiness config:", ProbeConfiguration.recommended("readiness"));
console.log("Liveness config:", ProbeConfiguration.recommended("liveness"));
Practice Questions
What should a readiness probe check? Dependency health (database, cache, downstream services). The service is ready to receive traffic only when its dependencies are available.
What should a liveness probe check? Process health (is the event loop responding, are goroutines healthy). The process is alive and making progress.
Why should startup probes have a high failure threshold? Startup can take 2-3 minutes. A high threshold (e.g., 30 failures at 5-second intervals = 150 seconds) gives the service time to initialize.
Challenge: Implement a server that differentiates between readiness, liveness, and startup probe responses.
const http = require("http");
function createThreeProbeServer() {
let startupComplete = false;
let readyForTraffic = false;
let processAlive = true;
setTimeout(() => { startupComplete = true; }, 3000);
setTimeout(() => { readyForTraffic = true; }, 8000);
return http.createServer((req, res) => {
let status = 503;
let body = { status: "not ok" };
if (req.url === "/startupz") {
status = startupComplete ? 200 : 503;
body = { status: startupComplete ? "started" : "starting" };
} else if (req.url === "/readyz") {
status = readyForTraffic ? 200 : 503;
body = { status: readyForTraffic ? "ready" : "not ready" };
} else if (req.url === "/healthz") {
status = processAlive ? 200 : 503;
body = { status: processAlive ? "alive" : "dead" };
}
res.writeHead(status, { "Content-Type": "application/json" });
res.end(JSON.stringify(body));
});
}
createThreeProbeServer().listen(8080);
FAQ
Mini Project
Build a probe demonstration server that goes through startup, becomes ready, experiences a dependency failure, and recovers, showing how each probe type responds.
const http = require("http");
function buildProbeDemo() {
const state = { startup: false, ready: false, alive: true, dbHealthy: true };
setTimeout(() => { state.startup = true; console.log("Startup complete"); }, 5000);
setTimeout(() => { state.ready = true; console.log("Ready for traffic"); }, 10000);
setTimeout(() => { state.dbHealthy = false; state.ready = false; console.log("DB failure"); }, 20000);
setTimeout(() => { state.dbHealthy = true; state.ready = true; console.log("DB recovered"); }, 30000);
const server = http.createServer((req, res) => {
const response = { timestamp: new Date().toISOString() };
if (req.url === "/startupz") {
response.status = state.startup ? "started" : "starting";
res.writeHead(state.startup ? 200 : 503);
} else if (req.url === "/readyz") {
response.status = state.ready ? "ready" : "not ready";
response.dbHealthy = state.dbHealthy;
res.writeHead(state.ready ? 200 : 503);
} else if (req.url === "/healthz") {
response.status = state.alive ? "alive" : "dead";
res.writeHead(state.alive ? 200 : 503);
}
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(response));
});
return server;
}
buildProbeDemo().listen(8080);
console.log("Probe demo starting...");
What's Next
Now that you understand probe types, learn about different types of health checks. Then implement a simple health check endpoint.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro