gRPC 서비스에 health check 구현하기

문제 상황

사내 결제 시스템을 gRPC로 전환하면서 Kubernetes에서 서비스 상태를 체크할 방법이 필요했다. HTTP 서버와 달리 gRPC는 별도의 health check 엔드포인트가 없어서 pod이 정상인지 판단할 수 없었다.

gRPC Health Checking Protocol

gRPC에는 표준 health check 프로토콜이 정의되어 있다. grpc.health.v1.Health 서비스를 구현하면 된다.

syntax = "proto3";

package grpc.health.v1;

service Health {
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

message HealthCheckRequest {
  string service = 1;
}

message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;
    SERVING = 1;
    NOT_SERVING = 2;
  }
  ServingStatus status = 1;
}

Node.js 구현

grpc-health-check 패키지를 사용했다.

const grpc = require('@grpc/grpc-js');
const health = require('grpc-health-check');

const server = new grpc.Server();
const healthImpl = new health.Implementation({
  '': health.servingStatus.SERVING,
  'payment.PaymentService': health.servingStatus.SERVING,
});

server.addService(health.service, healthImpl);

// DB 연결 실패 시 상태 변경
db.on('error', () => {
  healthImpl.setStatus('payment.PaymentService', 
    health.servingStatus.NOT_SERVING);
});

Kubernetes 설정

grpc-health-probe를 사용해 liveness/readiness probe를 구성했다.

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: payment-service
    image: payment:latest
    livenessProbe:
      exec:
        command: [
          "/bin/grpc-health-probe",
          "-addr=:50051"
        ]
      initialDelaySeconds: 10
    readinessProbe:
      exec:
        command: [
          "/bin/grpc-health-probe",
          "-addr=:50051",
          "-service=payment.PaymentService"
        ]

결과

배포 시 DB 마이그레이션이 완료될 때까지 pod이 NOT_SERVING 상태로 대기하도록 구현했다. 트래픽이 준비되지 않은 pod으로 가는 문제가 해결되었고, 무중단 배포가 안정적으로 동작하게 되었다.