Kubernetes Health and Readiness Probes in C

This article provides a practical guide on implementing Kubernetes liveness and readiness probes in C# applications using ASP.NET Core. You will learn how to configure built-in health check middleware, separate basic process health from external dependency availability using tags, and map these endpoints to Kubernetes deployment manifests.

1. Register Health Check Services

Modern ASP.NET Core applications include built-in health check abstractions. To differentiate between liveness (is the app running?) and readiness (is the app able to serve traffic?), register distinct checks using tags in your Program.cs file.

var builder = WebApplication.CreateBuilder(args);

// Register Health Checks with tags
builder.Services.AddHealthChecks()
    .AddCheck("self", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
    .AddCheck<DatabaseHealthCheck>("database", tags: new[] { "ready" });

var app = builder.Build();

2. Implement Custom Readiness Checks

Readiness probes should verify external dependencies like databases, message brokers, or third-party APIs. Implement the IHealthCheck interface to define dependency checks.

using Microsoft.Extensions.Diagnostics.HealthChecks;

public class DatabaseHealthCheck : IHealthCheck
{
    private readonly string _connectionString;

    public DatabaseHealthCheck(IConfiguration configuration)
    {
        _connectionString = configuration.GetConnectionString("DefaultConnection")!;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, 
        CancellationToken cancellationToken = default)
    {
        try
        {
            // Perform a lightweight query or connection ping
            await Task.Delay(10, cancellationToken); 
            
            return HealthCheckResult.Healthy("Database is reachable.");
        }
        catch (Exception ex)
        {
            return HealthCheckResult.Unhealthy("Database unavailable.", ex);
        }
    }
}

3. Expose Health Check Endpoints

Map dedicated endpoints for liveness and readiness by filtering against the assigned tags using HealthCheckOptions.

// Liveness endpoint: responds 200 OK if the process is up
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("live")
});

// Readiness endpoint: responds 200 OK only if all dependencies are healthy
app.MapHealthChecks("/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
});

app.Run();

4. Configure Kubernetes Probes

Once your C# service exposes /healthz and /ready, update your Kubernetes Deployment manifest to poll these endpoints.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: csharp-service
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: api
        image: my-csharp-app:latest
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          failureThreshold: 2

Best Practices

  • Keep Liveness Probes Lightweight: Avoid checking external dependencies in the liveness probe. If a shared database goes down, your liveness probe should not restart the container, as restarting will not fix the external issue.
  • Use Readiness Probes for Traffic Routing: If an external service fails, the readiness probe fails, causing Kubernetes to stop routing ingress traffic to that specific pod until the check passes again.
  • Set Realistic Timeouts: Set timeoutSeconds and periodSeconds to prevent false restarts under transient network load.