すべてのプロダクト
Search
ドキュメントセンター

Alibaba Cloud Service Mesh:Envoy プロキシ用の Go で Wasm プラグインを作成する

最終更新日:Jan 13, 2025

WebAssembly for Proxies は、開発者が WebAssembly(Wasm)を使用してポータブルプラグインを作成できるようにする新しい仕様です。これらのプラグインは、さまざまなプロキシサーバーで実行できます。Service Mesh(ASM)は、WebAssembly for Proxies 仕様をサポートしています。このトピックでは、ASM の Envoy プロキシ用の Go で Wasm プラグインを作成する方法について説明します。

前提条件

背景情報

Wasm は、実行可能コード用の有望でポータブルなバイナリ形式です。コードは、メモリセーフ(ホストの場合)サンドボックス内でほぼネイティブの速度で実行されます。サンドボックスには明確に定義されたリソース制約があり、埋め込みホスト環境(ここではプロキシを指します)と通信するための明確に定義された API を提供します。

Wasm プラグインには、次の利点があります。

  • アジリティ:Envoy プロキシを再起動せずにプラグインを更新できます。したがって、リクエストは期待どおりに処理できます。

  • 信頼性と分離:プラグインはリソース制約のあるサンドボックス内にデプロイされるため、Envoy プロキシをダウンさせずにクラッシュする可能性があります。

  • セキュリティ:プラグインは、プロキシと通信するための明確に定義された API を備えたサンドボックス内にデプロイされるため、適切に制御されます。

  • 多様性:プラグインは、C++、Go、Rust など、複数のプログラミング言語で記述できます。

Wasm プラグインの詳細については、WebAssembly-in-Envoy.md および OVERVIEW.md をご参照ください。

設定例

この例では、Wasm プラグインは Go で記述されています。プラグインが記述された後、Wasm バイナリファイルが生成され、イメージにパッケージ化されます。イメージは、OCI イメージリポジトリにアップロードする必要があります。イメージがアップロードされたら、ASM で WasmPlugin リソースを設定し、指定された Envoy プロキシにプラグインを適用します。

この例では、リクエストに allow: true ヘッダーが含まれているかどうかを確認するプラグインが開発されています。含まれていない場合は、ステータスコード 403 と指定された本文が返されます。含まれている場合は、HTTPBin アプリケーションに期待どおりにアクセスできます。

ステップ 1:開発環境を準備する

Envoy プロキシ用の Go で Wasm プラグインを開発するには、最初に次のツールをインストールする必要があります。

  • Go:Go コンパイラと関連ツールは、Go プロジェクトを作成するために使用されます。詳細については、The Go Programming Language をご参照ください。

  • Docker:この例では、Docker を使用して OCI イメージをビルドおよびプッシュします。

  • TinyGo:Go は Wasm プラグインを作成するために使用されます。ただし、公式の Go コンパイラを使用して Go コードを Wasm 形式にコンパイルすることはできません。TinyGo を使用する必要があります。TinyGo のインストール方法の詳細については、クイックインストールガイド をご参照ください。

Wasm プラグインが依存する SDK の詳細については、GitHub Web サイトの proxy-wasm-go-sdk をご参照ください。Web サイトで Proxy-Wasm 用 Go SDK の完全なコードを見つけることができます。他の SDK を使用する場合は、Proxy-Wasm 用 Go SDK のコードを参照してください。

ステップ 2:プラグインコードを作成する

  1. フォルダーを作成し、次の内容を含む main.go ファイルを作成します。

    main.go ファイルを表示

    package main
    
    import (
    	"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
    	"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
    )
    
    func main() {
    	proxywasm.SetVMContext(&vmContext{})
    }
    
    type vmContext struct {
    	// すべてのメソッドを再実装する必要がないように、
    	// デフォルトの VM コンテキストをここに埋め込みます。
    	types.DefaultVMContext
    }
    
    // types.DefaultVMContext をオーバーライドします。
    func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
    	return &pluginContext{}
    }
    
    type pluginContext struct {
    	// すべてのメソッドを再実装する必要がないように、
    	// デフォルトのプラグインコンテキストをここに埋め込みます。
    	types.DefaultPluginContext
    }
    
    // types.DefaultPluginContext をオーバーライドします。
    func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
    	return types.OnPluginStartStatusOK
    }
    
    // types.DefaultPluginContext をオーバーライドします。
    func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
    	return &HeaderAuthorizationHandler{}
    }
    
    type HeaderAuthorizationHandler struct {
    	// すべてのメソッドを再実装する必要がないように、
    	// デフォルトの HTTP コンテキストをここに埋め込みます。
    	types.DefaultHttpContext
    }
    
    // types.DefaultHttpContext をオーバーライドします。
    func (ctx *HeaderAuthorizationHandler) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
    	// カナリアクラスターにランダムにルーティングします。
    
    	const AuthorizationKey = "allow"
    	value, err := proxywasm.GetHttpRequestHeader(AuthorizationKey)
    	if err != nil || value != "true" {
    		proxywasm.LogDebugf("request header: 'allow' is %v, only true can passthrough", value)
    		return ctx.DenyRequest()
    	}
    	return types.ActionContinue
    }
    
    func (ctx *HeaderAuthorizationHandler) DenyRequest() types.Action {
    	proxywasm.SendHttpResponse(403, [][2]string{{"Content-Type", "text/plain"}}, []byte("Forbidden by ASM Wasm Plugin"), -1)
    	return types.ActionPause
    }
    
  2. 作成したフォルダーで次のコマンドを実行して、SDK の依存関係を取得します。

    go mod init
    go mod tidy
  3. 次のコマンドを実行して、コードを Wasm バイナリファイルにコンパイルします。

    tinygo build -o plugin.wasm -scheduler=none -target=wasi main.go

    plugin.wasm ファイルが生成されます。このファイルは、Wasm バイナリ実行可能ファイルです。

ステップ 3:Wasm プラグインの OCI イメージを作成し、Container Registry Enterprise Edition インスタンスにプッシュする

  1. ステップ 2 で作成したフォルダーに、次の内容を含む Dockerfile ファイルを作成します。

    FROM scratch
    ADD ./plugin.wasm ./plugin.wasm
  2. 次のコマンドを実行して、イメージを作成します。

    docker build -t header-authorization:v0.0.1 .
  3. イメージリポジトリを作成します。詳細については、「Coraza Wasm プラグインを使用して ASM ゲートウェイに WAF 機能を実装する」トピックのステップ 1 のサブステップ 2.a および 2.b をご参照ください。

    この例では、名前空間は test-oci で、リポジトリ名は header-authorization です。次の図は、作成されたリポジトリを示しています。

    image

    Container Registry Enterprise Edition インスタンスにイメージをプッシュする方法の詳細については、前の図の レジストリにイメージをプッシュする をご参照ください。

ステップ 4:イングレスゲートウェイに Wasm プラグインを適用する

  1. イメージをプルするための権限を設定します。詳細については、「ステップ 2:イメージをプルするための権限を設定する」をご参照ください。

    次のコマンドを実行して、wasm-secret という名前のシークレットを作成します。

    kubectl create secret docker-registry -n istio-system wasm-secret --docker-server=${Container Registry Enterprise Edition インスタンスのドメイン名} --docker-username =${ユーザー名} --docker-password =${パスワード}
  2. 次の内容を含む asm-plugin.yaml ファイルを作成します。

    apiVersion: extensions.istio.io/v1alpha1
    kind: WasmPlugin
    metadata:
      name: header-authorization
      namespace: istio-system
    spec:
      imagePullPolicy: IfNotPresent
      imagePullSecret: wasm-secret
      selector:
        matchLabels:
          istio: ingressgateway
      url: oci://${Container Registry Enterprise Edition インスタンスのドメイン名}/test-oci/header-authorization:v0.0.1
      phase: AUTHN
  3. kubeconfig ファイルの情報に基づいて kubectl を使用して ASM インスタンスに接続します。次に、次のコマンドを実行して、Wasm プラグインを ASM インスタンスに適用します。

    kubectl apply -f wasm-plugin.yaml

ステップ 5:Wasm プラグインが有効になっていることを確認する

  1. イングレスゲートウェイが存在するデータプレーン上のクラスターの kubeconfig ファイルを使用して、次のコマンドを実行し、イングレスゲートウェイに適用されている Wasm プラグインのデバッグログ機能を有効にします。

    kubectl -n istio-system exec ${イングレスゲートウェイが存在するポッドの名前} -c istio-proxy -- curl -XPOST "localhost:15000/logging?wasm=debug"
  2. 次のコマンドを実行して、イングレスゲートウェイを介して公開されている HTTPBin アプリケーションにアクセスします。

    curl ${イングレスゲートウェイの IP アドレス}/status/418

    予期される出力:

    Forbidden by ASM Wasm Plugin
  3. イングレスゲートウェイが存在するポッドのログを表示します。

    ログの例:

    2024-03-08T08:16:46.747394Z	debug	envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1168	wasm log istio-system.header-authorization: request header: 'allow' is , only true can passthrough	thread=24
    {"bytes_received":"0","bytes_sent":"28","downstream_local_address":"xxxxxxx","downstream_remote_address":"xxxxxxxx","duration":"0","istio_policy_status":"-","method":"GET","path":"/status/418","protocol":"HTTP/1.1","request_id":"780c8493-13e4-4f97-9771-486efe30347c","requested_server_name":"-","response_code":"403","response_flags":"-","route_name":"httpbin","start_time":"2024-03-08T08:16:46.747Z","trace_id":"-","upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local","upstream_host":"-","upstream_local_address":"-","upstream_service_time":"-","upstream_response_time":"-","upstream_transport_failure_reason":"-","user_agent":"curl/8.4.0","x_forwarded_for":"xxxxxx","authority_for":"xxxxxx"}
  4. 次のコマンドを実行して、イングレスゲートウェイを介して公開されている HTTPBin アプリケーションにアクセスします。

    curl ${イングレスゲートウェイの IP アドレス}/status/418 -H "allow: true"

    予期される出力:

        -=[ teapot ]=-
    
           _...._
         .'  _ _ `.
        | ."` ^ `". _,
        \_;`"---"`|//
          |       ;/
          \_     _/
            `"""`

    出力は、HTTPBin アプリケーションに期待どおりにアクセスできることを示しています。

TinyGo のメモリリーク

TinyGo を使用してコンパイルされた Envoy プロキシ用 Wasm プラグインにはメモリリークが存在します。proxy-wasm-go-sdk コミュニティは、コンパイルの最適化に nottinygc を使用することを推奨しています。nottinygc を使用してコンパイルを最適化するには、次の手順を実行します。

  1. main.go ファイルの先頭に次の import コードを追加します。

    import _ "github.com/wasilibs/nottinygc"

    依存関係が見つからない場合は、go mod tidy コマンドを実行して、依存関係を自動的にダウンロードできます。

  2. 次のコマンドを実行して、コードをコンパイルします。

    tinygo build -o plugin.wasm -gc=custom -tags='custommalloc nottinygc_envoy'  -target=wasi -scheduler=none main.go

    前のコマンドは、-gc パラメーターと -tags パラメーターを設定します。詳細については、nottinygc をご参照ください。