Google CloudとWasmの現在とこれから
Wasmの基礎を解説しつつ、Google CloudのService Extensionによる実装と将来の妄想
Table of contents
author: komem3
本資料はGI Cloud の Google Cloud LT 会 Vol.5で発表した内容をブログ用に書き起こしたものです。
はじめに
今回は、生成AIの影に隠れながらも成長を続けているWebAssembly(Wasm)に焦点を当てつつ、Google Cloud(この会の趣旨的に)とどう関係していくか? というのを記載していきます。
WebAssembly(Wasm)について
WebAssembly(Wasm)とは?
2015年6月に発表されたWebをコンパイルターゲットとして、移植性がある実行モデルを定義したものです。
Webのアセンブリということですね。
以下のGithub Issueに発表時のものでいくつかの発表ブログのリンクがあります。
https://github.com/WebAssembly/design/issues/150
Wasm の歴史
発表からの歴史として以下のようになります。
- 2017年3月 :FirefoxでWasmサポートにより初のブラウザ対応(https://pc.watch.impress.co.jp/docs/news/1048382.html)
- 2017年11月:主要ブラウザが対応(https://www.publickey1.jp/blog/17/webassembly_browsers.html)
- 2019年3月:WASIの発表(https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/)
- 2019年12月:Wasm 1.0がW3C勧告となり、正式に、HTML、CSS、JavaScriptに続く「第4のWeb標準言語」になる。(https://www.w3.org/TR/wasm-core-1/)
- 2020年3月:Proxy-Wasmの発表(https://istio.io/latest/blog/2020/wasm-announce/)
- 2021年12月:Wasm Component Modelの発表(https://radu-matei.com/blog/intro-wasm-components/)
- 2024年4月:WASI 0.2の発表(https://github.com/WebAssembly/WASI/releases/tag/v0.2.0)
あっという間にWeb標準になってから、今はブラウザ外での使用を策定中の段階です。
Wasm Component Model
次章のWASIの話をする前にこれの話が必要です。
Wasmは数値型しか受け渡しできません。 数値型しか渡せないため、共有メモリに渡したい変数を置いて、そのメモリのオフセットを渡す必要があります。
例として、重複排除する関数があるとすると以下のように、メモリのオフセットを受け取る関数を定義します。
remove-duplicates: func(offset: i32, length: i32) -> [i32, i32]
メモリをエクスポートしておきます。
export "string_mem" (mem 1)
そして、使用箇所でメモリをimportして、文字列を共有する必要がありました。
import "strings" "string_mem"
このようなことをせずに、数値型以外の型を使えるように機能拡張する仕様がWasm Component Modelです。
remove-duplicates: func(s: string) -> string
これを定めることで言語やWasmを跨いだ処理の受け渡しが可能になります。これにより言語を越えたWasmコンポーネントの共有が可能になりました。
WebAssembly System Interface(WASI)
Webブラウザの外でWasmを実行する際に、ファイルシステムやネットワークといったOSリソースへアクセスするための標準的なインターフェースを定めたものがWASIです Cloudflare Workersで実行可能なため、Wasm Component Modelに比べたら知名度の高い仕様です。
0.1の段階ではPosixのサポートがメインでしたが、0.2以降では、前述のWasm Component Modelを採用し、 型システムやモジュールなどを取り入れ様々な言語で使用できるように拡充されています。
「どのような環境でも」というのは環境毎のシステムコールができるようにという面が強いです。
つまり、Wasmのサンドボックス性を保ちつつ、HTTP呼び出しやファイルシステムの呼び出しなどを、どの環境でもできるようにするというなかなか大変な仕様です。
策定状況は以下のリンクから確認できます。まだまだこれからということが一目で分かります。
https://github.com/WebAssembly/WASI/blob/main/docs/Proposals.md
Proxy-Wasm
WASIと似た仕様としてProxy-Wasmが存在します。
これは、GoogleのistioチームがEnvoyをWasmによる拡張をサポートするために開発したものを、Envoy以外へのプロキシにも組み込めるように仕様を公開したものです。
WASIがWasmによる汎用インタフェースだとすると、Proxy-WasmはWasmによるプロキシ拡張専用インフェースです。
WASIの初出が2019年3月ですが、WASI 0.2の公開は2024年4月です。
このProxy-Wasmの公開は2020年3月なので、Wasm拡張という概念があったとしても、実装という面ではなかなか先を行っています。
Google CloudとWasm
ということで、ようやくGoogle Cloudの話ができる所まできました。
ここからは、Google CloudにおけるWasmの使用、そして今後どうなっていきそう、という妄想を話していきます。
Service Extensions
何で使われているか、と言ってもこれでしか使われていません。
それがこれ、Service Extensionsです。
Service Extensions を使用すると、Cloud Load Balancing や Media CDN などの Google Cloud プロダクトのユーザーが、カスタムコードをデータパスに直接挿入できます。 これにより、ビジネスニーズに合わせてこれらのプロダクトの動作をカスタマイズできます。
出展:https://docs.cloud.google.com/service-extensions/docs/overview?hl=ja
書いてあることは凄いです。エンジニアは、こういうドキュメントとお台場のガンダムで興奮します。
Service Extensionsの実態
賢い人なら気付いているはずです。Service ExtensionsのWasmはWASI 0.2ではなく、先程説明したProxy-Wasmになっています。
前述した通りProxy-WasmはGoogleがEnvoyを拡張するために作成しました。
そして今のLoadBalancerはEnvoyを使用しています。
Proxy-Wasmベースというのは当り前の話です。
そのため、Cloud LoadBalancerとCloud CDNとMedia CDNのみが対応しています。
もう一度公式ドキュメントを見ますか。
Service Extensions を使用すると、Cloud Load Balancing や Media CDN など
嘘は言ってないですね…。
使用する上でもProxy-Wasmの制限を受けるので、その辺を理解する必要があります。
Call OutとPlugin
Service ExtensionsにはCall Outという、外部のサーバーやサービスを呼び出すタイプと、Pluginと呼ばれるWasmを直接配置するタイプの2つがあります。 今回はWasmの話がメインのため、Call Outには触れません。
勿論Call Outの方ができることは多いです。他サービスのModel Armorを指定できますし、一度Cloud Runとかに飛ぶので、色々なことができちゃいます。 でも今回はWasmの話をしているので、ここでしか触れません。
ここがすごいよ Service Extensions
サゲから入りましたが、すごいことには変わりありません。
以下のようにWasmを埋め込むことでLoadbalancerの挙動を変更できます。
大体以下のことができます。
- ログの出力
- ヘッダー変更
- リクエストパスの変更
- Bodyの変更
ネットワーク呼び出しなどは行なえないです。(Proxy-Wasmの仕様上は可能)
試してみよう
ここからは試しにService Extensionsを使用します。
色々なタイミングに差し込めますが、今回はEdgeにBasic Auth認証を差し込んでみます。Armorより早いBasic Authです。
実装コード
以下のようなコードを書きました。
package main
import (
"encoding/base64"
"fmt"
"strings"
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm"
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types"
)
func main() {}
func init() {
proxywasm.SetVMContext(&vmContext{})
}
type vmContext struct {
types.DefaultVMContext
}
type pluginContext struct {
types.DefaultPluginContext
secret string
}
type httpContext struct {
types.DefaultHttpContext
pluginContext *pluginContext
}
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
return &pluginContext{}
}
// 初期化時に呼び出される
func (ctx *pluginContext) OnPluginStart(int) types.OnPluginStartStatus {
// 外部から設定ファイルを指定できる
config, err := proxywasm.GetPluginConfiguration()
if err != nil {
proxywasm.LogErrorf("Error reading the configuration: %v", err)
return types.OnPluginStartStatusFailed
}
ctx.secret = string(config)
return types.OnPluginStartStatusOK
}
func (ctx *pluginContext) NewHttpContext(uint32) types.HttpContext {
return &httpContext{pluginContext: ctx}
}
func (ctx *pluginContext) Secret() string {
return ctx.secret
}
// リクエストが来たときに呼ばれる
func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
defer func() {
err := recover()
if err != nil {
proxywasm.LogErrorf("onRequestHeaders error: %v", err)
proxywasm.SendHttpResponse(500, [][2]string{}, []byte(fmt.Sprintf("%v", err)), 0)
}
}()
proxywasm.LogInfof("onRequestHeaders: hello from wasm")
auth, err := proxywasm.GetHttpRequestHeader("Authorization")
if err != nil {
if err == types.ErrorStatusNotFound {
unAuthorized()
// Pause を返すと次の処理にいかない
return types.ActionPause
}
panic(err)
}
if !strings.HasPrefix(auth, "Basic ") {
unAuthorized()
return types.ActionPause
}
auth = strings.TrimPrefix(auth, "Basic ")
decodeAuth, err := base64.StdEncoding.DecodeString(auth)
if err != nil {
proxywasm.LogErrorf("failed to decode auth: %v", err)
unAuthorized()
return types.ActionPause
}
// 設定ファイルに記載のsecretと同じ値かを比較
if string(decodeAuth) != ctx.pluginContext.Secret() {
unAuthorized()
return types.ActionPause
}
return types.ActionContinue
}
// 認証エラーの時は401を返すようにする
func unAuthorized() {
if err := proxywasm.SendHttpResponse(401, [][2]string{
{"WWW-Authenticate", "Basic realm=\"SECRET AREA\""},
}, []byte("Unauthorized"), 0); err != nil {
panic(err)
}
}
リクエストデータをコンテキストから取得するのではなく、ライブラリが共有メモリから取得するのがWasmらしさになっています。
Google Cloudにアップロード
- 以下コマンドでビルドして
env GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o plugin.wasm main.go
- Dockerfileを作って
FROM scratch
COPY plugin.wasm plugin.wasm
-
docker buildして
-
Artifact RegistryにPushします。
Docker形式じゃなくてバイナリー直置きもできるのですが、Service Extensionsの更新APIが対応してなかったので、dockerがおすすめです。
バイナリーを組み込むというのは中々夢があるのですが、上手くできなかったのは残念です😢。
環境整備
コンソールでポチポチやるとプラグインを作成できます。

LB作成後、ポチポチと先程のプラグインに紐付けてあげます。

適当に作ったCloud RunサーバでBasic Auth認証が求められているのを確認できました。

Google CloudとWasmの未来を妄想
Service Extensionsとこれから
Service ExtensionsではプロダクトのWasmによる拡張という可能性を提示しています。 これはWASIベースではなくProxy-Wasmベースによる実装のため、他のCloud Storageなどのサービスへの展開はまだ望み薄です。
しかし、そもそもプラグインによってサービスを拡張するというのはWASIの思想そのものです。
そのため、WASIによるサービスの拡張というサービスがこれから出てくる可能性は高いと思っています。
一例ですが、こんなことができるようになったらいいなというものです。
- Cloud StorageにIP制限設ける
- Cloud Storageのファイル内容をユーザに応じて匿名化
- Pub/Subのメッセージ加工・フィルター(今のSMTをよりリッチにしたもの)
- BigQueryやGeminiに関数を組み込み(今は外部呼び出しは可能)
- IAMのカスタムバリデーション
WASIによる真のFaaS
みなさんはCloud Functionというサービスを覚えていますか?
今はもう、実質Cloud Run(正式にはCloud Run functionsという名前)になってしまったサービスです。
これは関数のコードをアップロードすると勝手にデプロイしてくれる、関数を書くだけでいいというサービスでした。
ぶっちゃけ今は裏で動いているのがCloud Runで設定項目もCloud Runになるため、ちょっとしたサーバコードを入れないという横着をしてまで使うサービス?と言われると疑問に思ってしまうサービスになっています。
しかし、WASIを使うと、Wasmとしてコンパイルした関数を、動いているインスタンスに乗せることができます。 Wasmはサンドボックス性があるため、同一インスタンスに異なるWasmを乗せることが可能です。
やることはCloudflare workersのV8分離と似てきます(cloudflare workersは真のFaaSに近いです)。ただ、WASIによるFaaSが見せてくれるのは、ベンダーロックインしないポータブルな世界です。
真のService ExtensionsとCloud Function
WASIの仕様がもう少し固まったら、Service ExtensionsとCloud Functionは真の姿に生まれ変わると信じています。
今のように、Loadbalancerしか拡張できないのではなく、
ただのCloud Runのラッパーではなく、
夢を感じる、そんなサービスになるはずです。
GoogleがProxy-Wasmに満足してないで、WASIの波に乗ってくれることを信じましょう。
おわりに
Wasmは安定期に入ってきていますが、WASIはまだまだこれからです。 なかなか流行らないのじゃないのです。まだ生まれたばかりなのです。
WASIが描き出す未来を楽しみにしましょう。
※本記事は、ジーアイクラウド株式会社の見解を述べたものであり、必要な調査・検討は行っているものの必ずしもその正確性や真実性を保証するものではありません。
※リンクを利用する際には、必ず出典がGIC dryaki-blogであることを明記してください。
リンクの利用によりトラブルが発生した場合、リンクを設置した方ご自身の責任で対応してください。
ジーアイクラウド株式会社はユーザーによるリンクの利用につき、如何なる責任を負うものではありません。