Question

Performing a health check in .NET Core Worker Service

How can I implement health checks in a .NET Core Worker Service?

The service will be run inside Docker and needs to be able to check the health of the service.

 48  28234  48
1 Jan 1970

Solution

 15

I don't think is worth it to change SDK to Microsoft.NET.Sdk.Web. You will include additional middlewares just because of one health check? No thanks ...

What you could do is to use a different protocol like TCP.

The general idea is:

  1. Create a separate background service that creates a TCP server (take a look at TcpListener.cs)
  2. When you receive a request you have two options: if the application is healthy accept TCP connection otherwise reject it.
  3. If you use containers your orchestrator should have an option to call it over TCP (in k8s there is a property tcpSocket)

If you need more detailed information you may check: Monitoring Health of ASP.NET Core Background Services With TCP Probes on Kubernetes

Cheers!

2020-12-21

Solution

 14

Another way of doing this is to implement IHealthCheckPublisher.

The benefits of this approach is the ability to re-use your existing IHealthChecks or integration with 3rd party libraries that rely on IHealthCheck interface (like this one).

Though you still target Microsoft.NET.Sdk.Web as the SDK you don't need to add any asp.net specifics.

Here is an example:

public static IHostBuilder CreateHostBuilder(string[] args)
{
  return Host
    .CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
      services
        .AddHealthChecks()
        .AddCheck<RedisHealthCheck>("redis_health_check")
        .AddCheck<RfaHealthCheck>("rfa_health_check");

      services.AddSingleton<IHealthCheckPublisher, HealthCheckPublisher>();
      services.Configure<HealthCheckPublisherOptions>(options =>
      {
        options.Delay = TimeSpan.FromSeconds(5);
        options.Period = TimeSpan.FromSeconds(5);
      });
    });
}

public class HealthCheckPublisher : IHealthCheckPublisher
{
  private readonly string _fileName;
  private HealthStatus _prevStatus = HealthStatus.Unhealthy;

  public HealthCheckPublisher()
  {
    _fileName = Environment.GetEnvironmentVariable(EnvVariableNames.DOCKER_HEALTHCHECK_FILEPATH) ??
                Path.GetTempFileName();
  }

  public Task PublishAsync(HealthReport report, CancellationToken cancellationToken)
  {
    // AWS will check if the file exists inside of the container with the command
    // test -f $DOCKER_HEALTH_CHECK_FILEPATH

    var fileExists = _prevStatus == HealthStatus.Healthy;

    if (report.Status == HealthStatus.Healthy)
    {
      if (!fileExists)
      {
        using var _ = File.Create(_fileName);
      }
    }
    else if (fileExists)
    {
      File.Delete(_fileName);
    }

    _prevStatus = report.Status;

    return Task.CompletedTask;
  }
}
2020-03-17