9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

wasme を利用した Wasm Filter 開発と Istio の Envoy にデプロイするまでの流れ

Last updated at Posted at 2020-06-18

はじめに

先日公開した「Envoy での WebAssembly サポートと WebAssembly Hub, WASM OCI Image Specification について」では、Envoy で WebAssembly がサポートされるまでの背景と WebAssembly Hub、WASM OCI Image Specification を紹介しました。

今回は上記の記事で軽く紹介した wasme を利用して、以下のステップを踏みながら自ら開発した Wasm Filter を Istio により Pod 内に展開された Envoy にデプロイするまでの流れを紹介していきたいと思います。

  1. Wasm Filter の開発
  2. Wasm OCI Image のビルド(以下 Wasm イメージ)
  3. Wasm イメージを WebAssembly Hub で公開
  4. Wasm Filter を Istio の Envoy にデプロイ

wasme の利用方法

今回は 2020年6月18日 時点での最新バージョン v0.0.20 を利用していきます。

$ wasme --version
wasme version 0.0.20

v0.0.20 でサポートされているサブコマンドは以下となります。

サブコマンド 概要
init Wasm Filter プロジェクトの初期化
build Wasm イメージのビルド
tag Wasm イメージのタグ付け
list Wasm イメージの一覧表示
login Wasm イメージレジストリへのログイン
push Wasm イメージのプッシュ
pull Wasm イメージのプル
deploy Wasm Filter を Envoy にデプロイ
undeploy Wasm Filter を Envoy から削除
help ヘルプ

wasme は Wasm Filter を Docker コンテナと同じくらい簡単に扱うことを目的に開発されたものなので、Docker の CLI に慣れ親しんでいる人には違和感がないコマンド群だと思います。それでは、上記のコマンドを利用しながら進めていきたいと思います。

wasme のインストール

以下のコマンドで wasme をインストールします。

curl -sL https://run.solo.io/wasme/install | sh
export PATH=$HOME/.wasme/bin:$PATH

Wasm Filter プロジェクトの初期化

以下のコマンドで Wasm Filter プロジェクトの初期化を行います。

wasme init <任意のディレクトリ>

コマンドを実行するとインタラクティブなプロンプトが起動されるので、開発に利用する言語(C++ もしくは AssemblyScript)と Wasm Filter を使用するプラットフォーム(Gloo, Istio 互換のみ)を選択します。

$ wasme init custom-filter
Use the arrow keys to navigate: ↓ ↑ → ← 
? What language do you wish to use for the filter: 
    cpp
  ▸ assemblyscript
Use the arrow keys to navigate: ↓ ↑ → ← 
? With which platforms do you wish to use the filter?: 
  ▸ gloo:1.3.x, istio:1.5.x

✔ assemblyscript
✔ gloo:1.3.x, istio:1.5.x
INFO[0070] extracting 1757 bytes to /Users/ryysud/Desktop/custom-filter

今回は Typescript のサブセットである AssemblyScript ベースで Gloo 1.3.x, Istio 1.5.x に互換性を持つ Wasm Filter を開発していきます。現在は選択できる言語とプラットフォームに限りがありますが、Wasm Filter が盛り上がっていくにつれて充実していくのではないかなと思います。

初期化によって Wasm Filter の開発に必要なファイルが生成されます。

$ cd custom-filter
$ tree .
.
├── assembly
│   ├── index.ts
│   └── tsconfig.json
├── package-lock.json
├── package.json
└── runtime-config.json

assembly/index.ts が Wasm Filter の実装となっているので、要件にあったものを開発する場合にはこのファイルをベースに処理を追加していく形となります。

以下のように、生成されたファイルが既に HTTP レスポンスに hello というヘッダーを追加する Wasm Filter になっているので、今回はこれをそのまま使います。

assembly/index.ts
export * from "@solo-io/proxy-runtime/proxy";
import { RootContext, Context, RootContextHelper, ContextHelper, registerRootContext, FilterHeadersStatusValues, stream_context } from "@solo-io/proxy-runtime";

class AddHeaderRoot extends RootContext {
  configuration : string;

  onConfigure(): bool {
    let conf_buffer = super.getConfiguration();
    let result = String.UTF8.decode(conf_buffer);
    this.configuration = result;
    return true;
  }

  createContext(): Context {
    return ContextHelper.wrap(new AddHeader(this));
  }
}

class AddHeader extends Context {
  root_context : AddHeaderRoot;
  constructor(root_context:AddHeaderRoot){
    super();
    this.root_context = root_context;
  }
  onResponseHeaders(a: u32): FilterHeadersStatusValues {
    const root_context = this.root_context;
    if (root_context.configuration == "") {
      stream_context.headers.response.add("hello", "world!");
    } else {
      stream_context.headers.response.add("hello", root_context.configuration);
    }
    return FilterHeadersStatusValues.Continue;
  }
}

registerRootContext(() => { return RootContextHelper.wrap(new AddHeaderRoot()); }, "add_header");

Wasm イメージのビルド

以下のコマンドで Wasm Filter プロジェクトから Wasm イメージのビルドを行います。

$ wasme build assemblyscript -t custom-header:dev .
...
INFO[0012] adding image to cache...                      filter file=/tmp/wasme784972573/filter.wasm tag="custom-header:dev"
INFO[0012] tagged image                                  digest="sha256:a515a5d244b021c753f2e36c744e03a109cff6f5988e34714dbe725c904fa917" image="docker.io/library/custom-header:dev"

Wasm イメージのビルドには builder という wasme リポジトリに含まれている Docker イメージで起動した Docker コンテナが利用されています。興味がある方は --verbose オプションを付与して実行すると良いかと思います。

以下のコマンドで Wasm イメージが正常にビルドされたことがわかります。

$ wasme list
NAME                            TAG SIZE    SHA      UPDATED
docker.io/library/custom-header dev 12.6 kB a515a5d2 18 Jun 20 00:42 JST

Wasm イメージの実体は ~/.wasme/store に保存されるので、Wasm イメージがどのようなファイルで構成されているかが気になる方は WASM OCI Image Specification と併せて確認してみると理解が深まるかと思います。

$ ll ~/.wasme/store
total 0
drwxr-xr-x  6 ryysud  staff   192B Jun 18 00:42 c9259e4c7263877b1c2bdb729a0c235f

$ ll ~/.wasme/store/c9259e4c7263877b1c2bdb729a0c235f
total 56
-rw-r--r--  1 ryysud  staff   224B Jun 18 00:49 descriptor.json
-rw-r--r--  1 ryysud  staff    12K Jun 18 00:49 filter.wasm
-rw-r--r--  1 ryysud  staff    35B Jun 18 00:49 image_ref
-rw-r--r--  1 ryysud  staff   166B Jun 18 00:49 runtime-config.json

Wasm イメージを WebAssembly Hub で公開

OCI イメージ互換なレジストリであれば何でも良いのですが、今回は WebAssembly Hub で Wasm イメージを公開していきます。事前に WebAssembly Hub にサインアップしたあとで、以下のようにリモートレジストリへのログインを行います。

$ wasme login
Enter username: ryysud
Enter password: ****************************
INFO[0017] Successfully logged in as ryysud (Ryuma Yoshida) 
INFO[0017] stored credentials in /Users/ryysud/.wasme/credentials.json

以下のコマンドで先述のステップでビルドした Wasm イメージにタグ付けして WebAssembly Hub にプッシュします。

$ wasme tag custom-header:dev webassemblyhub.io/ryysud/custom-header:v0.1
INFO[0000] tagged image                                  digest="sha256:a515a5d244b021c753f2e36c744e03a109cff6f5988e34714dbe725c904fa917" image="docker.io/library/custom-header:dev"

$ wasme list
NAME                                   TAG  SIZE    SHA      UPDATED
docker.io/library/custom-header        dev  12.6 kB a515a5d2 18 Jun 20 00:42 JST
webassemblyhub.io/ryysud/custom-header v0.1 12.6 kB a515a5d2 18 Jun 20 01:00 JST

$ wasme push webassemblyhub.io/ryysud/custom-header:v0.1
INFO[0000] Pushing image webassemblyhub.io/ryysud/custom-header:v0.1 
INFO[0007] Pushed webassemblyhub.io/ryysud/custom-header:v0.1 
INFO[0007] Digest: sha256:d3e989365d75489e969439f4dc56df517cade0edbf2cda6232536564df910743

WebAssembly Hub にアクセスしてみると正常に Wasm イメージをプッシュできたことがわかります。
スクリーンショット 2020-06-18 1.02.50.png
https://webassemblyhub.io/user/organizations/227

Web からでなくても、以下のコマンドで wasme から WebAssembly Hub 上の Wasm イメージを検索することも可能です。

$ wasme list --search ryysud/custom-header
NAME                                   TAG  SIZE    SHA      UPDATED
webassemblyhub.io/ryysud/custom-header v0.1 13.8 kB d3e98936 17 Jun 20 16:01 UTC

Wasm Filter を Istio の Envoy にデプロイ

まずは手元に Istio がインストールされた Kubernetes クラスタを作成します。

# Kubernetes クラスタの作成
kind create cluster --name istio-dev --image kindest/node:v1.18.0

# Istio のインストール
istioctl manifest apply --set profile=demo

今回開発した Wasm Filter は Istio 1.5.x に互換性があるものなので 1.5.6 を利用していきます。

$ istioctl version
client version: 1.5.6
control plane version: 1.5.6
data plane version: 1.5.6 (3 proxies)

サンプルアプリとして Bookinfo をデプロイします。

kubectl create ns bookinfo
kubectl label namespace bookinfo istio-injection=enabled --overwrite
kubectl apply -n bookinfo -f https://raw.githubusercontent.com/istio/istio/1.5.6/samples/bookinfo/platform/kube/bookinfo.yaml

以下のコマンドで Bookinfo が正しくデプロイできたか確認します。この時点ではレスポンスに hello ヘッダーは含まれていないことを覚えておいてください。

$ kubectl exec -ti -n bookinfo deploy/productpage-v1 -c istio-proxy -- curl -I http://details.bookinfo:9080/details/123
HTTP/1.1 200 OK
content-type: application/json
server: istio-envoy
date: Thu, 18 Jun 2020 05:01:33 GMT
content-length: 180
x-envoy-upstream-service-time: 2
x-envoy-peer-metadata: CjcKDElOU1RBTkNFX0lQUxInGiUxMC4yNDQuMC4xMSxmZTgwOjo4OGZiOjhhZmY6ZmU3ZDo1N2Y3CtUBCgZMQUJFTFMSygEqxwEKEAoDYXBwEgkaB2RldGFpbHMKIQoRcG9kLXRlbXBsYXRlLWhhc2gSDBoKNmM5ZjhiY2JjYgokChlzZWN1cml0eS5pc3Rpby5pby90bHNNb2RlEgcaBWlzdGlvCiwKH3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLW5hbWUSCRoHZGV0YWlscworCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIEGgJ2MQoPCgd2ZXJzaW9uEgQaAnYxChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAolCgROQU1FEh0aG2RldGFpbHMtdjEtNmM5ZjhiY2JjYi1qOGs1OQoXCglOQU1FU1BBQ0USChoIYm9va2luZm8KTwoFT1dORVISRhpEa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2Jvb2tpbmZvL2RlcGxveW1lbnRzL2RldGFpbHMtdjEKJQoPU0VSVklDRV9BQ0NPVU5UEhIaEGJvb2tpbmZvLWRldGFpbHMKHQoNV09SS0xPQURfTkFNRRIMGgpkZXRhaWxzLXYx
x-envoy-peer-metadata-id: sidecar~10.244.0.11~details-v1-6c9f8bcbcb-j8k59.bookinfo~bookinfo.svc.cluster.local
x-envoy-decorator-operation: details.bookinfo.svc.cluster.local:9080/*

ここまでで Istio のセットアップは完了したので、以下のコマンドで Istio の Envoy に Wasm Filter をデプロイします。ちなみに v0.0.20 時点でサポートされているデプロイ対象のプラットフォームは Istio と Gloo のみです。

wasme deploy istio webassemblyhub.io/ryysud/custom-header:v0.1 \
    --id=custom-header \
    --namespace bookinfo

実行結果は以下となります。

$ wasme deploy istio webassemblyhub.io/ryysud/custom-header:v0.1 \
    --id=custom-header \
    --namespace bookinfo
INFO[0000] cache namespace created                       cache=wasme-cache.wasme image="quay.io/solo-io/wasme:0.0.20"
INFO[0000] cache configmap created                       cache=wasme-cache.wasme image="quay.io/solo-io/wasme:0.0.20"
INFO[0000] cache service account created                 cache=wasme-cache.wasme image="quay.io/solo-io/wasme:0.0.20"
INFO[0000] cache role created                            cache=wasme-cache.wasme image="quay.io/solo-io/wasme:0.0.20"
INFO[0000] cache rolebinding created                     cache=wasme-cache.wasme image="quay.io/solo-io/wasme:0.0.20"
INFO[0000] cache daemonset created                       cache=wasme-cache.wasme image="quay.io/solo-io/wasme:0.0.20"
INFO[0004] added image to cache config...                cache="{wasme-cache wasme}" image="webassemblyhub.io/ryysud/custom-header:v0.1"
INFO[0004] waiting for event with timeout 1m0s
INFO[0005] cleaning up cache events for image webassemblyhub.io/ryysud/custom-header:v0.1
INFO[0005] updated workload sidecar annotations          filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=details-v1
INFO[0006] created Istio EnvoyFilter resource            envoy_filter_resource=details-v1-custom-header.bookinfo filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=details-v1
INFO[0006] updated workload sidecar annotations          filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=productpage-v1
INFO[0006] created Istio EnvoyFilter resource            envoy_filter_resource=productpage-v1-custom-header.bookinfo filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=productpage-v1
INFO[0006] updated workload sidecar annotations          filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=ratings-v1
INFO[0008] created Istio EnvoyFilter resource            envoy_filter_resource=ratings-v1-custom-header.bookinfo filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=ratings-v1
INFO[0008] updated workload sidecar annotations          filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=reviews-v1
INFO[0008] created Istio EnvoyFilter resource            envoy_filter_resource=reviews-v1-custom-header.bookinfo filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=reviews-v1
INFO[0008] updated workload sidecar annotations          filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=reviews-v2
INFO[0009] created Istio EnvoyFilter resource            envoy_filter_resource=reviews-v2-custom-header.bookinfo filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=reviews-v2
INFO[0009] updated workload sidecar annotations          filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=reviews-v3
INFO[0010] created Istio EnvoyFilter resource            envoy_filter_resource=reviews-v3-custom-header.bookinfo filter="id:\"custom-header\" image:\"webassemblyhub.io/ryysud/custom-header:v0.1\" rootID:\"add_header\" " workload=reviews-v3

実行結果のログからわかるように、まず初めに wasme ネームスペースに Wasm Filter をキャッシュするための DaemonSet などがデプロイされていることがわかります。DaemonSet として実行されるコンテナは wasme cache container と呼ばれるもので、cache container でプルとキャッシュされた Wasm イメージを hostPath で Pod 内の Envoy が利用する形となります。

以下は wasme ネームスペース内のリソースとなります。

$ kubectl get all -n wasme
NAME                    READY   STATUS    RESTARTS   AGE
pod/wasme-cache-qnp89   1/1     Running   0          51s

NAME                         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/wasme-cache   1         1         1       1            1           <none>          52s

更に実行結果のログから、Wasm Filter の設定を変換して Istio の EnvoyFilter リソースとして作成することで Istio の Envoy に Wasm Filter をデプロイしていることがわかります。

以下は実際に作成された EnvoyFilter リソースの例となります。リソースから envoy.wasm.runtime.v8 ランタイムで /var/local/lib/wasme-cache/a515a5d244b021c753f2e36c744e03a109cff6f5988e34714dbe725c904fa917 という Wasm Filter が Envoy に設定されることがわかるかと思います。

$ kubectl get envoyfilters.networking.istio.io -n bookinfo
NAME                           AGE
details-v1-custom-header       63s
productpage-v1-custom-header   63s
ratings-v1-custom-header       63s
reviews-v1-custom-header       61s
reviews-v2-custom-header       61s
reviews-v3-custom-header       60s

# 余計な情報は削除しています
$ kubectl get envoyfilters.networking.istio.io -n bookinfo productpage-v1-custom-header -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: productpage-v1-custom-header
  namespace: bookinfo
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        filterChain:
          filter:
            name: envoy.http_connection_manager
            subFilter:
              name: envoy.router
    patch:
      operation: INSERT_BEFORE
      value:
        config:
          config:
            name: custom-header
            rootId: add_header
            vmConfig:
              code:
                local:
                  filename: /var/local/lib/wasme-cache/a515a5d244b021c753f2e36c744e03a109cff6f5988e34714dbe725c904fa917
              runtime: envoy.wasm.runtime.v8
              vmId: custom-header
        name: envoy.filters.http.wasm
  workloadSelector:
    labels:
      app: productpage
      version: v1

Wasm Filter がデプロイできたので動作確認を行います。

$ kubectl exec -ti -n bookinfo deploy/productpage-v1 -c istio-proxy -- curl -I http://details.bookinfo:9080/details/123
HTTP/1.1 200 OK
content-type: application/json
server: istio-envoy
date: Thu, 18 Jun 2020 05:04:18 GMT
content-length: 180
x-envoy-upstream-service-time: 1
hello: world! # <----- Wasm Filter によって `hello` ヘッダーが追加されている
x-envoy-peer-metadata: CjcKDElOU1RBTkNFX0lQUxInGiUxMC4yNDQuMC4xOSxmZTgwOjpmY2FhOjU4ZmY6ZmUwZjo1ZDU2CtQBCgZMQUJFTFMSyQEqxgEKEAoDYXBwEgkaB2RldGFpbHMKIAoRcG9kLXRlbXBsYXRlLWhhc2gSCxoJYjhiNmM2OTdiCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KLAofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIJGgdkZXRhaWxzCisKI3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLXJldmlzaW9uEgQaAnYxCg8KB3ZlcnNpb24SBBoCdjEKGgoHTUVTSF9JRBIPGg1jbHVzdGVyLmxvY2FsCiQKBE5BTUUSHBoaZGV0YWlscy12MS1iOGI2YzY5N2ItcWxsdm0KFwoJTkFNRVNQQUNFEgoaCGJvb2tpbmZvCk8KBU9XTkVSEkYaRGt1YmVybmV0ZXM6Ly9hcGlzL2FwcHMvdjEvbmFtZXNwYWNlcy9ib29raW5mby9kZXBsb3ltZW50cy9kZXRhaWxzLXYxCiUKD1NFUlZJQ0VfQUNDT1VOVBISGhBib29raW5mby1kZXRhaWxzCh0KDVdPUktMT0FEX05BTUUSDBoKZGV0YWlscy12MQ==
x-envoy-peer-metadata-id: sidecar~10.244.0.19~details-v1-b8b6c697b-qllvm.bookinfo~bookinfo.svc.cluster.local
x-envoy-decorator-operation: details.bookinfo.svc.cluster.local:9080/*

レスポンスを見てみると hello ヘッダーが追加されていることから、Wasm Filter が正常にデプロイできたことが確認できました。以上が、自ら開発した Wasm Filter を Istio により Pod 内に展開された Envoy にデプロイするまでの流れとなります。

おまけ: Wasm Filter を手軽に試す方法

今回は手元で動作確認を行わずに Istio の Envoy に対して Wasm Filter をデプロイしましたが、wasme deploy envoy <Wasmイメージ> を利用することでローカルに Docker コンテナとして Wasm Filter を適用した Envoy を起動して動作確認を行えるので、開発時に手軽に挙動を確認したい場合はこのコマンドを利用するのが良いかと思います。

$ wasme pull webassemblyhub.io/ryysud/custom-header:v0.1
INFO[0000] Pulling image webassemblyhub.io/ryysud/custom-header:v0.1 
INFO[0003] Image: webassemblyhub.io/ryysud/custom-header:v0.1 
INFO[0003] Digest: sha256:a515a5d244b021c753f2e36c744e03a109cff6f5988e34714dbe725c904fa917 

$ wasme deploy envoy webassemblyhub.io/ryysud/custom-header:v0.1
INFO[0000] mounting filter file at /Users/ryysud/.wasme/store/2006ead26dd93ad64b2a4cf14278550d/filter.wasm 
INFO[0000] running envoy-in-docker                       container_name=add_header envoy_image="quay.io/solo-io/gloo-envoy-wasm-wrapper:1.3.5" filter_image="webassemblyhub.io/ryysud/custom-header:v0.1"

$ docker ps
CONTAINER ID        IMAGE                                           COMMAND                  CREATED              STATUS              PORTS                                              NAMES
6e6a673f8fa7        quay.io/solo-io/gloo-envoy-wasm-wrapper:1.3.5   "envoy --disable-hot…"   About a minute ago   Up About a minute   0.0.0.0:8080->8080/tcp, 0.0.0.0:19000->19000/tcp   add_header

$ curl -s -I localhost:8080 | grep hello
hello: world!

さいごに

今回は wasme を利用して、自ら開発した Wasm Filter を Istio により Pod 内に展開された Envoy にデプロイするまでの流れを紹介しました。非常に簡単に Wasm Filter のデプロイを行えるので、興味がある方は早速触ってみるのも良いのかと思います。

次回は Wasme Operator(WebAssembly Hub Operator)を使用して CRD ベースで Istio の Envoy に Wasm Filter をデプロイする方法を調査していきたいと思います。

参考資料

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?