Zenn
🫡

Rust× Envoy を活用したマイクロサービス実装入門

2025/03/20に公開
1

概要

Rust で gRPC サーバーを構築し、Envoy を API ゲートウェイとして組み合わせたマイクロサービスの基盤を作成しました。本記事では、その設計・実装・課題・今後の展望について詳しく解説します。
また、本記事では図解もふんだんに載せておりますので、流し見でも大枠はつかめるかと思います。

はじめに

  • なぜ Rust なのか?
    Rust言語はgRPCを用いたマイクロサービスアーキテクチャを構築する場合に以下のような利点があります。
    Rustがマイクロサービスアーキテクチャに適する点

    🔸高速性とメモリ安全性

    C++に勝るとも劣らない実行スピード、そしてメモリリークの起きにくい

    🔸強力な型システムによるバグ防止

    多くの不具合がビルド時に解決できます

    🔸gRPC との相性の良さ(`tonic` の活用)

    tonicクレートを使うことでGoやPythonのように明示的にコードを生成することなく、バックグラウンドでコードが生成されます。

  • なぜ gRPC なのか?
    gRPCを選択する理由

    🔸高速・スキーマ駆動の通信

    低レイテンシーで通信スピードが速い特徴があります。また、protoBufによるスキーマを定義し、PoC(Proof of Concept:概念実証)の一環としてスキーマの設計・検証を行い、その後スキーマ駆動開発に移行できる

    🔸REST API よりも効率的なデータ通信が可能

    高速性とやや被ってしまいますが、バイナリ通信が可能なので効率的なデータ通信が可能です。
    後述しますが、ストリーミングも可能です。ただし、本記事ではストリーミングは使いません。

  • なぜ Envoy なのか?

Envoyを選ぶ理由

🔸 gRPC-Web のサポート

gRPC は高効率な通信を実現できますが、ブラウザ環境ではそのまま使用できません。
Envoy は gRPC-Web プロキシ の役割を果たし、クライアントとサーバー間の通信を可能にします。

🔸 負荷分散や認証などの機能が強力

Envoy は L7 プロキシとして動作し、トラフィックのルーティングやロードバランシングを細かく制御できます。
また、認証やトレーシング機能も組み込まれており、スケーラブルなマイクロサービスの基盤 を構築しやすいです。

🔸 Istio との統合も視野に

Envoy は Istio のデフォルトのデータプレーンとして設計されており、サービスメッシュ構成 との親和性が高いです。
Kubernetes 上で動作するマイクロサービスを セキュアかつ柔軟に管理 できるようになります。


想定する読者層

  • Rust でのバックエンド開発に興味がある人
  • gRPC を実戦で活用したい人
  • Envoy の設定に苦戦している人
  • マイクロサービスの設計を考えている人

この記事を読んで分かること

  1. Rust × gRPC × Envoy の組み合わせを学べる
  2. Docker を使った実践的な環境構築の方法
  3. 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秒 に設定。
  • endpointslb_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 --from=builder /app/target/release/server /app/server
COPY --from=builder /app/proto/helloworld_descriptor.bin /app/proto/

# サーバーを実行
CMD ["/app/server"]

Rust製gRPCサーバーのコンテナ化

  1. Rust公式イメージをベースにビルド

    • rust:latest を使用し、gRPCの .proto ファイルを処理するために protobuf-compiler をインストール。
  2. 依存関係のキャッシュ最適化

    • Cargo.tomlCargo.lock を先にコピーし、cargo fetch を実行することでビルド時間を短縮。
  3. .proto のコンパイル

    • protoc を使用して helloworld_descriptor.bin を生成し、gRPCの通信定義を準備。
  4. Rustサーバーのバイナリ化

    • cargo build --release で最適化されたバイナリを作成。
  5. 軽量なDebian環境へ移行

    • 実行環境には debian:bookworm-slim を使用し、コンパイル済みのバイナリのみをコピー(コンテナサイズ削減)。
    • /app/serverCMD で起動。

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サーバーの連携

  1. gRPCサーバー (server サービス)

    • server/Dockerfile をビルドし、ポート 50051:50051 を公開。
  2. 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) として動作する状態になりました。しかし、今後さらに実用性を高めるために、以下の点を改善・拡張していく予定です。
Rust製のgRPCマイクロサービスとEnvoyを組み合わせた基盤

  • Kubernetes でのオーケストレーション
    現状はDocker Composeを利用していますが、マイクロサービスをより本番環境に近い形で管理するため、Kubernetes(K8s)を導入予定。
    Envoyのサービスディスカバリとの統合や、Podのスケール管理を実験する

  • フロントエンドとの統合
    gRPC-Web を活用し、フロントエンド(Next.js / React など)から Rust の gRPC サーバーに直接通信できるようにする。
    Envoy のリバースプロキシ機能を活用し、スムーズなフロントエンド統合を実現

  • モニタリング・ログ管理の追加
    tracingprometheus を導入し、リアルタイムのメトリクス収集やリクエストトラッキング を可能にする。
    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🤗


本記事のリポジトリ

https://github.com/kazuma0606/rust-microservice-with-envoy

1

Discussion

ログインするとコメントできます