Google CloudとWasmの現在とこれから

2026/03/27に公開されました。
2026/03/27に更新されました。

Wasmの基礎を解説しつつ、Google CloudのService Extensionによる実装と将来の妄想


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 の歴史

発表からの歴史として以下のようになります。

あっという間に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の話をしているので、ここでしか触れません。

callout-overview

ここがすごいよ Service Extensions

サゲから入りましたが、すごいことには変わりありません。

以下のようにWasmを埋め込むことでLoadbalancerの挙動を変更できます。

plugin-overview

大体以下のことができます。

  • ログの出力
  • ヘッダー変更
  • リクエストパスの変更
  • Bodyの変更

ネットワーク呼び出しなどは行なえないです。(Proxy-Wasmの仕様上は可能)

試してみよう

ここからは試しにService Extensionsを使用します。

色々なタイミングに差し込めますが、今回はEdgeにBasic Auth認証を差し込んでみます。Armorより早いBasic Authです。

callout-lb

実装コード

以下のようなコードを書きました。

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にアップロード

  1. 以下コマンドでビルドして
env GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o plugin.wasm main.go
  1. Dockerfileを作って
FROM scratch
COPY plugin.wasm plugin.wasm
  1. docker buildして

  2. Artifact RegistryにPushします。

Docker形式じゃなくてバイナリー直置きもできるのですが、Service Extensionsの更新APIが対応してなかったので、dockerがおすすめです。

バイナリーを組み込むというのは中々夢があるのですが、上手くできなかったのは残念です😢。

環境整備

コンソールでポチポチやるとプラグインを作成できます。

service extension

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

lb extension

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

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が見せてくれるのは、ベンダーロックインしないポータブルな世界です。

cloud run vs cloudflare workers vs wasi faas

真のService ExtensionsとCloud Function

WASIの仕様がもう少し固まったら、Service ExtensionsとCloud Functionは真の姿に生まれ変わると信じています。

今のように、Loadbalancerしか拡張できないのではなく、
ただのCloud Runのラッパーではなく、
夢を感じる、そんなサービスになるはずです。

GoogleがProxy-Wasmに満足してないで、WASIの波に乗ってくれることを信じましょう。

おわりに

Wasmは安定期に入ってきていますが、WASIはまだまだこれからです。 なかなか流行らないのじゃないのです。まだ生まれたばかりなのです。

WASIが描き出す未来を楽しみにしましょう。

※本記事は、ジーアイクラウド株式会社の見解を述べたものであり、必要な調査・検討は行っているものの必ずしもその正確性や真実性を保証するものではありません。

※リンクを利用する際には、必ず出典がGIC dryaki-blogであることを明記してください。
リンクの利用によりトラブルが発生した場合、リンクを設置した方ご自身の責任で対応してください。
ジーアイクラウド株式会社はユーザーによるリンクの利用につき、如何なる責任を負うものではありません。