kubernetes上で動くgRPCサーバーのヘルスチェック: Health checking of gRPC server on kubernetes

TL;DR

  • KubernetesのLiveness & Readiness Probeを使って、Pod内のコンテナ、プロセスのヘルスチェックが行える
  • grpc-health-probeで簡単に実装できる

gRPCについて

gRPCはGoogleによって開発されたRPCシステムです。Protocol Buffersをインターフェースの定義に使用しています。gRPCについては以下の公式サイトで詳細に記述されています。 2017年からCNCFにホストされており、パフォーマンスの面や異なる言語間でもprotoファイルによってインターフェースを定義できるといった特定から、マイクロサービスにおいてよく使用されています。

gRPCサーバーのヘルスチェックについて

gRPCを使ったサーバーを開発し、Kubernetes上で稼働させる際にヘルスチェックを導入しました。しかし、その調査をしていた際に、gRPCサーバーのヘルスチェックの手法に関して日本語での記事が少なかったので今回記事にしました。 Kubernetes上でのgRPCサーバのヘルスチェックに関しては公式サイトにて提案がされています。公式サイトにもあるように2018/10時点ではKubernetesはgRPCサーバのhealth checkingをサポートしていないため、開発者が用意する必要があります。

If you’re unfamiliar, Kubernetes health checks (liveness and readiness probes) is what’s keeping your applications available while you’re sleeping. They detect unresponsive pods, mark them unhealthy, and cause these pods to be restarted or rescheduled. Kubernetes does not support gRPC health checks natively. This leaves the gRPC developers with the following three approaches when they deploy to Kubernetes:

公式サイトでは4つの手法が提案されていますが、本記事では、その中でもおすすめされているgrpc-health-probeを使ってヘルスチェックを行います。

f:id:tomiokasyogo:20190427192957p:plain 引用サイト: Health checking gRPC servers on Kubernetes - Kubernetes

図にあるようにcontainer内にstandard health checking protocolを利用してgRPCのサーバのヘルスチェックを行うprobeを導入することでヘルスチェックを行います。ヘルスチェックによってserverのstatusをチェックし、SERVINGが返ってくればOK、そうでなければUnhealthy として判断します。

デモ

今回はGo言語を使用してサーバを実装します。protoファイルは以下のようになっており、名前を送信するとメッセージが返ってくるだけの至極シンプルなサービスです。クライアントとやりとりを行うGatewayサービスと実際のメッセージを生成するBackendサービスを用意しました。

追記:2019/4/28 ソースコードこちらに置いておきます

syntax = "proto3";

package proto;

service GreetingServer {
    rpc Greeting(GreetingRequest) returns (GreetingResponse) {}
}

message GreetingRequest {
  string name = 1;    
}

message GreetingResponse {
    string message = 1;
}

全体像としては以下のようになります。 f:id:tomiokasyogo:20190427194951p:plain

このサービスを提供するサーバを実装していきます。記事とは関係がないので内容は省略します。 また、ヘルスチェック用としてstandard health checking protocolを満たすサーバを実装する必要があります。実際のprotoとしては公式サイトにもあるように以下のprotoで定義されています。

syntax = "proto3";

package grpc.health.v1;

message HealthCheckRequest {
  string service = 1;
}

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

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

実際のサービスの場合は、protocなどでprotoファイルを元にコードを生成し、interfeceを満たすようにサーバの中身を実装しますが、golangではgoogle.golang.org/grpc/health/grpc_health_v1standard health checking protocolをカバーしたものがあるので今回はこれを利用します。自分でprotoからファイルを生成しても問題はないと思います。 実装したヘルスチェック用のサーバは以下のようになります。

package server

import (
    "golang.org/x/net/context"

    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    health "google.golang.org/grpc/health/grpc_health_v1" <- ここ
    "google.golang.org/grpc/status"
)

func RegisterHeathCheck(s *grpc.Server){
    health.RegisterHealthServer(s, &healthServer{})
}

type healthServer struct{}

func(h *healthServer)Check(context.Context, *health.HealthCheckRequest) (*health.HealthCheckResponse, error){
    return &health.HealthCheckResponse{
        Status:health.HealthCheckResponse_SERVING,  <- "SERVING"を返す
    },nil
}

func (h *healthServer) Watch(*health.HealthCheckRequest, health.Health_WatchServer) error {
    return status.Error(codes.Unimplemented, "service watch is not implemented current version.")
}

standard health checking protocolにはなかったWatchメソッドがありますが、これはもう少し発展的なヘルスチェック用に使うことができます。今回は特に使わないのでUNIMPLEMENTEDで返しておきます。他にもgoogle.golang.org/grpc/codesの中で様々なstatusが定義されており使用することができます。

一旦これでヘルスチェックを通すことができるようになりました。一旦このアプリケーションをapplyしてgrpc-health-probeコマンドを使用して手動でヘルスチェックをしてみます。すると以下のようにSERVING status が返ってくることが確認できました。

$ > grpc-health-probe -addr=localhost:XXX
status: SERVING

Liveness & Readiness Probe

Kubernetesではクラスタ内のPodの正常判断を行うための機構がLiveness ProbeReadiness Probeです。この二種類のヘルスチェック機構はそれぞれ役割と失敗した際の挙動が異なります。

Linevess Probe

Podが正常に動作しているのかどうかをチェックするために使用する。失敗した際にはPodを再起動する

Readiness Probe

Podがサービスを提供できる状態にあるのかどうかをチェックする。失敗した際には該当のPodに対してトラフィックを流さないがPodは再起動しない。

本記事ではこれらの機構を使用してヘルスチェックを行います。手順に関してはgrpc-health-probeのREADME.mdに記述があります。まずはgrpc_health-probeを使用できるようにDockerfileに以下を追加します。

RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \
    wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \
    chmod +x /bin/grpc_health_probe

そして、以下のようにKubernetesのPodマニフェストReadiness ProbeLiveness Probeに関する記述を追加します。

  containers:
  - name: server
    image: "[YOUR-DOCKER-IMAGE]"
    ports:
    - containerPort: 5000
    readinessProbe:
      exec:
        command: ["/bin/grpc_health_probe", "-addr=:5000"]
      initialDelaySeconds: 5
    livenessProbe:
      exec:
        command: ["/bin/grpc_health_probe", "-addr=:5000"]
      initialDelaySeconds: 10

ヘルスチェックの機構を追加したマニフェストをapplyします。 そうすると、最初はPodのStatusがRunnningにもかかわらず、READYが0になっていることが確認できます。 Probeに関するデータを探すとLiveness ProbeReadiness Probeが設定されていることが確認できます。

NAME                       READY   STATUS    RESTARTS   AGE
gateway-54d7cd4b8c-9rtfd   0/1     Running   0          5s

これは Readiness Probeのヘルスチェックが一度も成功していないからです。少し待つとSTATUS1/1になってPodに対してトラフィックを流せる状態になります。

NAME                       READY   STATUS    RESTARTS   AGE
gateway-54d7cd4b8c-9rtfd   1/1     Running   0          18s

podの設定を確認すると正しくprobeが設定されていることがわかります。細かいオプションや条件に関しては公式サイトに詳しく掲載されています。

$> kd pod | grep probe  
    Liveness:   exec [/bin/grpc_health_probe -addr=:50001] delay=10s timeout=1s period=10s #success=1 #failure=3
    Readiness:  exec [/bin/grpc_health_probe -addr=:50001] delay=5s timeout=1s period=10s #success=1 #failure=3

Readiness probe を失敗させてみる

せっかくですのでわざと、ヘルスチェックが失敗するようにしてみます。Readiness probeがヘルスチェックを行なっているポート番号を適当に変更して失敗するようにしてapplyしてみます。 そうすると下にあるようにPodは起動するが、一分ほど待ってもREADYが0のままです。Readiness probeの紹介の通りの挙動をしていることが確認できます。

NAME                       READY   STATUS    RESTARTS   AGE
gateway-6867dfdcdf-tl7pc   0/1     Running   0          1m

Liveness probe を失敗させてみる

次にLiveness probeが失敗するようにしてみます。そうすると何回もRESTARTSが行われ、CrashLoopBackOffになっていることが確認できます。これもLiveness probeによって正しくヘルスチェックが行えています。

NAME                       READY   STATUS             RESTARTS   AGE
gateway-bc555785b-8vg9l    0/1     CrashLoopBackOff   6          7m

Conculusion

思ってたより簡単にヘルスチェックができました。 ただし、このやり方だとアプリケーション側のコードにも変更が必要だったり、マイクロサービスが大きくなった時に管理が大変だったりしそうなので、IstioやLinkerdなどのコントロールプレーンでヘルスチェックもコントロールできるようになる(もうなってる?)と思うのであくまで現状での解決方に過ぎないと思います。 質問、意見、修正などあれば気軽にコメント、連絡お願いします。

Twitter ID: @tomiokasyogo

参考資料、サイト

https://grpc.io/docs/guides/concepts/ https://www.cncf.io/blog/2017/03/01/cloud-native-computing-foundation-host-grpc-google/ https://grpc.io/docs/guides/concepts/ https://cloud.google.com/blog/products/gcp/kubernetes-best-practices-setting-up-health-checks-with-readiness-and-liveness-probes https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ https://github.com/grpc-ecosystem/grpc-health-probe

gRPC - What is gRPC?の和訳 from Qiita