Service Mesh (ASM) は、カスタムリクエスト処理のために Envoy プロキシでの Wasm プラグインをサポートしています。このチュートリアルでは、HTTP リクエストヘッダーを検査し、その結果に基づいてリクエストを許可またはブロックする Rust での Wasm プラグインの作成について説明します。
プラグインは、受信リクエストに allow: true ヘッダーが含まれているかどうかをチェックします。ヘッダーがないか、他の値に設定されている場合、プラグインはカスタムエラーメッセージとともに HTTP 403 を返します。ヘッダーが存在し、true に設定されている場合、リクエストはアップストリームサービスに渡されます。
仕組み
Wasm プラグインは、サンドボックスモジュールとして Envoy プロキシ内で実行されます。Proxy-Wasm の仕様は、プロキシホストと Wasm モジュール間の通信のための標準アプリケーションバイナリインターフェイス (ABI) を定義しています。Proxy-Wasm はプロキシに依存しないため、この ABI に対して構築されたプラグインは、仕様を実装する任意のプロキシ間でポータブルです。
Proxy-Wasm Rust SDK は、プラグインロジックを 3 つの trait 型を中心に整理しています。
| Trait | 役割 |
|---|---|
RootContext | プラグインのライフサイクルと構成を管理します。各リクエストに対して新しい HTTP またはストリームコンテキストを作成します。 |
HttpContext | 単一の HTTP リクエスト/応答サイクルを処理します。on_http_request_headers などのコールバックを実装して、トラフィックを検査または変更します。 |
Context | ルートコンテキストと HTTP コンテキストの両方に継承される共有ユーティリティ関数 (プロパティアクセス、タイマーなど) を提供します。 |
Envoy が HTTP リクエストを受信すると、RootContext を介して新しい HttpContext インスタンスを作成します。HttpContext コールバックは、リクエスト処理の各段階で起動し、プラグインにヘッダー、本文、トレーラーに対する完全な制御を与えます。
背景情報
Wasm は、ほぼネイティブな実行パフォーマンスを提供し、メモリセーフなサンドボックスで実行されるため、プロキシバイナリを再コンパイルすることなく Envoy を拡張するのに適しています。ただし、組み込みの GC を持つ言語は、Wasm でパフォーマンスオーバーヘッドを発生させる可能性があります。このため、手動メモリ管理を行う言語 (C++ と Rust) が推奨される選択肢です。
C++ と比較して、Rust はよりシンプルなコンパイルおよびビルドワークフローを提供しますが、学習曲線は急です。各言語に対するチームの習熟度に基づいて選択してください。
前提条件
開始する前に、以下があることを確認してください。
バージョン 1.18 以降の ASM インスタンスに追加されたクラスター。詳細については、「ASM インスタンスへのクラスターの追加」をご参照ください。
Sidecar インジェクションが有効になっていること。詳細については、「Sidecar インジェクションポリシーの構成」をご参照ください。
イングレスゲートウェイが作成されていること。詳細については、「イングレスゲートウェイの作成」をご参照ください。
HTTPBin アプリケーションがデプロイされ、アクセス可能であること。詳細については、「HTTPBin アプリケーションのデプロイ」をご参照ください。
Container Registry Enterprise Edition インスタンスが作成されていること。詳細については、「Container Registry Enterprise Edition インスタンスの作成」をご参照ください。
ワークフロー概要
エンドツーエンドのプロセスには、次の 5 つのステップがあります。
開発環境のセットアップ -- Rust ツールチェーンと Wasm コンパイルターゲットをインストールします。
プラグインの作成 -- Rust ライブラリプロジェクトを作成し、依存関係を定義し、リクエストヘッダー検査ロジックを実装します。
ビルドとパッケージ化 -- Rust コードを Wasm バイナリにコンパイルし、OCI イメージとしてパッケージ化します。
デプロイ -- イメージを Container Registry にプッシュし、ASM で Wasm プラグインリソースを適用します。
検証 -- プラグインが期待どおりにトラフィックをブロックおよび許可することを確認するためにテストリクエストを送信します。
ステップ 1: 開発環境のセットアップ
rustup を介して Rust ツールチェーンをインストールします。手順については、「Rust のインストール」をご参照ください。
wasm32-wasiコンパイルターゲットを追加します。wasm32-wasiターゲットは、Envoy の Wasm ランタイムが期待する WASI システムインターフェイスへのアクセスを伴って Rust コードを WebAssembly にコンパイルします。Rust がすでにインストールされている場合は、まず更新してください。rustup target add wasm32-wasirustup update
ステップ 2: プラグインの作成
このステップでは、プロジェクトの作成、依存関係の構成、プラグインロジックの実装の 3 つのパートについて説明します。
プロジェクトの作成
rust-example という名前のディレクトリを作成し、それに切り替えて、新しい Rust ライブラリを初期化します。
mkdir rust-example && cd rust-example
cargo init --lib依存関係の構成
Cargo.toml の内容を以下で置き換えます。
[package]
name = "rust-example"
version = "0.1.0"
edition = "2021"
[lib]
# Build as a C-compatible dynamic library so Envoy can load it
crate-type = ["cdylib"]
[dependencies]
log = "0.4.8"
proxy-wasm = "0.2.2"cdylib クレート型は、Wasm ランタイムと互換性のある動的ライブラリを生成します。2 つの依存関係は次のとおりです。
log-- 構造化されたログ出力のための標準 Rust ロギングファサード。proxy-wasm-- trait 定義とホスト関数バインディングを提供する Proxy-Wasm Rust SDK。
プラグインロジックの実装
プラグインは以下を行う必要があります。
このプラグインが HTTP トラフィックを処理することを Envoy に伝える
RootContextを登録します。受信 HTTP リクエストごとに、リクエストヘッダーを検査する
HttpContextを作成します。on_http_request_headersコールバックで、allow: trueヘッダーをチェックします。ヘッダーがない場合は HTTP 403 でリクエストをブロックし、存在する場合は通過させます。
src/lib.rs の内容を以下で置き換えます。
use log::info;
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
proxy_wasm::main! {{
proxy_wasm::set_log_level(LogLevel::Trace);
proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> { Box::new(HttpHeadersRoot) });
}}
struct HttpHeadersRoot;
// Inherit shared utility functions (property access, timers, etc.)
impl Context for HttpHeadersRoot {}
impl RootContext for HttpHeadersRoot {
fn get_type(&self) -> Option<ContextType> {
Some(ContextType::HttpContext)
}
fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> {
Some(Box::new(HttpHeaders { context_id }))
}
}
struct HttpHeaders {
context_id: u32,
}
impl Context for HttpHeaders {}
impl HttpContext for HttpHeaders {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
info!("#{} wasm-rust: on_http_request_headers", self.context_id);
match self.get_http_request_header("allow") {
Some(allow) if allow == "true" => {
Action::Continue
}
_ => {
info!("#{} wasm-rust: allow header not found or is not true, deny by default", self.context_id);
self.send_http_response(
403,
vec![("Content-Type", "text/plain")],
Some(b"Forbidden by ASM Wasm Plugin, rust version\n"),
);
Action::Pause
}
}
}
}コードの内訳は次のとおりです。
| セクション | 目的 |
|---|---|
proxy_wasm::main! | エントリポイントマクロ。Trace にログレベルを設定し、HttpHeadersRoot をルートコンテキストとして登録します。 |
HttpHeadersRoot + RootContext | このプラグインが HTTP トラフィックを処理することを Envoy に伝えるために ContextType::HttpContext を返します。各リクエストに対して新しい HttpHeaders インスタンスを作成します。 |
HttpHeaders + HttpContext | on_http_request_headers を実装します。allow ヘッダーを読み取ります。値が "true" の場合は Action::Continue を返し、それ以外の場合は HTTP 403 応答を送信し、リクエストを停止するために Action::Pause を返します。 |
プラグインのコンパイル
wasm32-wasi ターゲットでプラグインをビルドします。
cargo build --target wasm32-wasi --releaseビルドが成功すると、Wasm バイナリは次の場所にあります。
target/wasm32-wasi/release/rust_example.wasmステップ 3: OCI イメージのビルドと Container Registry へのプッシュ
ASM がプルしてロードできるように、Wasm バイナリを OCI イメージとしてパッケージ化します。
プロジェクトルートに
Dockerfileを作成します。FROM scratch # Copy the compiled Wasm binary into the image as plugin.wasm ADD target/wasm32-wasi/release/rust_example.wasm ./plugin.wasmイメージをビルドしてプッシュします。詳細な手順については、「Wasm プラグインの OCI イメージを作成し、Container Registry Enterprise Edition インスタンスにプッシュする」をご参照ください。
<your-registry>と<tag>を実際のレジストリアドレスとイメージタグに置き換えてください。docker build -t <your-registry>/wasm-rust-example:<tag> . docker push <your-registry>/wasm-rust-example:<tag>
ステップ 4: Wasm プラグインのイングレスゲートウェイへの適用
ASM で WasmPlugin リソースを構成し、プラグインをイングレスゲートウェイの Envoy プロキシにロードします。
完全な手順については、「Wasm プラグインをイングレスゲートウェイに適用する」をご参照ください。url フィールドがステップ 3 でプッシュしたイメージを指していることを確認してください。
ステップ 5: プラグインの検証
プラグインをデプロイした後、allow: true ヘッダーの有無にかかわらずリクエストを送信してテストします。
デバッグロギングの有効化
データプレーンクラスターの kubeconfig を使用して、ゲートウェイ Pod で Wasm デバッグログを有効にします。
kubectl -n istio-system exec <gateway-pod-name> -c istio-proxy -- \
curl -XPOST "localhost:15000/logging?wasm=debug"<gateway-pod-name> をご利用のゲートウェイ Pod の実際の名前で置き換えてください。
allow ヘッダーなしでのテスト
allow ヘッダーなしでリクエストを送信します。
curl <ASM-gateway-IP>/status/418期待される出力:
Forbidden by ASM Wasm Plugin, rust versionプラグインはリクエストをブロックし、HTTP 403 を返します。
ゲートウェイログの確認
ゲートウェイ Pod ログで次のようなエントリを検査します。
2024-09-05T08:33:31.079869Z info envoy wasm wasm log istio-system.header-authorization: #2 wasm-rust: on_http_request_headers
2024-09-05T08:33:31.079943Z info envoy wasm wasm log istio-system.header-authorization: #2 wasm-rust: allow header not found or is not true, deny by default
{"authority_for":"xx.xx.xx.xx","bytes_received":"0","bytes_sent":"43","downstream_local_address":"xx.xx.xx.xx:80","downstream_remote_address":"xx.xx.xx.xx:xxxxx","duration":"0","istio_policy_status":"-","method":"GET","path":"/status/418","protocol":"HTTP/1.1","request_id":"d5250d1a-54b3-406d-8bea-5a51b617b579","requested_server_name":"-","response_code":"403","response_flags":"-","route_name":"httpbin","start_time":"2024-09-05T08:33:31.079Z","trace_id":"-","upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local","upstream_host":"-","upstream_local_address":"-","upstream_response_time":"-","upstream_service_time":"-","upstream_transport_failure_reason":"-","user_agent":"curl/8.9.0-DEV","x_forwarded_for":"xx.xx.xx.xx"}これらのログ行は、プラグインが実行され、リクエストを拒否したことを確認します。
allow ヘッダーありでのテスト
allow: true を付けてリクエストを送信します。
curl <ASM-gateway-IP>/status/418 -H "allow: true"期待される出力:
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`リクエストは HTTPBin に渡され、標準の HTTP 418 teapot 応答を返します。プラグインは正常に動作しています。
次のステップ
代わりに Go で Wasm プラグインを作成するには、このチュートリアルの Go バージョンをご参照ください。
HTTP レスポンス本文の変更、構成のロード、gRPC 認証など、proxy-wasm Rust SDK リポジトリでさらに多くのプラグインの例を探索してください。