Go で WebAssembly を利用した実用的なプラグインシステムの構築方法
Guest(プラグイン本体)で実行する処理を Host(プラグインを利用するアプリケーション)で制限できる
Guest がクラッシュした時に、Host が巻き込まれない
一度ビルドしたら、ランタイムがあればどこでも動く
WASI の仕様は現在 Preview 2(WASI 0.2)までリリース済み
Go は Preview 1(WASI 0.1)まで実装済み
v.1.21 から対応
Preview 1 では Network Socket や Thread が未対応
v1.21 で wasmimport(Host Function)が、1.24 で wasmexport(Guest Function)が利用可能
WASM にした時のサイズが Go と比べて小さい
reflect を使っている場合などはビルドできない
WASM ランタイム
Pure Go で実装されている唯一のランタイム
採用実績あり
Interpreter(WASM VM で逐次 WASM 命令を実行)と Compiler(WASM Binary から 機械語 を生成して実行) の 2 つのランタイムを持つ WASM の壁
Guest 関数を呼び出した場合、関数を抜けると非同期処理も停止
WASM インスタンスはシングルスレッド / 非同期割り込みなし
最小の Go コードをインスタンス化した際に利用するメモリ量は 8 MB ある
そのため、リクエストごとにインスタンスを作って並行処理するのは難しい
Network Socket
wasmimport で HTTP リクエストを Host に委譲することもできる
しかし、全ての箇所を置き換える必要がある
サードパーティの処理まで考えると現実的ではない
WASI P1 ビルドでは、サーバ証明書の正当性をチェックする システム証明書ストア を用意していない そのため、明示的に証明書を指定しないと失敗する
コマンド実行
WASM P1 は exec.Command をサポートしていない
回避策
Network Socket
サードパティライブラリを使うことで、Network Socket 対応の実装を wazero で利用できる
ビルド時に標準ライブラリの実装を差し替える
go build -overlay: ファイルを任意のものに差し替える
//go:linkname: 他の package にある symbol を直接参照する
symbol 解決のタイミングをリンク時に遅らせることで、パッケージの循環参照問題を回避できる
プラグインビルド用の専用コマンドを用意することで、置き換え処理を意識せずに利用できる
TLS / コマンド実行
標準ライブラリの置換で対応する
TLS: crypto/x509.(*Certificate).Verify を置換
exec.Command: os/exec.(*Cmd).Start/Stop を置換
wasmimport を使い、置換した関数から Host に処理を委譲する
Host に処理が戻るため、実行したくないコマンドを弾くといったセキュアな実行方法も実現できる
非同期処理
https://gyazo.com/dcce1fe0084fd54737c11c5abcf10060
https://gyazo.com/b4c2ea6dc0a8956e7694a8adb557b01d
メモリ使用量
インスタンスを任意の数起動しておき、ロードバランスする運用が有効
Host と Guest のメモリ管理は別なので、Guest 側で GC が適切に動くように環境変数を設定したり、runtime.GC() を明示的に呼び出すなど工夫も有効 Tips
GOMAXPROCS に 2 以上を指定すると panic Env を Guest に渡す場合には、GOMAXPROCS をフィルタリングする
外部 API 利用時には、mTLS の処理を無効化する必要がある GOOGLE_API_USE_CLIENT_CERTIFICATE=false
GOOGLE_API_USE_MTLS_ENDPOINT=never