Rust× Envoy を活用したマイクロサービス実装入門
概要
Rust で gRPC サーバーを構築し、Envoy を API ゲートウェイとして組み合わせたマイクロサービスの基盤を作成しました。本記事では、その設計・実装・課題・今後の展望について詳しく解説します。
また、本記事では図解もふんだんに載せておりますので、流し見でも大枠はつかめるかと思います。
はじめに
-
なぜ Rust なのか?
Rust言語はgRPCを用いたマイクロサービスアーキテクチャを構築する場合に以下のような利点があります。
🔸高速性とメモリ安全性
C++に勝るとも劣らない実行スピード、そしてメモリリークの起きにくい
🔸強力な型システムによるバグ防止
多くの不具合がビルド時に解決できます
🔸gRPC との相性の良さ(`tonic` の活用)
tonicクレートを使うことでGoやPythonのように明示的にコードを生成することなく、バックグラウンドでコードが生成されます。
-
なぜ gRPC なのか?
🔸高速・スキーマ駆動の通信
低レイテンシーで通信スピードが速い特徴があります。また、protoBufによるスキーマを定義し、PoC(Proof of Concept:概念実証)の一環としてスキーマの設計・検証を行い、その後スキーマ駆動開発に移行できる
🔸REST API よりも効率的なデータ通信が可能
高速性とやや被ってしまいますが、バイナリ通信が可能なので効率的なデータ通信が可能です。
後述しますが、ストリーミングも可能です。ただし、本記事ではストリーミングは使いません。 -
なぜ Envoy なのか?
🔸 gRPC-Web のサポート
gRPC は高効率な通信を実現できますが、ブラウザ環境ではそのまま使用できません。
Envoy は gRPC-Web プロキシ の役割を果たし、クライアントとサーバー間の通信を可能にします。
🔸 負荷分散や認証などの機能が強力
Envoy は L7 プロキシとして動作し、トラフィックのルーティングやロードバランシングを細かく制御できます。
また、認証やトレーシング機能も組み込まれており、スケーラブルなマイクロサービスの基盤 を構築しやすいです。
🔸 Istio との統合も視野に
Envoy は Istio のデフォルトのデータプレーンとして設計されており、サービスメッシュ構成 との親和性が高いです。
Kubernetes 上で動作するマイクロサービスを セキュアかつ柔軟に管理 できるようになります。
想定する読者層
- Rust でのバックエンド開発に興味がある人
- gRPC を実戦で活用したい人
- Envoy の設定に苦戦している人
- マイクロサービスの設計を考えている人
この記事を読んで分かること
- Rust × gRPC × Envoy の組み合わせを学べる
- Docker を使った実践的な環境構築の方法
- Envoy の詳細な設定
本編
1. アーキテクチャ設計
今回はシンプルにコンテナは一つだけ、かつディレクトリ構成も必要最低限になっています。
本来であればクリーンアーキテクチャやCQRSパターンなども考慮したいですが、本記事ではまずはマイクロサービスアーキテクチャを実装することをモチベーションにしています。
-
構成図
1.Rust製gRPC サーバー: サーバの実体
2.Envoy: リバースプロキシとして配置
3.Docker: 環境を統一
-
ディレクトリ構成
以下は、rust-microservice-with-envoy
リポジトリのディレクトリ構成です:
rust-microservice-with-envoy/
├── envoy/
│ └── envoy.yaml
├── proto/
│ └── helloworld.proto
├── server/
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
├── docker-compose.yml
└── README.md
各ディレクトリ/ファイルの詳細
-
envoy/
: Envoy プロキシの設定ファイルを格納するディレクトリ。-
envoy.yaml
: Envoy のメイン設定ファイル。
-
-
proto/
: gRPC のプロトコルバッファ(.proto
)ファイルを格納するディレクトリ。-
helloworld.proto
: サービスとメッセージの定義を含むプロトコルバッファファイル。
-
-
server/
: Rust で実装された gRPC サーバーのソースコードを格納するディレクトリ。-
Cargo.toml
: Rust のパッケージマネージャーである Cargo の設定ファイル。 -
src/
: ソースコードを格納するディレクトリ。-
main.rs
: アプリケーションのエントリーポイントとなる Rust ファイル。
-
-
-
docker-compose.yml
: Docker Compose を使用して、アプリケーションの各コンポーネント(gRPC サーバー、Envoy プロキシなど)を定義し、管理するための設定ファイル。 -
README.md
: プロジェクトの概要、セットアップ手順、使用方法などの情報を提供するドキュメントファイル。
-
各コンポーネントの役割
server
: gRPC サーバー
proto
: gRPC の定義
envoy
: API Gateway
docker-compose.yml
: 全体の管理
2. 環境構築
-
前提条件
- Rustがインストール済み
- Dockerがインストール済み
-
リポジトリのクローン
git clone https://github.com/kazuma0606/rust-microservice-with-envoy.git cd rust-microservice-with-envoy
-
Docker コンテナの起動
docker-compose up --build
-
動作確認
grpcurl -plaintext -d '{"name": "Rust"}' localhost:8080 helloworld.Greeter/SayHello
3. gRPC サーバーの実装(Rust)
- 1.
protoBuf
の作成
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
rpc SayHello (HelloRequest) returns (HelloReply) のように記載することで、
・SayHello関数
・HelloRequest構造体(RequestDto)
・HelloRequest構造体(ResponseDto)
以上が定義されます。
-
2.
Cargo.toml
の設定
今回はサーバはシンプルにgRPCで生成される関数を使って通信するだけの実装なので、依存関係はかなりシンプルです。
[package]
name = "server"
version = "0.1.0"
edition = "2021"
[dependencies]
tonic = "0.9"
prost = "0.11"
tokio = { version = "1", features = ["full"] }
tonic-reflection = "0.9.2" # tonicのバージョンに合わせて0.9系を使用
[build-dependencies]
tonic-build = "0.9"
[[bin]]
name = "server"
path = "src/server.rs"
中でも特に以下の項目が重要です。
tonicクレートはprotoBufから関数と構造体を生成します。
さらに[[bin]]でビルドターゲットを指定しています。
Cargo initで生成されるmain.rs
は使用していません。
[dependencies]
tonic = "0.9"
[build-dependencies]
tonic-build = "0.9"
[[bin]]
name = "server"
path = "src/server.rs"
- 3.
build.rs
のセットアップ - 4.
tonic
を利用した gRPC の構築
4. Envoy の設定
envoy.yaml
の解説
static_resources:
listeners:
- name: grpc_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: grpc_proxy
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: grpc_backend
http_filters:
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: grpc_backend
type: STRICT_DNS
connect_timeout: 5s
load_assignment:
cluster_name: grpc_backend
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: server
port_value: 50051
http2_protocol_options: {}
Envoy の設定ファイルの中で特に重要な部分を解説します。
🔹 static_resources
の設定
Envoy の基本的な設定を定義するセクションで、主に リスナー(listeners) と クラスタ(clusters) を指定します。
listenersとclustersの詳細
-
リスナー(
listeners
)- クライアントからのリクエストを受け取るためのエントリーポイント。
- ここでは
8080
ポートで gRPC-Web のリクエストを受け付けるよう設定。
-
クラスタ(
clusters
)- Envoy が通信するバックエンドサービス(gRPC サーバーなど)を定義する。
- ここでは Rust 製の gRPC サーバー(
server
)にリクエストを転送するよう設定。
🔹 listeners
の設定
listeners:
- name: grpc_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8080
-
grpc_listener
という名前のリスナーを作成。 -
0.0.0.0:8080
でリクエストを待ち受ける(全てのネットワークインターフェースからアクセス可能)。
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: grpc_proxy
-
http_connection_manager
フィルターを適用し、リクエストのルーティングを管理。 -
codec_type: AUTO
により、リクエストのプロトコルを自動判別(gRPC-Web をサポート)。
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: grpc_backend
- ルーティング設定(
route_config
)を定義。 -
prefix: "/"
で全てのリクエストをgrpc_backend
クラスター(gRPC サーバー)に転送。
http_filters:
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
-
envoy.filters.http.grpc_web
- gRPC-Web をサポートするフィルターを追加(Web クライアントからのリクエストを gRPC に変換)。
-
envoy.filters.http.router
- Envoy の基本的なルーティングを処理するフィルター。
🔹 clusters
の設定(gRPC サーバーへのルーティング)
clusters:
- name: grpc_backend
type: STRICT_DNS
connect_timeout: 5s
load_assignment:
cluster_name: grpc_backend
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: server
port_value: 50051
詳細な説明をみる
-
name: grpc_backend
-
grpc_backend
という名前のクラスタを作成。 -
listeners
で定義したルートがこのクラスタにリクエストを転送。
-
-
type: STRICT_DNS
-
server
というホスト名を DNS で解決し、動的に gRPC サーバーのアドレスを決定。 - Docker のサービス名
server
を使用しているので、docker-compose
内で解決される。
-
-
connect_timeout: 5s
- gRPC サーバーへの接続待ち時間を
5秒
に設定。
- gRPC サーバーへの接続待ち時間を
-
endpoints
→lb_endpoints
-
server:50051
にリクエストを転送するよう設定。 -
port_value: 50051
なので、Rust gRPC サーバーが 50051 ポートでリクエストを受け付ける前提。
-
http2_protocol_options: {}
- gRPC は HTTP/2 を使用するため、
http2_protocol_options
を有効化。
5.Dockerの設定
Dockerfile (server/Dockerfile)
# Rust の公式イメージをベースにする
FROM rust:latest as builder
# 必要なパッケージをインストール(protocを含む)
RUN apt-get update && apt-get install -y \
protobuf-compiler \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Cargo.toml と Cargo.lock をコピー
COPY server/Cargo.toml server/Cargo.lock ./
# 依存関係を先にダウンロードしてキャッシュを活用
RUN cargo fetch
# `proto/` をコンテナ内にコピー
COPY proto/ proto/
# ソースコードをコピー
COPY server/ .
# 必要なディレクトリを作成
RUN mkdir -p proto
# 事前にプロトコルバッファをコンパイルして descriptor.bin を生成
RUN cd proto && protoc --include_imports --descriptor_set_out=helloworld_descriptor.bin helloworld.proto
# バイナリをビルド
ENV DOCKER_BUILD=1
RUN cargo build --release
# Debian bookworm を使用
FROM debian:bookworm-slim
WORKDIR /app
# 必要なディレクトリを作成
RUN mkdir -p proto
# 必要なバイナリとファイルをコピー
COPY /app/target/release/server /app/server
COPY /app/proto/helloworld_descriptor.bin /app/proto/
# サーバーを実行
CMD ["/app/server"]
Rust製gRPCサーバーのコンテナ化
-
Rust公式イメージをベースにビルド
-
rust:latest
を使用し、gRPCの.proto
ファイルを処理するためにprotobuf-compiler
をインストール。
-
-
依存関係のキャッシュ最適化
-
Cargo.toml
やCargo.lock
を先にコピーし、cargo fetch
を実行することでビルド時間を短縮。
-
-
.proto
のコンパイル-
protoc
を使用してhelloworld_descriptor.bin
を生成し、gRPCの通信定義を準備。
-
-
Rustサーバーのバイナリ化
-
cargo build --release
で最適化されたバイナリを作成。
-
-
軽量なDebian環境へ移行
- 実行環境には
debian:bookworm-slim
を使用し、コンパイル済みのバイナリのみをコピー(コンテナサイズ削減)。 -
/app/server
をCMD
で起動。
- 実行環境には
docker-compose.yml
version: "3.7"
services:
server:
build:
context: .
dockerfile: server/Dockerfile
container_name: grpc_server
ports:
- "50051:50051"
envoy:
# **Envoy の `grpc_web` フィルターを含むバージョンに変更**
image: envoyproxy/envoy:v1.26-latest
container_name: envoy
volumes:
- ./envoy/envoy.yaml:/etc/envoy/envoy.yaml
ports:
- "8080:8080"
EnvoyとgRPCサーバーの連携
-
gRPCサーバー (
server
サービス)-
server/Dockerfile
をビルドし、ポート50051:50051
を公開。
-
-
Envoyプロキシ (
envoy
サービス)-
envoyproxy/envoy:v1.26-latest
イメージを使用し、gRPC-Web をサポート。 -
envoy.yaml
をボリュームマウントし、設定を適用。 -
8080
ポートでクライアントからのリクエストを受け付け、server
に転送。
-
5. 遭遇した問題と解決策
本プロジェクトでは、Rust製gRPCサーバーとEnvoyの統合 を試みる中で、いくつかの技術的な課題に直面しました。初期段階では、環境設定やコンテナ内の動作の違いにより、想定通りに通信ができないケースが多発しました。特に以下の点で 設定変更と環境の整合性を保つために多くの試行錯誤 を重ねました。
試したことの概要
-
GLIBCのバージョン問題
Rustのバイナリを軽量なDebian環境で実行しようとした際に、GLIBC_2.29
などのバージョン違いが原因で実行時エラーが発生。
→ Rustのコンパイル環境と実行環境のバージョンを統一することで解決 -
CORS設定の難しさ
gRPC-Web を動作させるために、EnvoyのCORS設定を適切に調整する必要があった。
→ Envoyのcors
設定を追加し、適切なオリジンを許可することで解決 -
Envoyの設定ミスによるgRPC通信エラー
envoy.yaml
のリスナー設定ミスにより、gRPCリクエストが適切にルーティングされずエラー発生。
→ リクエストの流れをデバッグしながら、適切なroute_config
を設定することで解決 -
ローカル開発とDocker環境の整合性問題
ローカルで動作するprotoc
のバージョンと、コンテナ内のprotoc
のバージョンに差異があり、gRPCの.proto
コンパイル時にエラーが発生。
→ Dockerコンテナ内でprotoc
を実行する形に統一し、開発環境間の差異をなくすことで解決
6. 今後の展望
本プロジェクトは、Rust製のgRPCマイクロサービスとEnvoyを組み合わせた基盤構築の MVP (Minimum Viable Product) として動作する状態になりました。しかし、今後さらに実用性を高めるために、以下の点を改善・拡張していく予定です。
-
Kubernetes でのオーケストレーション
現状はDocker Composeを利用していますが、マイクロサービスをより本番環境に近い形で管理するため、Kubernetes(K8s)を導入予定。
→ Envoyのサービスディスカバリとの統合や、Podのスケール管理を実験する -
フロントエンドとの統合
gRPC-Web を活用し、フロントエンド(Next.js / React など)から Rust の gRPC サーバーに直接通信できるようにする。
→ Envoy のリバースプロキシ機能を活用し、スムーズなフロントエンド統合を実現 -
モニタリング・ログ管理の追加
tracing
やprometheus
を導入し、リアルタイムのメトリクス収集やリクエストトラッキング を可能にする。
→ Grafana / Loki との連携を視野に入れ、可視化とデバッグを強化 -
サーバーレス環境(AWS Lambda, Cloud Run)への適用
低コストでスケール可能な環境として、RustのgRPCサーバーを AWS Lambda や Cloud Run にデプロイする方法を模索。
→ gRPCをサーバーレス環境で最適に動作させる方法を検証し、最小構成でのデプロイを目指す -
デスクトップアプリからの gRPC 通信
Webアプリだけでなく、デスクトップアプリ(Tauri / Electron)と gRPC の組み合わせ も検討。
→ ローカル環境で高速・セキュアにデータをやり取りできる構成を実装予定
7. まとめ
Rust × gRPC × Envoy を組み合わせることで、型安全かつ高パフォーマンスなマイクロサービス基盤 を構築できることを確認しました。Docker を活用し、環境の再現性も確保できました。
🔹 Rust × gRPC × Envoy の強み
Rust の型安全性とパフォーマンス により、バグの少ない堅牢なマイクロサービスを実装可能
Envoy による gRPC-Web サポートと柔軟なプロキシ機能 で、負荷分散や認証も対応可能
Docker で統一環境を構築 し、ローカルと本番の整合性を確保
🔹 実装で得た知見
Envoy の設定ミスが gRPC の不具合につながるため、細かい調整が必要
GLIBC のバージョン問題やローカルと Docker 環境の整合性に注意が必要
🔹 次の挑戦
Kubernetes でのオーケストレーション
フロントエンドとの統合
モニタリング・ログ管理の強化
デスクトップアプリからの gRPC 通信
さいごに
皆様のいいね!やフォローが大変励みになっております。
続いてRustやマイクロサービスアーキテクチャについて発信していきますので、ぜひ保存もお願いします。
Xでもたまに呟いていますので、フォロー頂けると嬉しいです。
それではまた次回の記事でお会いできるのを楽しみにしております。
Happy, Coding🤗
本記事のリポジトリ
Discussion