grpc / grpc-dotnet

gRPC for .NET
Apache License 2.0
4.17k stars 769 forks source link

gRPC HealthChecks returning '''Unknown''' Rather than Serving #2249

Open ExtremeSwat opened 1 year ago

ExtremeSwat commented 1 year ago

What version of gRPC and what language are you using?

2.55.0 / 2.56.0-pre2 --> C#

What operating system (Linux, Windows,...) and version?

Windows 11

What runtime / compiler are you using (e.g. .NET Core SDK version dotnet --info)

net7.0 (7.0.307)

What did you do?

I was able to replicate this issue with the default grpc template from VS. Basically dialing into the grpc.health.v1.services --> Check/Watch will return Unkwnown as a status.

This is my Program.cs

using GrpcService1.HealthCheck;
using GrpcService1.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();
builder.Services.AddGrpcHealthChecks(o =>
{
    o.Services.Map("", r => r.Tags.Contains("public"));
}).AddCheck<PingHealthCheck>("ping");

var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapGrpcService<GreeterService>();
app.MapGrpcHealthChecksService();

app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");

app.Run();

And this is my healthCheck:

 public class PingHealthCheck : IHealthCheck
    {
        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context,
            CancellationToken cancellationToken = new())
        {

            using var channel = GrpcChannel.ForAddress("http://localhost:5127/", new GrpcChannelOptions
            {

            });

            var client = new Health.HealthClient(channel);
            var response = await client.CheckAsync(new HealthCheckRequest());
            var status = response.Status;

            return status == HealthCheckResponse.Types.ServingStatus.Serving
                ? HealthCheckResult.Healthy()
                : HealthCheckResult.Unhealthy();
        }
    }

I've been following this doc: https://learn.microsoft.com/en-us/aspnet/core/grpc/health-checks?view=aspnetcore-7.0

Health results determine what the gRPC service reports:

1. Unknown is reported when there are no health results.
2. NotServing is reported when there are any health results of [HealthStatus.Unhealthy](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.diagnostics.healthchecks.healthstatus#microsoft-extensions-diagnostics-healthchecks-healthstatus-unhealthy).
3. Otherwise, Serving is reported.

This is a bit weird, because I don't get why there aren't any health results, do I need to kickstart anything? Like if I comment out the MapGrpcHealthChecksService the service will rightfully fail, telling me that it's not implemented.

I have attached my sample.

What did you expect to see?

{
    "status": "SERVING"
}

What did you see instead?

{
    "status": "UNKNOWN"
}

GrpcService1.zip

JamesNK commented 1 year ago

I'm guessing you get that error because of this line: o.Services.Map("", r => r.Tags.Contains("public"));

You're mapping to health results that have a tag of public, but the only health check you're adding with AddCheck<PingHealthCheck>("ping") doesn't have a tag.

I think it would work if configured like this:

.AddCheck<PingHealthCheck>("ping", tags: new[] { "public" });
ExtremeSwat commented 1 year ago

Thanks @JamesNK, missed that when I wrote it, added it now, thanks :)

Unfortunately, it's still not working for me. This is how I register my health checks

   services
       .AddGrpcHealthChecks(o =>
       {
           o.Services.Map("", r => r.Tags.Contains("public"));
       })
       .AddCheck<PingHealthCheck>($"{serviceDescriptor.Name}-ping-health-check", tags: new[] { "grpc", "ping" })

If I add a new tag for my ping health check (add public) then my code will not even finish running, it will act weirdly.

  public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new())
 {
     try
     {
         //host.docker.internal
         //http://localhost:10042/
         using var channel = GrpcChannel.ForAddress("http://localhost:10042/", new GrpcChannelOptions
         {
             HttpClient = _httpClient
         });

         var client = new Grpc.Health.V1.Health.HealthClient(channel);
         var response = await client.CheckAsync(new HealthCheckRequest());
         var status = response.Status;

         return status is HealthCheckResponse.Types.ServingStatus.Serving or HealthCheckResponse.Types.ServingStatus.Unknown ? HealthCheckResult.Healthy() : HealthCheckResult.Unhealthy();
     }
     catch (Exception)
     {
         return HealthCheckResult.Unhealthy();
     }

it will act as if have a timeout and return nothing, which is kinda weird, if I attempt to change the url in any way, it will fail accordingly. not sure what I'm missing.