proxy-wasm-rust-sdkとその周辺技術
WebAssemblyでenvoyのフィルターが作成できると聞き調べてみました。
WebAssemblyで作るenvoyのフィルターを以降wasm filterと呼んでいきます。
wasm-filterを作成する方法は色々ありますが、今回はタイトルにもあるproxy-wasm-rust-sdkを利用していきます。
proxy-wasm-rust-sdk?
proxy-wasm-sdk-rushはWebAssembly for ProxiesというApplication Binary Interface(以降ABIと表記)に従って実装されているライブラリです。
予め提供されているtraitに実装を追加していくことで簡単にwasm-filterを作成できます。
proxy-wasm-rust-sdkの使い方
早速、wasm-filterを作っていきましょう。
作り方は大まかに以下の3つのstepになります。
- cargo.tomlのdependenciesに proxy-wasm を追加
- lib.rsに_start()を作成
- 任意のcontextの実装を作成
それぞれの手順について少しだけ丁寧に見ていきます。
cargo.tomlのdependenciesに proxy-wasm を追加
[dependencies]
log = "0.4.8"
proxy-wasm = "0.1.0" # The Rust SDK for proxy-wasm[lib]
path = "src/lib.rs"
crate-type = ["cdylib"]
proxy-wasmの依存を追加する他に、crate-type = [“cdylib”]を追加する必要があります。(別言語から動的にロードされるライブラリとして出力するためです。)
lib.rsに_start()を作成
#[no_mangle]
pub fn _start() {
proxy_wasm::set_log_level(LogLevel::Trace);
proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> { Box::new(HelloWorld) });
}
_start()のなかでset_xxx_contextを呼びだします。
proxy-wasm-rust-sdkでは以下3つのcontext traitに任意のstructを紐付けることができます。
- RootContext
- StreamContext
- HttpContext
任意のcontextの実装を作成
struct HelloWorld;impl Context for HelloWorld {}impl RootContext for HelloWorld {
fn on_vm_start(&mut self, _: usize) -> bool {
info!("ka23 dayo");
self.set_tick_period(Duration::from_secs(5));
true
}
...
}
拡張したい任意の機能に実装を追加します。
上記の例では、infoログを出力した後にtickの間隔を5秒に設定しています。
ここまでくれば後はbuildするだけです。
以下のコマンドでビルドしてしまいましょう。
$ cargo build --target wasm32-unknown-unknown --release
お疲れ様です。これで無事wasmファイルが作成されるはずです。
envoyへの設定方法を見る前に、proxy-wasm-rust-sdkが動く仕組みについて深ぼっていきましょう。
WebAssembly for Proxies
ここからはWebAssembly for Proxiesについて説明していきます。
上記の図のように、C++のnative拡張からwasmファイルが呼び出される設計となっています。
なぜこのような設計になったのか?その経緯は以下の通りです。
- envoy静的なバイナリとして提供される
- そのため、envoyの拡張も同じタイミングでコンパイルしないといけなかった
- 上記の理由からenvoyの拡張をするともれなく自前のenvoyバイナリを運用することが必須だった(裏を返すと公式のenvoyバイナリや素の状態のenvoyバイナリが利用できなくなってしまう制約があった)
- 動的にロードされるC++での拡張も考えられたが、活発なenvoyの開発スピードとはうまくマッチしなかった
- そこで WebAssemblyと安定したABIの提供でこの問題を解決しようという運びになった
Wasmでenvoyのフィルターを作成する試みは当初envoy-wasmというフォークリポジトリで開発が進められていました。
その後2020/03/05に以下のブログでWebAssembly for Proxiesが公開されます。
そして2020/10/10にenvoy本体にenvoy-wasmがmergeされました。
2020/12/06現在、upstreamのenvoyでwasm-filterが利用できます。(現在ベータ版の状態)
ABIの定義は以下にまとまっています。
ちなみにABIのバージョンを取得する方法がグローバル変数ではなくproxy_abi_version_X_Y_Zという関数になっているのは
Considered alternatives: Ideally, this would be exported as a global variable, but existing toolchains have trouble with that (some don’t support global variables at all, some incorrectly export language-specific and not Wasm-generic variables).
ツールチェインによって問題が起きることが大きな理由となっています。
そもそもグローバル変数をサポートしていなかったり、言語固有の変数やWasmジェネリック変数を正しくexportしてくれない場合があるようです。
wasm filterのデプロイとwasme
話をwasm filterのデプロイに戻しましょう。
デプロイにはwasme(自分はワズミーと呼んでいます。正しい呼称なのか自信はありません。)というcliを利用します。
$ curl -sL https://run.solo.io/wasme/install | sh
$ export PATH=$HOME/.wasme/bin:$PATH
wasmeを利用してwasm-filterをデプロイする場合、runtime-config.jsonというファイルを作成する必要があるので、これも作っていきます。
$ vi runtime-config.json
{
"type": "envoy_proxy",
"abiVersions": ["v0-097b7f2e4cc1fb490cc1943d0d633655ac3c522f", "v0-4689a30309abf31aee9ae36e73d34b1bb182685f", "v0.2.1"],
"config": {
"rootIds": [
"hello_world"
]
}
}
ここまでできたらbuild&deployで動作確認ができます。
お疲れ様でした。
$ wasme build precompiled target/wasm32-unknown-unknown/release/hello_world.wasm --tag hello_world:v0.1$ wasme deploy envoy hello_world:v0.1
$ wasme build precompiled
wasmファイルからOCI imageを作成するコマンドです。
ちなみに $ wasme build rust
というコマンドも存在しており、こちらはRustファイルから直接OCI imageを作成することができます。
wasmeについて
wasmeはSolo.io が提供するcliで、WebAssembly HubというEnvoy Filterを登録するためのレジストリサービスのために作られたツールです。
OCI image をpush/pullする機能の他にローカルやistio/Glooにデプロイする機能を有しています(今回はローカルデプロイ機能を利用しています)
(寄り道 — 1)The WASM OCI Artifactについて
wasmeで作成されるOCI imageは以下のリポジトリで仕様が定義されています。
本OCI imageは2つのレイヤーで構成されています。
- application/vnd.module.wasm.config.v1+json
- application/vnd.module.wasm.content.layer.v1+wasm
このうち、 application/vnd.module.wasm.config.v1+json の定義は runtime-config.json
に依存しています。
先の手順で runtime-config.json
を作成する必要があったのはこのためです。
runtime-config.jsonで定義できるのは以下3つの設定値です。
- type
- abiVersions
- root_Ids
typeの設定値は envoy_proxy
固定です。abiVersionsには配列で使用するabiVersionを指定します。rootIds提供されるフィルターで使用できるrootIdを指定します。
また、簡略化のため一つのOCI imageに登録できるmoduleは一つだけになっています。
wasme pull / wasme buid
をすると ~/.wasme/store/imageref
直下に以下のディレクトリとファイル群が保存されます。
- descriptor.json
- filter.wasm
- image_ref
- runtime-config.json
ちなみにOCIへの変換は wasme push
で行われています。(逆に wasme pull
では逆にローカルキャッシュする段階でOCIから上記のファイル構成に展開している)
(寄り道 — 2)wasme deploy envoyについて
wasme deploy envoy
で、なぜ動作確認ができるのか不思議に思う方もいるかもしれないので、簡単な処理の流れを説明します。
- ユーザが指定したwasmフィルターのイメージ(~/.wasme/store配下にある)を取得
- envoyのconfig にユーザが指定したwasmフィルターまでの絶対パスを追加
- docker を利用して envoy を実行(※)
上記の通り、dockerを利用してenvoyへのデプロイを実現しています。
使用しているdocker imageやenvoyのconfigは以下のとおりです。(2020/12/06時点)
- デフォルトで利用されるイメージ: docker.io/istio/proxyv2:1.5.1
- デフォルトで利用されるenvoyのconfig:jsonplaceholder.typicode.comへのリクエストをフィルターするもの
さいごに
当初はproxy-wasm-rust-sdkの話に絞って調査しようと思っていましたが、思いの外調査範囲が発散してしまいました。
一方で、Wasmを利用したevoy filterの開発はとても興味深くまた実際的なプロダクトだと感じました。(好きな言語で実装できるのは素敵ですよね!!!)
ここまで調べてから、GetEnvoy Extension Toolkitが面白そうという事実に気づいてしまいました。
https://www.getenvoy.io/reference/getenvoy_extension_toolkit_reference/#walkthrough
折をみて、こちらも見てみようと思います。
ここまで読んでくださって、ありがとうございます。
参考資料
- Extending Envoy with WASM and Rust
- Redefining extensibility in proxies — introducing WebAssembly to Envoy and Istio
- Extending Istio with Rust and WebAssembly
- Envoy での WebAssembly サポートと WebAssembly Hub, WASM OCI Image Specification について
- WebAssembly がネットワーク プロキシにもたらす拡張性
- Introducing the WebAssembly Hub, a service for building, deploying, sharing, and discovering Wasm extensions for Envoy Proxy
- Extension Toolkit Reference
- Proxy-wasm and its landscape
- WASM Artifact Image Specification v0.0.0