ASP.NET HealthCheck

Health checks são o recurso do ASP.NET Core que expõe, em um endpoint configurável, o estado atual da aplicação e das suas dependências. Neste artigo, você vai implementá-los em uma Web API com .NET 5, verificar um SQL Server, montar um dashboard visual e criar a sua própria verificação.

O que são health checks

Os health checks nada mais são do que um middleware que fornece um endpoint configurável. Ao receber uma requisição, ele responde com o estado de saúde da aplicação.

O recurso está disponível desde a versão 2.2 do ASP.NET Core. Além disso, o pacote Microsoft.AspNetCore.Diagnostics.HealthChecks já vem referenciado implicitamente em aplicações ASP.NET Core.

Segundo a documentação oficial de health checks, orquestradores de contêineres e balanceadores de carga usam esses endpoints como sondas. Por exemplo, um orquestrador pode reiniciar um contêiner que falha na verificação.

Por que monitorar aplicações distribuídas

Trabalhar com um parque de aplicações distribuídas é desafiador e exige atenção, principalmente no monitoramento. Antigamente, era comum criar um endpoint que apenas devolvia o status 200 para indicar que a aplicação estava no ar.

Com o avanço dos microsserviços, essa tarefa se tornou indispensável. Afinal, uma API pode responder normalmente enquanto o banco de dados que ela usa está fora do ar.

Por isso, uma boa verificação de saúde olha além do próprio processo. Ela também testa bancos, filas e serviços externos dos quais a aplicação depende.

Criando o projeto no .NET 5

Neste exemplo, usamos uma Web API simples com o .NET 5. Para criar o projeto, escolha a pasta de destino e execute o comando abaixo.

dotnet new webapi --framework net5.0

Em seguida, abra o projeto no seu editor ou IDE favorito. O template já vem com o Startup.cs, que concentra a configuração de serviços e do pipeline.

Registrando os health checks no Startup

No Startup.cs, adicionamos o AddHealthChecks() sobre a interface IServiceCollection, dentro do método ConfigureServices. Dessa forma, os serviços de health checks ficam disponíveis no container.

public void ConfigureServices(IServiceCollection services)
{
    // Registra os serviços de health checks
    services.AddHealthChecks();

    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "HealthCheck", Version = "v1" });
    });
}

Agora configuramos o middleware e a rota da verificação. Para isso, chamamos UseHealthChecks("/health") no método Configure, que recebe um IApplicationBuilder.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseSwagger();
        app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "HealthCheck v1"));
    }

    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

    // Ativa o middleware e define o caminho da verificação
    app.UseHealthChecks("/health");
}

O caminho do endpoint é livre e pode seguir o padrão do seu time. Neste exemplo, usamos /health, mas a documentação da Microsoft costuma usar /healthz.

Testando o endpoint /health

Agora executamos a aplicação com o comando abaixo.

dotnet run

Com a aplicação rodando, enviamos uma requisição pelo Postman para https://localhost:5001/health. O resultado aparece na imagem a seguir.

Requisição no Postman ao endpoint de health checks retornando status 200 e o texto Healthy
Primeira chamada ao endpoint /health com a aplicação saudável.

Repare que a resposta traz o status 200 e a mensagem Healthy. Ou seja, a aplicação conseguiu responder no endpoint e, portanto, está saudável.

Status possíveis de um health check

Cada health check devolve um valor da enumeração HealthStatus. Existem três estados possíveis: Healthy, Degraded e Unhealthy.

Por padrão, o middleware converte esses estados em códigos HTTP. Healthy e Degraded retornam 200, enquanto Unhealthy retorna 503 Service Unavailable.

Quando há várias verificações registradas, o pior resultado define o estado geral. Assim, basta uma dependência falhar para o endpoint inteiro reportar o problema.

Se precisar mudar esse mapeamento, a classe HealthCheckOptions oferece a propriedade ResultStatusCodes. Com ela, você define qual código HTTP cada estado deve retornar.

Verificando a integridade do SQL Server

Além da saúde da própria aplicação, podemos estender a verificação para outros serviços. Neste exemplo, vamos checar uma instância do SQL Server.

Para isso, instalamos o pacote AspNetCore.HealthChecks.SqlServer com o comando abaixo.

dotnet add package AspNetCore.HealthChecks.SqlServer

Esse pacote faz parte do projeto open source AspNetCore.Diagnostics.HealthChecks, mantido pela comunidade Xabaril. Vale lembrar que a Microsoft não mantém nem oferece suporte a essa biblioteca.

Além do SQL Server, o projeto oferece pacotes para diversos bancos e serviços. Há opções para PostgreSQL, MySQL, MongoDB, Redis e RabbitMQ, entre outros.

Depois da instalação, adicionamos o método AddSqlServer logo após a chamada do AddHealthChecks().

public void ConfigureServices(IServiceCollection services)
{
    // Configura a verificação do SQL Server
    services.AddHealthChecks()
        .AddSqlServer(
            connectionString: Mapping.SqlConnectionString.GetConnectionString(),
            name: "SQL Server instance");

    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "HealthCheck", Version = "v1" });
    });
}

Neste exemplo, a classe estática Mapping.SqlConnectionString devolve a string de conexão abaixo. Em produção, no entanto, prefira ler esse valor da configuração da aplicação.

Server=.\SQLExpress;Database=master;Integrated Security=True

Por trás dos panos, a verificação executa um SELECT 1 contra o banco. Se a consulta funcionar, o SQL Server está saudável.

Ao rodar a aplicação, o resultado continua igual ao da imagem anterior. Porém, se desativarmos a instância do SQL Server para simular uma indisponibilidade, a resposta muda.

Requisição no Postman retornando status 503 e o texto Unhealthy com o SQL Server desligado
Com o banco fora do ar, o endpoint passa a sinalizar falha.

Dessa vez, o retorno traz a mensagem Unhealthy e o status 503. Assim, qualquer ferramenta de monitoramento consegue detectar a falha automaticamente.

Dashboard com HealthChecks UI

Outra funcionalidade interessante dos health checks é a interface gráfica. Com ela, você visualiza eventos e históricos em uma espécie de dashboard.

Instalando os pacotes

Para isso, instalamos três pacotes na aplicação: AspNetCore.HealthChecks.UI, AspNetCore.HealthChecks.UI.Client e AspNetCore.HealthChecks.UI.InMemory.Storage.

Você pode instalá-los pelo gerenciador do NuGet ou pela linha de comando.

dotnet add package AspNetCore.HealthChecks.UI
dotnet add package AspNetCore.HealthChecks.UI.Client
dotnet add package AspNetCore.HealthChecks.UI.InMemory.Storage

Configurando os serviços da interface

Com os pacotes instalados, registramos os serviços AddHealthChecksUI() e AddInMemoryStorage() no Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddCors();

    services.AddHealthChecks()
        .AddSqlServer(
            connectionString: Mapping.SqlConnectionString.GetConnectionString(),
            name: "SQL Server instance");

    // Configura a interface gráfica e o armazenamento do histórico
    services.AddHealthChecksUI(options =>
    {
        options.SetEvaluationTimeInSeconds(5);
        options.MaximumHistoryEntriesPerEndpoint(10);
        options.AddHealthCheckEndpoint("API with health checks", "/health");
    })
    .AddInMemoryStorage(); // Adiciona o banco em memória

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "HealthCheck", Version = "v1" });
    });
}

Vale destacar as opções que passamos na configuração:

  • SetEvaluationTimeInSeconds define o intervalo entre as verificações.
  • MaximumHistoryEntriesPerEndpoint define o número máximo de registros no histórico.
  • AddHealthCheckEndpoint define qual endpoint de health checks o dashboard vai consultar.

Por fim, o método AddInMemoryStorage configura um banco em memória para guardar o histórico das verificações. Em ambientes reais, o projeto também oferece armazenamento em bancos como SQL Server e PostgreSQL.

Habilitando o dashboard no pipeline

No método Configure, primeiro passamos um HealthCheckOptions para o UseHealthChecks. Essa classe permite configurar os delegates Predicate e ResponseWriter.

Depois, chamamos o UseHealthChecksUI, que de fato habilita a interface gráfica.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseSwagger();
        app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "HealthCheck v1"));
    }

    app.UseCors(x => x.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

    app.UseHealthChecks("/health", new HealthCheckOptions
    {
        Predicate = p => true,
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });

    app.UseHealthChecksUI(options => { options.UIPath = "/dashboard"; });
}

O Predicate igual a true executa todas as verificações registradas. Enquanto isso, o UIResponseWriter.WriteHealthCheckUIResponse gera a resposta em JSON detalhado, no formato que o dashboard entende.

Pronto, agora basta rodar a aplicação e acessar https://localhost:5001/dashboard.

Dashboard padrão do HealthChecks UI listando os health checks da API e o histórico de verificações
Dashboard padrão exibido no caminho /dashboard.

Personalizando o visual do dashboard

Os recursos não param por aí. Também podemos personalizar o dashboard com um arquivo CSS e deixá-lo com a cara da nossa aplicação.

Para isso, criamos um arquivo de estilo na raiz do projeto, chamado aqui de balta-style.css. O conteúdo redefine as variáveis de cor e o logo da interface.

:root {
  --bgMain: #1f1f1f; /* cor de fundo */
  --fcBase: #fff; /* cor da fonte */
  --bcMenuActive: #fffb00; /* cor da borda do item de menu ativo */
  --primaryColor: #8625d2; /* cor primária */
  --secondaryColor: #9343d1; /* cor secundária */
  --bgMenuActive: #7a0bcf6e; /* cor de fundo do item de menu ativo */
  --bgButton: #63199b; /* cor de fundo dos botões */
  --logoImageUrl: url('https://raw.githubusercontent.com/diegostan/healthchecks-aspnet5/main/images/baltaLogo.png'); /* URL do logo */
  --bgAside: var(--primaryColor); /* cor de fundo da barra lateral */
  --bgTable: #2f2f2f; /* cor de fundo da tabela */
  --bgTableSecondary: #63199b; /* cor secundária da tabela */
}

A lista completa de variáveis está na página de estilos e branding do HealthChecks UI. Neste exemplo, usamos as cores e o logo do balta.io.

Agora adicionamos a opção AddCustomStylesheet, passando o caminho do CSS. O UseHealthChecksUI fica assim.

app.UseHealthChecksUI(options =>
{
    options.UIPath = "/dashboard";
    options.AddCustomStylesheet("balta-style.css");
});

Ao rodar a aplicação novamente, o dashboard já aparece com o novo visual.

Dashboard de health checks personalizado com fundo escuro, tons de roxo e o logo do balta.io
O mesmo painel após aplicar o CSS customizado.

Criando seu próprio health check com IHealthCheck

Existe uma vasta lista de provedores prontos para bancos e serviços. Mesmo assim, podemos criar verificações próprias e ganhar mais flexibilidade com os health checks.

Para isso, basta implementar a interface IHealthCheck. Vamos criar a classe CustomHealthChecks.cs como exemplo.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace HealthCheck
{
    public class CustomHealthChecks : IHealthCheck
    {
        public Task<HealthCheckResult> CheckHealthAsync(
            HealthCheckContext context,
            CancellationToken cancellationToken = default)
        {
            return Task.FromResult(new HealthCheckResult(
                status: HealthStatus.Unhealthy,
                description: "API is unhealthy"));
        }
    }
}

Vamos aos pontos importantes do código:

  • A interface IHealthCheck define o método CheckHealthAsync.
  • Esse método precisa retornar um objeto HealthCheckResult.
  • O HealthCheckResult informa o estado do serviço verificado.

A propriedade status do HealthCheckResult indica a saúde do serviço. Como este é apenas um exemplo, fixamos o valor em Unhealthy, ou seja, o serviço aparece como doente.

Em um cenário real, a lógica dentro do CheckHealthAsync testaria algo concreto. Por exemplo, ela poderia verificar o espaço em disco, a resposta de uma API externa ou o tamanho de uma fila.

Agora registramos a verificação no AddHealthChecks() com o método AddCheck.

services.AddHealthChecks()
    .AddSqlServer(
        connectionString: Mapping.SqlConnectionString.GetConnectionString(),
        name: "SQL Server instance")
    .AddCheck<CustomHealthChecks>("Custom health check"); // Registra a verificação customizada

Ao rodar a aplicação, a nova verificação aparece no dashboard com o estado definido.

Dashboard mostrando a verificação customizada com estado Unhealthy ao lado da checagem do SQL Server
A verificação criada com IHealthCheck já aparece no painel.

Filtrando health checks com tags

À medida que as verificações crescem, nem sempre faz sentido executar todas no mesmo endpoint. Por isso, o método AddCheck aceita um nome, um estado de falha e uma lista de tags.

services.AddHealthChecks()
    .AddCheck<CustomHealthChecks>(
        "Custom health check",
        failureStatus: HealthStatus.Degraded,
        tags: new[] { "custom" });

Nesse exemplo, uma falha da verificação customizada passa a reportar Degraded em vez de Unhealthy. Assim, o endpoint continua devolvendo 200, mas o problema fica registrado.

Em seguida, usamos o Predicate do HealthCheckOptions para escolher quais verificações rodam em cada caminho.

app.UseHealthChecks("/health/custom", new HealthCheckOptions
{
    // Executa apenas as verificações marcadas com a tag "custom"
    Predicate = check => check.Tags.Contains("custom")
});

Dessa forma, você pode ter um endpoint leve para sondas frequentes e outro completo para diagnósticos. Na prática, essa separação reduz a carga sobre bancos e serviços externos.

O HealthCheckOptions também tem a propriedade AllowCachingResponses. Por padrão, ela vale false, e o middleware adiciona cabeçalhos que impedem o cache da resposta.

Health checks com Entity Framework Core

Se a aplicação usa o Entity Framework Core, existe uma alternativa oficial ao pacote da comunidade. Basta instalar o Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.

dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore

Depois, registramos a verificação com o método AddDbContextCheck, informando o DbContext da aplicação.

services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(Mapping.SqlConnectionString.GetConnectionString()));

services.AddHealthChecks()
    .AddDbContextCheck<AppDbContext>();

Por padrão, essa verificação chama o método CanConnectAsync do EF Core. Ou seja, ela confirma se a aplicação consegue abrir uma conexão com o banco configurado no contexto.

Caso precise de um teste mais específico, as sobrecargas do AddDbContextCheck permitem trocar a operação executada. Por exemplo, você pode consultar uma tabela essencial em vez de apenas abrir a conexão.

Consumindo o JSON dos health checks

Até aqui, usamos os health checks para verificar serviços e montamos um dashboard amigável. Além disso, também podemos consumir diretamente o JSON publicado no endpoint.

Com esse JSON, é possível criar painéis unificados que mostram o estado de várias aplicações ao mesmo tempo. Ao acessar o endpoint /health, configurado no início do artigo, obtemos o resultado abaixo.

Resposta JSON do endpoint de health checks com o status geral, a duração e o resultado de cada verificação
O JSON detalhado gerado pelo UIResponseWriter.

Dessa forma, você pode integrar o endpoint a qualquer ferramenta de monitoramento ou automação. Na prática, o JSON traz o estado geral, a duração e o resultado de cada verificação.

Se preferir enviar os resultados em vez de esperar consultas, o ASP.NET Core oferece a interface IHealthCheckPublisher. Quando você a registra no container, o sistema executa as verificações periodicamente e chama o método PublishAsync com o relatório.

Com isso, fica simples encaminhar o estado das verificações para uma ferramenta de observabilidade. As opções Delay e Period controlam quando a primeira execução acontece e o intervalo entre as seguintes.

UseHealthChecks ou MapHealthChecks

Neste artigo, usamos o UseHealthChecks, que registra o middleware diretamente no pipeline. Porém, o ASP.NET Core também oferece o MapHealthChecks, que registra os health checks como um endpoint de roteamento.

Segundo a Microsoft, o UseHealthChecks encerra o pipeline assim que a requisição corresponde ao caminho. Isso evita trabalho desnecessário, como logs e outros middlewares.

Por outro lado, o MapHealthChecks permite usar middlewares que conhecem o endpoint, como a autorização. Assim, você pode proteger o caminho da verificação com uma política específica.

Para entender melhor como o pipeline funciona, vale ler o artigo sobre middleware no ASP.NET. Ele explica a ordem de execução e como criar o seu próprio componente.

Liveness e readiness

Em ambientes como o Kubernetes, é comum separar duas perguntas diferentes. A sonda de liveness responde se a aplicação travou e precisa reiniciar.

Já a sonda de readiness responde se a aplicação está pronta para receber tráfego. Por exemplo, uma API pode estar viva, mas ainda aguardando a conexão com o banco.

Para separar as duas, você registra as verificações com tags e filtra cada endpoint pelo Predicate. Dessa forma, o orquestrador decide com precisão quando reiniciar e quando apenas segurar o tráfego.

Fora do Kubernetes, o próprio Docker oferece a instrução HEALTHCHECK. Com ela, o contêiner chama periodicamente o endpoint de health checks e marca a si mesmo como saudável ou não.

HEALTHCHECK CMD curl --fail http://localhost:5000/health || exit 1

Vale notar que as imagens Linux do .NET não trazem o curl por padrão. Portanto, instale o pacote no Dockerfile ou use o wget em imagens baseadas em Alpine.

Conclusão sobre health checks

Neste artigo, vimos o que são os health checks e como configurar essa ferramenta tão útil em uma aplicação ASP.NET 5. Também verificamos o SQL Server, montamos um dashboard, personalizamos o visual e criamos uma verificação própria.

Com poucos ajustes, sua API deixa de responder apenas se está no ar. Em vez disso, ela passa a informar com clareza a saúde de cada dependência.

O código-fonte completo deste exemplo está disponível no repositório ExemploHealthChecks no GitHub.

FAQ

Qual a diferença entre Degraded e Unhealthy?

Degraded indica que o serviço funciona com limitações e, por padrão, retorna 200. Já Unhealthy indica falha e retorna 503.

Preciso instalar algum pacote para usar health checks?

Não para o uso básico, porque o pacote principal já vem referenciado no ASP.NET Core. Pacotes extras só são necessários para verificar bancos, filas ou exibir o dashboard.

O pacote AspNetCore.HealthChecks.SqlServer é da Microsoft?

Não. Ele faz parte do projeto open source da comunidade Xabaril, que a Microsoft não mantém.

Posso proteger o endpoint de health checks com autenticação?

Sim. Com o MapHealthChecks, você aplica uma política de autorização ao endpoint, como em qualquer outra rota.

O histórico do dashboard sobrevive a um reinício?

Não com o AddInMemoryStorage, porque os dados ficam apenas na memória. Para manter o histórico, use um provedor de armazenamento persistente.

Glossário

Termo Significado
Health check Verificação que informa se a aplicação ou uma dependência está saudável.
Middleware Componente do pipeline do ASP.NET Core que processa requisições e respostas.
HealthStatus Enumeração com os estados Healthy, Degraded e Unhealthy.
IHealthCheck Interface usada para criar verificações de saúde personalizadas.
HealthChecks UI Interface gráfica do projeto Xabaril que exibe o estado e o histórico das verificações.
Liveness Sonda que indica se a aplicação travou e precisa reiniciar.
Readiness Sonda que indica se a aplicação está pronta para receber tráfego.
Predicate Delegate que filtra quais verificações rodam em um endpoint.

Fontes