proxy-wasm-rust-sdkとその周辺技術

dorayakikun
14 min readDec 7, 2020

--

Photo by Robynne Hu on Unsplash

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になります。

  1. cargo.tomlのdependenciesに proxy-wasm を追加
  2. lib.rsに_start()を作成
  3. 任意の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を紐付けることができます。

  1. RootContext
  2. StreamContext
  3. 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について説明していきます。

https://github.com/proxy-wasm/spec/blob/master/docs/WebAssembly-in-Envoy.md より引用

上記の図のように、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 precompiledwasmファイルから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つの設定値です。

  1. type
  2. abiVersions
  3. 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 で、なぜ動作確認ができるのか不思議に思う方もいるかもしれないので、簡単な処理の流れを説明します。

  1. ユーザが指定したwasmフィルターのイメージ(~/.wasme/store配下にある)を取得
  2. envoyのconfig にユーザが指定したwasmフィルターまでの絶対パスを追加
  3. 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

折をみて、こちらも見てみようと思います。
ここまで読んでくださって、ありがとうございます。

参考資料

--

--

dorayakikun
dorayakikun

Written by dorayakikun

Flag of Japan he/him. Web Developer. A founder of the http://rust.tokyo . An organizer of http://RustFest.global . All my own views.