はじめに
この記事はスタンバイ Advent Calendar 2025の12日目の記事です。
スタンバイで BFF を中心にバックエンド開発を行っている熊本です!
スタンバイでは全社的にマイクロサービスアーキテクチャで開発を行っており、各サービス間の通信を gRPC サーバーにする対応を進めています。
今回は Rust で gRPC サーバーを立てるために最低限必要な実装から、ユースケースによって必要となる実装の紹介まで説明します。
本記事で紹介するサンプルコードは次にまとめています。
※本記事は2025年5月開催の技術書典で出版したスタンバイ Tech Book vol.1 第10章の再掲になります。
事前準備
まずプロジェクトを作成します。
$ cargo new rust_grpc_server
今回、Rust の gRPC サーバーの実装では tonicというライブラリを使用します。
tonic では async/await をサポートした gRPC サーバーを実装できます。
次のコマンドで tonic 及びその他利用するライブラリを追加します。
$ cargo add tonic
$ cargo add prost
$ cargo add tokio --features full
$ cargo add tower
各ライブラリの役割
- prost: Rust で Protocol Buffers のシリアライズ/デシリアライズを扱う際に利用する
- tokio: Rust で非同期処理を実行する際に利用する
- tower: ネットワーククライアント/サーバーを実装するための抽象的なモジュールで、サーバーにミドルウェアを設定する際に利用する
また後ほど詳しく説明しますが、コードの自動生成をツールとして管理したいので、ツール用のサブプロジェクトも作成しておきます。
$ mkdir tools && cargo init tools/grpc_codegen
コード生成では prost ベースの tonic-buildというライブラリを利用するため、次のコマンドで追加します。
$ cargo add prost
$ cargo add tonic-build
最後に動作確認の際に利用する grpcurl をインストールします。
grpcurl は curl ライクに gRPC サーバーにリクエストを送信できるツールです。
以上で事前準備完了です!
Protocol Buffers
Protocol Buffersは proto ファイルに定義された構造化されたデータをシリアライズ/デシリアライズするため仕組みです。
gRPC サーバーでは Protocol Buffers を利用して通信を行うため、proto ファイルに gRPC サーバーサービスやデータ定義など記述する必要があります。今回はサンプルとしてプロジェクトルート配下に proto/user.proto を次の内容で作成します。
syntax = "proto3";
import "google/protobuf/empty.proto";
service UserService {
rpc Create(CreateRequest) returns (google.protobuf.Empty);
}
message CreateRequest {
string name = 1;
string email = 2;
}
上記では gRPC サーバーサービスとして UserService を定義しています。そのサービスの rpc メソッドとして、リクエストで CreateRequest のデータ型を受け取り、空のレスポンスを返す Create メソッドを用意しています。
コード生成
ツールの作成
ここでは先ほど作成した proto ファイルから gRPC サーバーサービス定義やデータ型に対応する Rust のコードを生成するツールを作成します。
事前に準備しておいたサブプロジェクトの main.rs に次の内容を記載します。
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
println!("Running code generation...");
let proto_path ="../../proto";
tonic_build::configure()
.include_file("mod.rs")
.file_descriptor_set_path("../../src/tonic_build_gen/descriptor.bin")
.build_client(false)
.build_server(true)
.out_dir("../../src/tonic_build_gen")
.compile_protos(
&[
format!("{}/user.proto", proto_path),
],
&[proto_path],
)?;
println!("Code generation completed.");
Ok(())
}
Taskfileの作成と実行
私達のチームでは Taskfile でツール実行のコマンドを管理しています。
その紹介を兼ねて、今回は Taskfile に定義したコマンドから先ほど作成したツールを実行してみます。
まずTaskfile.yamlを次の内容でプロジェクトルート配下に作成します。
version: "3"
tasks:
grpc:
desc: proto ファイルから gRPC サーバーのコードを生成します
cmds:
- cd tools/grpc_codegen && cargo run
次に task コマンドをインストールして、プロジェクトルート配下で task コマンドを実行します。
$ task grpc
task: [grpc] cd tools/grpc_codegen && cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/grpc_codegen`
Running code generation...
cargo:rerun-if-changed=../../proto/user.proto
cargo:rerun-if-changed=../../proto
Code generation completed.
コマンド実行後、次の3つのファイルが生成されます。
- descriptor.bin: proto ファイルに定義されたデータを含むバイナリファイルで、gRPC サーバーのリフレクションを利用時に参照する
- mod.rs: tonic_build_gen をモジュールとして定義して、生成されたコードを外部から参照できるようにするファイル
- user.v1.rs: サービス定義やメッセージ型に対応した Rust のコードで gRPC サーバーを Rust で実装する際に利用する
コード生成結果
最後にコード生成された user.v1.rs は次で説明する gRPC サーバーの実装で利用するため、一部抜粋して紹介します。
user_service_server::UserService
こちらは gRPC サーバーサービスが提供する機能を定義しており、サーバー側ではトレイトに定義してあるメソッドを満たせるように実装します。
#[async_trait]
pub trait UserService: std::marker::Send + std::marker::Sync + 'static {
async fn create(
&self,
request: tonic::Request<super::CreateRequest>,
) -> std::result::Result<tonic::Response<()>, tonic::Status>;
}
UserServiceServer 構造体
こちらは trait UserService を実装した構造体 (T) のインスタンスを保持し、gRPC サーバーとしての機能を提供するための構造体です。
#[derive(Debug)]
pub struct UserServiceServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
またそれ以外にも gRPC サーバーサービスが提供しているメソッドのリクエスト、レスポンスの型など定義してあります。
gRPCサーバーの実装
ここでは最低限 gRPC サーバーを立てるところから、各ユースケースによって必要となる機能の実装について紹介します。
※説明の中ですべてのコードは紹介していないので、詳細を確認したい場合は冒頭に記載しているサンプルコードをご確認ください。
Taskfileの編集
gRPC サーバーの実装に入る前に、gRPC サーバーの起動、停止、フォーマッター実行などの際に利用するコマンドを準備します。
version: "3"
tasks:
grpc:
desc: tools/ を実行して proto ファイルから gRPC サーバーのコードを生成します
cmds:
- cd tools/grpc_codegen && cargo run
ps:
desc: 起動中のプロセスを表示します
cmds:
- ps aux | grep -e "rust-grpc-server" -e "cargo"
start:
desc: |
gRPC サーバーを起動します。
バックグラウンドでcargo watchを起動しているので、ファイルの変更を検知して自動で再ビルドします。
cmds:
- bash -c "cargo watch -x 'run --bin rust-grpc-server' > ./.tmp/rust-grpc-server.log 2>&1 & echo \$! > ./.tmp/rust-grpc-server.pid"
stop:
desc: gRPC サーバーを停止します
cmds:
- kill $(cat ./.tmp/rust-grpc-server.pid)
- rm ./.tmp/rust-grpc-server.pid
- rm ./.tmp/rust-grpc-server.log
up:
desc: gRPC サーバーを起動します
cmds:
- task start
- task ps
down:
desc: gRPC サーバーを停止します
cmds:
- task stop
- task ps
lintfix:
desc: コードを静的解析しルールに準拠していることをチェックします。準拠していない場合は修正します。
cmds:
- cargo fmt --all
- cargo clippy --all-targets --all-features -- -D warnings -D clippy::unwrap_used
lint:
desc: コードを静的解析しルールに準拠していることをチェックします。
cmds:
- cargo fmt --all --check
- cargo clippy --all-targets --all-features -- -D warnings -D clippy::unwrap_used
私達のチームではこのように Taskfile でコマンドを管理して、ローカル環境での開発を行っています。
gRPCサーバーを立てる
まず最低限の機能で gRPC サーバーを立ててみます。
gRPC サーバーが提供するメソッドが呼ばれたとき、先ほどコード生成の際に説明した user.v1.rs に定義されている user_service_server::UserService トレイト のメソッドが呼び出されます。
最初にそのトレイトの実装を行います。
UserService トレイトの実装は、rust-grpc-server/src/services/user_services.rs に次の内容を記載します。
※以下はサンプルのため内部実装は省略しています
use crate::tonic_build_gen::user::v1::{user_service_server::UserService, CreateRequest};
pub struct UserServiceImpl {}
#[tonic::async_trait]
impl UserService for UserServiceImpl {
async fn create(
&self,
request: tonic::Request<CreateRequest>,
) -> std::result::Result<tonic::Response<()>, tonic::Status> {
// Implement the logic for creating a user here
Ok(tonic::Response::new(()))
}
}
次に gRPC サーバーの実装です。
rust-grpc-server/src/main.rs に次の内容を記載します。
use std::net::SocketAddr;
use rust_grpc_server::{
services::user_service::UserServiceImpl,
tonic_build_gen::user::v1::user_service_server::UserServiceServer,
};
use tonic::transport::Server;
#[tokio::main]
async fn main() {
let addr: String = "0.0.0.0:50051".parse().unwrap();
let addr: SocketAddr = addr.parse().expect("failed to parse address");
let user_service = UserServiceServer::new(UserServiceImpl {});
Server::builder()
.add_service(user_service)
.serve(addr)
.await
.expect("failed to start gRPC server");
}
プロジェクトルート配下で次のコマンドを実行し、gRPC サーバーを立ち上げます。
$ task up
事前にインストールしておいた grpcurl で疎通できるか確認してみます。
$ grpcurl -plaintext -d '{"name": "test", "email": "test@test.com"}' localhost:50051 user.v1.UserService.Create
上記コマンド実行後、次のエラーで失敗するかと思います。
Error invoking method "user.v1.UserService.Create": failed to query for service descriptor "user.v1.UserService": server does not support the reflection API
これは gRPC サーバーにリフレクションという機能が設定されていないためエラーが発生しています。
リフレクションについては後ほど詳しく説明します。これを解消するためには grpcurl 実行時に proto ファイルを指定する必要があります。
プロジェクトルート配下で次のコマンドを実行します。
$ grpcurl -proto proto/user.proto -plaintext -d '{"name": "test", "email": "test@test.com"}' localhost:50051 user.v1.UserService.Create
上記のコマンド実行後、正常にレスポンスが返ってきたら OK です!
リフレクションを有効にする
まず初めに先ほど少しお話したリフレクションについて説明します。
リフレクションとは gRPC サーバー自身が提供しているサービス、メソッド、メッセージ型などの情報をクライアントが実行時に問い合わせるための仕組みです。
簡単にいうと、リフレクションを有効にした場合は「このサーバーは何ができるの?」という質問に、サーバー自身が答えることができる状態になります。
通常 gRPC サーバークライアントが gRPC サーバーと通信をするためには、サーバーが公開している proto ファイルが必要です。
クライアントはその proto ファイルを元に生成したコードを使ってサーバーのメソッドを呼び出しデータを送受信します。
最初に実行した grpcurl が失敗したのは、proto ファイルの指定を行っておらず、gRPC サーバーのリフレクションも有効になっていないのが原因です。
ここで gRPC サーバーのリフレクションを有効にしてみます。
まず、tonic で実装された gRPC サーバーのリフレクションの設定を行うため、次のライブラリを追加します。
$ cargo add tonic-reflection
その後、rust-grpc-server/src/main.rs を次のように修正します。
use std::net::SocketAddr;
use rust_grpc_server::{
services::user_service::UserServiceImpl,
tonic_build_gen::user::v1::user_service_server::UserServiceServer,
};
use tonic::transport::Server;
use tonic_health::pb::FILE_DESCRIPTOR_SET;
use tonic_reflection::server::Builder;
#[tokio::main]
async fn main() {
let addr: String = "0.0.0.0:50051".parse().unwrap();
let addr: SocketAddr = addr.parse().expect("failed to parse address");
let reflection = Builder::configure()
.register_encoded_file_descriptor_set(include_bytes!("tonic_build_gen/descriptor.bin"))
.register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET)
.build_v1()
.expect("failed to create reflection service");
let user_service = UserServiceServer::new(UserServiceImpl {});
Server::builder()
.add_service(reflection)
.add_service(user_service)
.serve(addr)
.await
.expect("failed to start gRPC server");
}
上記の状態で、proto ファイルの指定なしに grpcurl コマンドを実行すると正常にレスポンスが返ってくるかと思います。
$ grpcurl -plaintext -d '{"name": "test", "email": "test@test.com"}' localhost:50051 user.v1.UserService.Create
ただリフレクションを有効にした場合、サーバーの内部構造に関する情報が外部に公開されることになります。
そのためセキュリティ上の懸念が生じる可能性があるので、本番環境でのリフレクションの有効は注意が必要です。
リフレクションの次の記述から、リフレクションの有効/無効はユーザビリティとセキュリティとのバランスを取って判断する必要があるということがわかります。
If your gRPC API is accessible to public users, you may not want to expose the reflection service, as you may consider this a security issue. Ultimately, you will need to make a call here that strikes the best balance between security and ease-of-use for you and your users.
そのため gRPC サーバーが汎用的に呼び出されることがない場合は、本番では無効、検証環境では有効の状態にしておく方がよいかと思います。
私達のチームでも、検証環境ではリフレクションを有効にして検証者が proto ファイルの情報を知らなくとも gRPC サーバーとの疎通確認をできる状態にし、本番環境ではリフレクションの有効は行っていません。
ヘルスチェックサービスを追加する
これまでは grpcurl で疎通確認を行っていました。
ただ gRPC サーバーを運用していく中でサーバーの状態を定期的に確認するためヘルスチェックを実行したいというケースがあるかと思います。
ここでは tonic で実装した gRPC サーバーにヘルスチェックサービスを追加する方法を説明します。
まず tonic で実装された gRPC サーバーにヘルスチェックサービスを実装するためのライブラリを追加します。
$ cargo add tonic-health
その後、rust-grpc-server/src/main.rs を次のように修正します。
use std::net::SocketAddr;
use rust_grpc_server::{
services::user_service::{UserServiceImpl},
tonic_build_gen::user::v1::user_service_server::UserServiceServer,
};
use tonic::transport::Server;
use tonic_health::{pb::FILE_DESCRIPTOR_SET, server::health_reporter};
use tonic_reflection::server::Builder;
#[tokio::main]
async fn main() {
let addr: String = "0.0.0.0:50051".parse().unwrap();
let addr: SocketAddr = addr.parse().expect("failed to parse address");
// 本番環境ではリフレクションの登録は行わない
let reflection = Builder::configure()
.register_encoded_file_descriptor_set(include_bytes!("tonic_build_gen/descriptor.bin"))
.register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET)
.build_v1()
.expect("failed to create reflection service");
let (mut health_reporter, health_service) = health_reporter();
health_reporter
.set_serving::<UserServiceServer<UserServiceImpl>>()
.await;
let user_service = UserServiceServer::new(UserServiceImpl {});
Server::builder()
.add_service(health_service)
.add_service(reflection)
.add_service(user_service)
.serve(addr)
.await
.expect("failed to start gRPC server");
}
gRPC サーバーのヘルスチェックは grpc-health-probeで行います。
そのため実行環境にあったバイナリファイルをダウンロードして、そのファイルの実行権限を付与します。
例
$ sudo chmod u+x grpc_health_probe-darwin-arm64
その後、ヘルスチェックの呼び出しを行って、SERVING で返ってくることを確認します。
$ ./grpc_health_probe-darwin-arm64 -addr=localhost:50051
status: SERVING
上記では gRPC サーバー全体の状態のヘルスチェックを行います。
サーバーの立ち上げに失敗している場合は、次のようにタイムアウトなどで失敗します。
./grpc_health_probe-darwin-arm64 -addr=localhost:50051
timeout: failed to connect service "localhost:50051" within 1s
ただこの確認方法では gRPC サーバーに対するヘルスチェックを行っており、特定のサービスが NOT_SERVING の場合でもサーバーが立ち上がっている場合は SERVING が返ってきます。
たとえば次のように UserServiceServer のステータスを NOT_SERVING で登録して、gRPC サーバーと UserService、それぞれのヘルスチェックを行ってみます。
health_reporter
.set_not_serving::<UserServiceServer<UserServiceImpl>>()
.await;
gRPC サーバーのヘルスチェック
$ ./grpc_health_probe-darwin-arm64 -addr=localhost:50051
status: SERVING
UserService のヘルスチェック
$ ./grpc_health_probe-darwin-arm64 -addr=localhost:50051 -service=user.v1.UserService
service unhealthy (responded with "NOT_SERVING")
上記のとおり、特定のサービスが NOT_SERVING の場合でも、gRPC サーバーのヘルスチェックには成功して、特定のサービスのヘルスチェックには失敗します。
ここで特定のサービスのヘルスチェックが失敗している状態の検知をクライアント側に委ねているのはなぜなのかと疑問に思ったので、GRPC Health Checking Protocolを参考にもう少し確認してみます。
以下から gRPC サーバーのヘルスチェックの特徴として、サービスごとのヘルスステータスを確認できることがあり、
"Secondly, it has rich semantics such as per-service health status."
以下から gRPC サーバー側ですべてのサービスの個別のステータスを設定する必要があるということがわかります。
The server should register all the services manually and set the individual status, including an empty service name and its status
上記より、gRPC サーバーのヘルスチェックプロトコルはサーバー全体のヘルスチェックだけでなく、個々のサービスの状態を管理してクライアントがそれを確認できる仕組みを提供していることがわかります。
そのためサーバーが起動しているか、ネットワーク的に到達可能かといった観点で gRPC サーバー全体のヘルスチェックを行うことはもちろんですが、それぞれのサービス実行に何かしら依存するものがある場合、それぞれのサービスのヘルスチェックも行う方がより詳細にシステムの健全性を把握できるのかと思いました。
ミドルウェアを設定する
ここではミドルウェアを設定した gRPC サーバーの実装方法を紹介します。
まず tonic で実装された gRPC サーバーにミドルウェアを設定できるようにするため、次のライブラリを追加します。
$ cargo add tonic-middleware
今回はミドルウェアとしてリクエストのインターセプターを実装してみます。
rust-grpc-server/src/middleware/sample_interceptor.rs に次の実装を記載します。
※以下はサンプルのため内部実装は省略しています
use tonic::codegen::http::Request;
use tonic::{async_trait, body::Body, Status};
use tonic_middleware::RequestInterceptor;
#[derive(Clone)]
pub struct SampleInterceptor {}
#[async_trait]
impl RequestInterceptor for SampleInterceptor {
async fn intercept(&self, request: Request<Body>) -> Result<Request<Body>, Status> {
// Example: Add custom logic here if needed
println!("Intercepted request: {:?}", request);
Ok(request)
}
}
ここでは tonic_middleware に RequestInterceptor トレイトがあるので、そのトレイト実装を行っています。
次に gRPC サーバーにインターセプターを登録します。
rust-grpc-server/src/main.rs を次のように修正します。
use std::net::SocketAddr;
use rust_grpc_server::{
middleware::sample_interceptor::SampleInterceptor, services::user_service::{self, UserServiceImpl},
tonic_build_gen::user::v1::user_service_server::UserServiceServer,
};
use tonic::transport::Server;
use tonic_health::{pb::FILE_DESCRIPTOR_SET, server::health_reporter};
use tonic_middleware::RequestInterceptorLayer;
use tonic_reflection::server::Builder;
use tower::ServiceBuilder;
#[tokio::main]
async fn main() {
let addr: String = "0.0.0.0:50051".parse().unwrap();
let addr: SocketAddr = addr.parse().expect("failed to parse address");
// 本来、本番環境ではリフレクションの登録は行わない
let reflection = Builder::configure()
.register_encoded_file_descriptor_set(include_bytes!("tonic_build_gen/descriptor.bin"))
.register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET)
.build_v1()
.expect("failed to create reflection service");
let (mut health_reporter, health_service) = health_reporter();
health_reporter
.set_serving::<UserServiceServer<UserServiceImpl>>()
.await;
let user_service_with_middleware = ServiceBuilder::new()
.layer(RequestInterceptorLayer::new(SampleInterceptor {}))
.service(UserServiceServer::new(UserServiceImpl {}));
Server::builder()
.add_service(health_service)
.add_service(reflection)
.add_service(user_service_with_middleware)
.serve(addr)
.await
.expect("failed to start gRPC server");
}
上記より、ミドルウェアとしてリクエストインターセプターの実装を実現できました。
トレーシングを設定する
マイクロサービスアーキテクチャにおいて、複数のサービスが連携して動作するシステムでは、リクエストがどのように処理されているかを把握することが重要になります。
ここで役立つのがトレーシングという仕組みです。
トレーシングを導入することで、gRPC サーバーが受信したリクエストからレスポンスを返すまでの処理の流れを可視化し、パフォーマンスの問題やエラーの原因を特定しやすくなります。
tonic で gRPC サーバーにトレーシング機能を組み込むためには、tonic-tracing-opentelemetry1というライブラリを利用するのが一般的です。
OpenTelemetry は、分散トレーシング、メトリクス、ロギングのための標準化されたテレメトリーデータ収集・管理のフレームワークであり、さまざまなバックエンドシステムと連携できます。
まず tonic-tracing-opentelemetry を追加します。
$ cargo add tonic-tracing-opentelemetry
次に gRPC サーバーの起動処理に OtelGrpcLayer を追加します。サーバー起動時の処理を次のように修正します。
Server::builder()
.layer(OtelGrpcLayer::default())
.add_service(health_service)
.add_service(reflection)
.add_service(user_service_with_middleware)
.serve(addr)
.await
.expect("failed to start gRPC server");
上記の変更により、gRPC サーバーはリクエストの受付からレスポンスの送信までの情報をトレースデータとして生成するようになります。
しかし、この時点ではまだトレースデータはどこにも送信されていません。トレースデータを実際に収集・可視化するためには、OpenTelemetry のエクスポーターを設定し、分散トレーシングシステムに送信する必要があります。
たとえば、Datadog にトレースデータを送信する場合は、opentelemetry-datadog クレートなどを利用してエクスポーターを設定し、トレーサーを初期化する必要があります。その設定は通常、アプリケーションの初期化処理などで行います。
次のコマンドで必要なライブラリを追加します。
$ cargo add tracing
$ cargo add tracing-opentelemetry
$ cargo add tracing-subscriber --features json
$ cargo add opentelemetry
$ cargo add opentelemetry-datadog --features reqwest-client
$ cargo add opentelemetry_sdk --features rt-tokio
rust-grpc-server/src/trace.rs にトレーシングシステムへの送信処理を追加します。
use opentelemetry_datadog::{new_pipeline, ApiVersion};
use std::error::Error;
use std::str::FromStr;
use tracing::Level;
use tracing::Subscriber;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Registry;
use tracing_subscriber::{filter, prelude::*};
pub fn datadog_subscriber() -> Result<(), Box<dyn Error>> {
let tracer = new_pipeline()
.with_service_name("{service_name}") // TODO: サービス名設定
.with_api_version(ApiVersion::Version05)
.with_agent_endpoint("{endpoint}") // TODO: エンドポイント設定
.install_batch(opentelemetry_sdk::runtime::Tokio)?;
let telemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer);
let stdout_log_layer = tracing_subscriber::fmt::layer()
.json()
.with_current_span(true)
.with_file(true)
.with_line_number(true)
.with_thread_ids(true)
.with_thread_names(true);
let level = Level::from_str("info")?;
let level_filter = filter::LevelFilter::from_level(level);
// ノイズとなるようなトレースを除外するフィルタ
let common_target_exclusions = filter::filter_fn(|metadata| {
let target = metadata.target();
if target.starts_with("hyper")
{
return false;
}
true
});
let subscriber = Registry::default()
.with(telemetry_layer.with_filter(common_target_exclusions.clone()))
.with(
stdout_log_layer
.with_filter(level_filter)
.with_filter(common_target_exclusions),
);
init_subscriber(subscriber)
}
fn init_subscriber(subscriber: impl Subscriber + Send + Sync + 'static) -> Result<(), Box<dyn Error>> {
tracing::subscriber::set_global_default(subscriber).map_err(|e| e.into())
}
最後にrust-grpc-server/src/main.rsを次のように修正します。
#[tokio::main]
async fn main() {
let addr: String = "0.0.0.0:50051".parse().unwrap();
let addr: SocketAddr = addr.parse().expect("failed to parse address");
// NOTE: 実際に使用する場合は、サービス名やエンドポイントを設定する必要がある
datadog_subscriber().expect("failed to initialize datadog subscriber");
// 本来、本番環境ではリフレクションの登録は行わない
let reflection = Builder::configure()
.register_encoded_file_descriptor_set(include_bytes!("tonic_build_gen/descriptor.bin"))
.register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET)
.build_v1()
.expect("failed to create reflection service");
let (mut health_reporter, health_service) = health_reporter();
health_reporter
.set_serving::<UserServiceServer<UserServiceImpl>>()
.await;
let user_service_with_middleware = ServiceBuilder::new()
.layer(RequestInterceptorLayer::new(SampleInterceptor {}))
.service(UserServiceServer::new(UserServiceImpl {}));
Server::builder()
.add_service(health_service)
.add_service(reflection)
.add_service(user_service_with_middleware)
.serve(addr)
.await
.expect("failed to start gRPC server");
}
上記より、gRPC サーバーのリクエスト受付からレスポンス送信までのトレースデータを確認できます。
まとめ
今回は Rust を使った gRPC サーバー実装で、最低限の機能で gRPC サーバーを立てるところからそれぞれのユースケースにあった gRPC サーバーの実装方法について紹介しました。
もし Rust で gRPC サーバーを実装する機会があれば、参考にしていただければと思います。
最後までお読みいただきありがとうございました!
明日の記事は@k-rfさんです。お楽しみに!