grpc-node icon indicating copy to clipboard operation
grpc-node copied to clipboard

Add a health check executable

Open HarlemSquirrel opened this issue 9 months ago • 8 comments

Is your feature request related to a problem? Please describe.

When deploying a Node.js gRPC server in a container, there is no straight-forward way to preform a health check when using grpc-health-check.

Describe the solution you'd like

It would be great to have an executable that we can run like:

npx grpc-health-check 0.0.0.0:50051

Describe alternatives you've considered

In order to perform a health check within the container, there seems to be a few options currently:

  • pull in an additional binary such as https://github.com/grpc-ecosystem/grpc-health-probe
  • install grpcurl
  • build a custom client like https://gist.github.com/HarlemSquirrel/c635b7f78a411cf68b49614af7256117

HarlemSquirrel avatar Mar 04 '25 20:03 HarlemSquirrel

I believe the binary and building from proto are the ways that you're intended to do healthchecks in the gRPC ecosystem. How would this feature work? Are you imagining that it'd just bundle the health probe and add a script to package.json?

tstirrat15 avatar Mar 25 '25 17:03 tstirrat15

I'm new to gRPC but it seems strange there's no easier way to perform a health check within a Docker container.

The readme doesn't really explain well how to write a health check client.

HarlemSquirrel avatar Mar 28 '25 12:03 HarlemSquirrel

Yeah, the entry-level documentation on gRPC definitely leaves something to be desired.

What problem are you trying to solve?

The way that we do our k8s healthchecks with our service is that we have a service defined using the common healthcheck proto, and then we use the grpc-health-probe binary you've mentioned above within the same container as the healthcheck entrypoint on the liveness check definition on the k8s manifest.

k8s also has built-in gRPC liveness probes. We don't use them but I can't remember why off the top of my head.

tstirrat15 avatar Mar 28 '25 14:03 tstirrat15

I'm not using kubernetes so I want a way to check the health of my container running a gRPC server ideally without pulling in a binary that's not in the OS repository

HarlemSquirrel avatar Mar 28 '25 15:03 HarlemSquirrel

As a workaround while this feature is not present, here are the steps I would take to write a health check client:

  1. Start with the hello world client example.
  2. Modify that example so that instead of loading a proto file and using hello_world.Greeter, create the Client class by passing the service object exported by grpc-health-check to grpc.makeClientConstructor, and use that to construct the client.
  3. Call the check method instead of sayHello. The signature of the method can be found in health.proto
  4. Run the client with the server address as an argument.

murgatroid99 avatar Mar 28 '25 16:03 murgatroid99

OK thanks, I was able to revise my script based on your steps.

It is significantly slower than the binary. That may be TypeScript's fault or just the reality of interpreted JS vs compiled binary.

root@container:/code# time yarn check-health
Starting health check on 0.0.0.0:50051
{ status: 'SERVING' }

real    0m1.704s
user    0m2.815s
sys     0m0.285s

root@container:/code# time ./vendor/bin/grpc_health_probe-linux-amd64 -addr=:50051 -tls=false
status: SERVING

real    0m0.005s
user    0m0.002s
sys     0m0.001s

HarlemSquirrel avatar Mar 28 '25 17:03 HarlemSquirrel

There are reasons to expect the compiled library to be faster: compiled code is faster in general, the Go gRPC library is more optimized than the Node gRPC library, and the grpc_health_probe probably has compiled proto file statically linked in, while the Node code loads the proto file from the file system and parses it at runtime. However, that doesn't account for the multiple seconds you see. I modified your code slightly to just be JavaScript, and when I ran it I got this:

$ time node ./health-client.js 
Starting health check on 0.0.0.0:50051
{ status: 'SERVING' }

real	0m0.190s
user	0m0.171s
sys	0m0.040s

Is it possible that you are including TypeScript compilation time in your measurement?

murgatroid99 avatar Mar 28 '25 18:03 murgatroid99

Yeah that's definitely it, thanks.

HarlemSquirrel avatar Mar 28 '25 20:03 HarlemSquirrel