0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

rust で grpc サーバの healthcheck を試す

Posted at

概要

自分メモ用

rust で grpc サーバを実装する
実装するサーバは以下のような機能を持つ

  • カスタム定義のサービス
    • healthcheck の挙動確認が主のためこの部分の実装はかなり適当
  • Healthcheck サービス

このような grpc サーバを実装し、k8s 上にデプロイして grpc の readinessProbe が成功する様子を確認する

以下のレポジトリでサンプルを保存

grpc-healthcheck (https://github.com/kronos1209/grpc-healthcheck)

前提

以下の環境で確認
それぞれのツールの導入については省略

  • wsl (Ubuntu 22.04)
  • docker
  • minikube
    • k8s クラスタ作成用
    • kind などでもよいがダッシュボードの追加を容易にできるため minikube がおすすめ
  • kubectl

実装

proto 定義

以下のようなサービスを定義してコンパイルする

syntax = "proto3";
package my_service;

message MyRequest {}
message MyResponse {}

service MyService {
    rpc Get(MyRequest) returns(MyResponse);
}

proto のコンパイル用の build.rs

build.rs
use std::path::PathBuf;

fn main() {
    // 指定したディレクトリを再帰的に検索し、proto ファイルを探す
    let proto_dir = vec!["proto"];
    let protos: Vec<_> = proto_dir.iter().fold(vec![], |mut acc, base| {
        acc.append(&mut find_protos(base));
        acc
    });

    tonic_build::configure()
        .build_server(true)
        .build_client(true)
        .compile_protos(&protos, &vec![""])
        .unwrap();
}

fn find_protos(base: &str) -> Vec<PathBuf> {
    let mut protos = Vec::new();
    for entry in walkdir::WalkDir::new(base).into_iter() {
        let entry = entry.unwrap();

        // Directory はスキップ
        if entry.path().is_dir() {
            continue;
        }

        // 拡張子が proto であるならば返り値に追加する
        let path = entry.into_path();
        if path.extension().unwrap() == "proto" {
            protos.push(path)
        }
    }
    protos
}

コンパイルした proto をライブラリに追加

my_service.rs
tonic::include_proto!("my_service");

grpc サーバ実装

前段でコンパイルしたコードを利用して grpc サーバを実装する。

healthcheck サービスは tonic-health クレートが実装を提供してくれているため、そちらを利用する

main.rs
#[tokio::main]
async fn main() {
    // ヘルスチェックのステータスマップに MyService を追加する
    // この時点では MyService は NotServing として登録する
    let (mut reporter, health_server) = tonic_health::server::health_reporter();
    reporter
        .set_not_serving::<MyServiceServer<MyService>>()
        .await;

    let my_service = MyService {};
    let my_server =
        proto_definition::proto::my_service::my_service_server::MyServiceServer::new(my_service);

    // 30秒後にサービスが利用可能になっているようにする
    let mut clone_reporter = reporter.clone();
    tokio::spawn(async move {
        sleep(Duration::from_secs(30)).await;
        clone_reporter
            .set_serving::<MyServiceServer<MyService>>()
            .await;
    });

    // サーバーを起動する
    let addr = "0.0.0.0:8080".parse().unwrap();
    tonic::transport::Server::builder()
        .add_service(my_server)
        .add_service(health_server)
        .serve(addr)
        .await
        .unwrap();
}

k8s リソース

イメージ化

まずは実装した grpc サーバをイメージ化する

FROM rust as build

WORKDIR /app

# ビルドに必要な protoc をインストールする
RUN apt update \
    && apt upgrade -y \
    && apt install -y protobuf-compiler libprotobuf-dev

# ビルド
COPY . .
RUN cargo build --release --package grpc-server --bin server

#############################################################################
# scrach イメージしたかったが、うまく実行できなかったため ubuntu イメージを利用
# おそらくリンクとかの問題?
FROM ubuntu as runtime
WORKDIR /server

RUN apt update \
    && apt upgrade -y 

COPY --from=build /app/target/release/server /server

ENTRYPOINT [ "/server/server" ]

イメージを利用した pod マニフェスト

pod マニフェストを作成する

readinessProbespec.containers[].readinessProbe で定義できる

ここで重要なのは readinessProbe.grpc.service で grpc サーバで実装されているサービスのうち、特定のサービスが利用可能になっているかどうかを確認することができる

HealthService はハッシュマップで各サービスが準備完了状態であるかどうか管理しており、今回は MyService だけ30秒間立たないと準備完了状態にならないため、
service を指定していない場合は一回目の readinessProbe で成功し、
servicemy_service.MyServie に指定すると一定期間 readinessProbe は失敗する

また、ここで指定するサービス名というのは tonic_build で proto をコンパイルした際に {サービス名}Server という構造体が自動で生成されており、この構造体に実装されている NamedService トレイトの SERVICE_NAME がそれにあたる

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    name: myapp
spec:
  containers:
  - name: myapp
    image: test:v1
    imagePullPolicy: IfNotPresent
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
    ports:
    - containerPort: 8080
    livenessProbe:
      grpc:
        port: 8080
        service: my_service.MyService
      initialDelaySeconds: 60
    # MyService grpc サービスが準備完了状態になっているかどうか healtchcheck を行う
    readinessProbe:
      grpc:
        port: 8080
        # NamedService のサービス名の指定する
        service: my_service.MyService
      initialDelaySeconds: 10
      periodSeconds: 10

挙動確認

挙動を確認するためにリソースをデプロイしていく。

デプロイ

  1. サンプルイメージの作成

    docker build . -f ./grpc-server/dockerfile  -t test:v1
    
  2. k8s クラスタの用意
    minkube dashboard を実行したときに、ダッシュボードにアクセスするための url が表示されるためのそれを使ってダッシュボードにアクセスする

    # クラスタ作成
    minikube start
    
    #クラスタ内にイメージを取り込む
    minikube image load test:v1
    
    # ダッシュボードの起動
    minikube dashboard
    
  3. k8s リソース追加

    kubectl apply -f ./mamifest/pod.yaml
    

確認

k8s のダッシュボードからポッドのページを開き、myapp のポッドのステータスを確認する

正常に動作していれば、以下のような ReadinessProbe 失敗のログが3回発生したのち、ポッドのステータスが準備完了状態に移行しているはず

Readiness probe failed: service unhealthy (responded with "NOT_SERVING")

まとめ

rust で grpc-healtch-check を実装するには tonic-health クレートを使えばよさそう。
ただし、crate.io の概要を見ると grpc-health-check の仕様をすべて実装しているわけではないようなので要調査

k8s のポッドの grpc の readinessProbe は k8s にビルドインで実装されている。
以前見たときは別途 grpc-health-probe をイメージに含めないといけなかったが k8a 内で完結できそうなので良かった

参考文献

tonic-health
Configure Liveness, Readiness and Startup Probes

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?