Skip to main content

Liveness e Readiness no .NET | CSharp

  • Liveness e Readiness no .NET | CSharp

O que são health checks

Health checks no ASP.NET Core são endpoints HTTP usados para informar o estado da aplicação e, opcionalmente, de suas dependências. Eles normalmente são consumidos por balanceadores, gateways, monitoramento externo e orquestradores de contêineres. O middleware de health checks é nativo do ASP.NET Core, e a recomendação oficial é decidir primeiro quem vai consumir esses checks, porque isso influencia o desenho dos endpoints e dos testes de saúde. (Microsoft Learn)

Na prática, os dois termos mais importantes são estes:

  • Liveness: responde à pergunta “a aplicação ainda está viva?”
  • Readiness: responde à pergunta “a aplicação está pronta para receber tráfego?”

A documentação do .NET Aspire resume isso de forma muito clara: readiness significa “não me envie tráfego ainda” quando falha; liveness significa “reinicie-me” quando falha. (Microsoft Learn)


Diferença entre liveness e readiness

Liveness

Liveness existe para detectar se o processo travou, entrou em deadlock ou ficou em um estado do qual não consegue mais se recuperar sozinho. Quando esse check falha, o comportamento esperado do orquestrador é reiniciar a aplicação. Por isso, liveness deve ser simples, rápido e não deve depender de serviço externo. (Microsoft Learn)

Readiness

Readiness existe para dizer se a instância pode ou não receber requisições naquele momento. Se esse check falha, a aplicação pode continuar rodando normalmente, mas deve sair do balanceamento até voltar a ficar pronta. Esse endpoint pode considerar inicialização incompleta, aquecimento de recursos, dependências externas, modo degradado controlado ou outras condições operacionais. (Microsoft Learn)

Regra prática

  • Liveness falhou → o ambiente tende a reiniciar
  • Readiness falhou → o ambiente tende a parar de enviar tráfego, sem necessariamente reiniciar

Essa separação é especialmente importante em Kubernetes, gateways e balanceadores, porque evita reinícios desnecessários quando o problema é apenas operacional ou temporário. (Microsoft Learn)


Quando usar cada um

Use liveness para

  • detectar travamento do processo
  • detectar estado irrecuperável
  • informar ao orquestrador que a instância precisa ser reiniciada

Use readiness para

  • informar que a aplicação ainda está subindo
  • bloquear tráfego enquanto algum aquecimento interno termina
  • sinalizar indisponibilidade temporária para receber requisições
  • validar dependências, quando fizer sentido

A documentação do ASP.NET Core mostra explicitamente a separação entre endpoints diferentes de health check e o uso de filtros por Predicate e tags para compor esse cenário. (Microsoft Learn)


Erro conceitual mais comum

O erro mais comum é colocar dependências externas no liveness. Exemplo: consultar SQL Server, Redis ou API externa no endpoint de liveness.

Isso é ruim porque, se a dependência externa cair, sua aplicação pode entrar em ciclo de restart, quando na verdade o correto seria apenas sair do balanceamento. Em outras palavras:

  • liveness não deve falhar porque o banco caiu
  • readiness pode falhar porque o banco caiu

Essa distinção decorre exatamente do objetivo operacional de cada probe. (Microsoft Learn)


Como aplicar no .NET

1. Registrar o serviço de health checks

O ponto de partida é registrar o serviço:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks();

Em aplicações ASP.NET Core, o suporte principal de health checks já faz parte da plataforma, e o middleware é exposto com MapHealthChecks. (Microsoft Learn)


2. Criar endpoints separados

A forma mais correta de aplicar liveness e readiness é expor endpoints distintos.

Exemplo básico

using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks()
.AddCheck("self-live", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
.AddCheck("self-ready", () => HealthCheckResult.Healthy(), tags: new[] { "ready" });

var app = builder.Build();

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});

app.Run();

Esse padrão usa tags para separar checks e Predicate para decidir quais checks serão executados em cada endpoint, exatamente no modelo recomendado pela documentação oficial. (Microsoft Learn)


3. Cenário sem dependências externas

Se sua API não depende de banco, Redis, fila ou serviço externo, ainda assim vale manter os dois endpoints separados.

Nesse caso:

  • /health/live valida que o processo está vivo
  • /health/ready valida que a aplicação está apta a receber tráfego

Hoje os dois podem retornar o mesmo resultado, mas o significado operacional continua diferente. Isso evita refatoração futura e mantém o padrão correto desde já. A própria orientação do ecossistema .NET parte dessa distinção semântica entre “estou rodando” e “estou pronto”. (Microsoft Learn)

Exemplo enxuto para esse cenário

using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks()
.AddCheck("live", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
.AddCheck("ready", () => HealthCheckResult.Healthy(), tags: new[] { "ready" });

var app = builder.Build();

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});

app.Run();

4. Cenário com dependências externas

Quando existirem dependências reais, o ideal é deixá-las no readiness.

Exemplo com SQL Server e Redis

using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks()
.AddCheck("self-live", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
.AddSqlServer(
connectionString: builder.Configuration.GetConnectionString("sql")!,
name: "sql",
tags: new[] { "ready" })
.AddRedis(
redisConnectionString: builder.Configuration.GetConnectionString("redis")!,
name: "redis",
tags: new[] { "ready" });

var app = builder.Build();

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});

app.Run();

A documentação mostra exatamente essa ideia de usar um endpoint de readiness filtrando checks relevantes, enquanto o liveness não executa verificações de dependência. (Microsoft Learn)

Observação importante: no exemplo acima, Predicate = _ => false no /health/live é um padrão aceito para dizer “se o processo respondeu ao endpoint, ele está vivo”, sem executar nenhum teste extra. A documentação oficial mostra esse tipo de composição ao separar liveness e readiness. (Microsoft Learn)


5. Customizar a resposta

Por padrão, o endpoint pode retornar algo simples. Quando você quiser JSON estruturado, use ResponseWriter.

Exemplo de resposta JSON customizada

using System.Text.Json;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";

var payload = new
{
status = report.Status.ToString(),
totalDuration = report.TotalDuration,
entries = report.Entries.Select(x => new
{
name = x.Key,
status = x.Value.Status.ToString(),
duration = x.Value.Duration,
description = x.Value.Description,
error = x.Value.Exception?.Message
})
};

await context.Response.WriteAsync(JsonSerializer.Serialize(payload));
}
});

Isso é útil para diagnóstico, dashboards e integração com ferramentas de observabilidade. O ASP.NET Core permite essa customização diretamente via HealthCheckOptions. (Microsoft Learn)


Como pensar isso em produção

Padrão mental correto

Use esta lógica:

  • live = “o processo ainda respira?”
  • ready = “essa instância deve receber requisições agora?”

Se sua aplicação ainda está subindo, aquecendo cache, carregando configuração dinâmica ou aguardando alguma etapa interna, o readiness pode falhar temporariamente enquanto o liveness continua saudável. Essa é exatamente a utilidade prática da separação. (Microsoft Learn)

Impacto em orquestradores

Kubernetes e outros orquestradores usam probes para redirecionar tráfego ou reiniciar contêineres de acordo com o tipo do probe. A documentação de gRPC health checks no ASP.NET Core, por exemplo, menciona explicitamente o uso por container orchestrators e load balancers para liveness e readiness. (Microsoft Learn)


Exemplo de aplicação em Kubernetes

livenessProbe:
httpGet:
path: /health/live
port: 80

readinessProbe:
httpGet:
path: /health/ready
port: 80

Com isso:

  • se /health/live falhar, o contêiner tende a ser reiniciado
  • se /health/ready falhar, a instância tende a sair do balanceamento

Esse comportamento é coerente com a finalidade descrita na documentação .NET e ASP.NET Core. (Microsoft Learn)


Boas práticas

1. Sempre separe os endpoints

Mesmo que hoje ambos retornem saudável, separar os endpoints desde o início evita acoplamento conceitual errado e facilita a evolução do sistema. (Microsoft Learn)

2. Não consulte dependência externa no liveness

Esse é um dos erros mais caros em produção, porque transforma indisponibilidade temporária em restart desnecessário. (Microsoft Learn)

3. Use tags para organizar checks

tags e Predicate são o padrão mais limpo para decidir o que roda em cada endpoint. A documentação oficial mostra essa abordagem como forma de filtrar checks para cenários específicos. (Microsoft Learn)

4. Mantenha o liveness barato

Liveness precisa ser rápido, previsível e estável. Não transforme esse endpoint em mini monitoramento. (Microsoft Learn)

5. Faça o readiness refletir a capacidade real de atendimento

Se a instância não pode receber tráfego com segurança, o readiness deve falhar. Esse é o ponto dele. (Microsoft Learn)


Exemplo final recomendado para sua API atual

Como você disse que hoje não terá dependências externas, este seria um bom ponto de partida:

using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks()
.AddCheck("live", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
.AddCheck("ready", () => HealthCheckResult.Healthy(), tags: new[] { "ready" });

var app = builder.Build();

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";

await context.Response.WriteAsJsonAsync(new
{
status = report.Status.ToString(),
totalDuration = report.TotalDuration,
entries = report.Entries.Select(x => new
{
name = x.Key,
status = x.Value.Status.ToString(),
duration = x.Value.Duration
})
});
}
});

app.Run();

Esse desenho já deixa sua API preparada para crescer sem precisar mudar a semântica dos endpoints depois. Ele está alinhado com o modelo oficial de health checks do ASP.NET Core e com a distinção operacional entre readiness e liveness no ecossistema .NET. (Microsoft Learn)


Resumo prático

  • Liveness verifica se o processo ainda deve existir
  • Readiness verifica se a instância deve receber tráfego
  • Liveness falhou costuma significar reinício
  • Readiness falhou costuma significar retirada do balanceamento
  • Em .NET, a forma correta de separar isso é com MapHealthChecks, tags e Predicate (Microsoft Learn)