Wasme Operator を利用した Wasm Filter のデプロイ

## はじめに

こちらの記事は以前に公開した ["wasme を利用した Wasm Filter 開発と Istio の Envoy にデプロイするまでの流れ"](https://qiita.com/ryysud/items/bf4139edc8165f991045) の続編となります。

前回の記事では wasme という CLI を利用することで Envoy の Wasm Filter の開発とデプロイを簡単に行えることを紹介しましたが、wasme 自体は Wasm Filter の開発やテストで利用することが想定されているもので、本番環境の Kubernetes クラスタで稼働する Envoy へのデプロイは Wasme Operator を利用して Custom Resource で宣言的に管理することが推奨されています。

## Wasme Operator について
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49924/a9683407-f9dc-1736-2344-a527d3270b01.png)
引用: [Declarative WebAssembly deployment for Istio](https://istio.io/latest/blog/2020/deploy-wasm-declarative/)

Wasme Operator は、WebAssembly Hub などのレジストリから Wasm Filter イメージをダウンロードして Node にキャッシュする `wasme-cache` と、Wasm Filter を Envoy にデプロイする `wasme-operator` の2つのコンポーネントで構成されており、各コンポーネントはデフォルトで `wasme` ネームスペースで稼働する仕様になっています。

各コンポーネントのソースコードは solo-io/wasm の [tools/wasme ディレクトリ](https://github.com/solo-io/wasm/tree/v0.0.28/tools/wasme) で管理されており、`wasme-cache` は wasme の cache サブコマンドで、`wasme-operator` は operator サブコマンドで起動されています。

現時点では Wasme Operator がサポートするデプロイ対象は Istio のみとなっており、Kubernetes クラスタに Istio（具体的には `EnvoyFilter` リソース）がインストールされていない場合は処理が失敗するので注意してください。

Wasme Operator が稼働している状態で `FilterDeployment` リソース（CRD は [こちら](https://github.com/solo-io/wasm/blob/v0.0.28/tools/wasme/cli/operator/install/wasme/crds/wasme.io_v1_crds.yaml), Spec/Status の詳細は [こちら](https://github.com/solo-io/wasm/blob/v0.0.28/tools/wasme/cli/docs/content/reference/operator/filter_deployment.md)）を Kubernetes クラスタに作成することで、自動で Istio 内の Envoy に Wasm Filter がデプロイすることが可能になります。

それでは実際に動かしてみて Wasme Operator がどのように機能するのかを見ていきます。

## 実際に動かしてみる

Wasme Operator は2020年10月29日時点での最新バージョン `v0.0.28` を利用します。

### Istio がインストールされた Kuberntes クラスタの作成

Kubernetes クラスタ 1.18.0 を作成します。

```bash
minikube start --kubernetes-version v1.18.0
```

Istio 1.5.6 をインストールします。今回デプロイする Wasm Filter が 1.5.x に互換性があるものなので少々古いバージョンとなりますが 1.5.6 を利用しています。

```bash
istioctl manifest apply --set profile=demo
```

バージョンを確認します。

```bash
$ kubectl version --short
Client Version: v1.19.0
Server Version: v1.18.0

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

### Wasme Operator のインストール

`FilterDeployment` リソースの CRD をインストールします。

```bash
kubectl apply -f https://github.com/solo-io/wasm/releases/download/v0.0.28/wasme.io_v1_crds.yaml
```

Wasme Operator をインストールします。

```bash
kubectl apply -f https://github.com/solo-io/wasm/releases/download/v0.0.28/wasme-default.yaml
```

`wasme-cache` は DaemonSet で `wasme-operator` は Deployment でデプロイされます。

```bash
$ kubectl get all -n wasme
NAME                                  READY   STATUS    RESTARTS   AGE
pod/wasme-cache-drjbd                 1/1     Running   0          50s
pod/wasme-operator-7b9c77976b-zlmv8   1/1     Running   0          50s

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

NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/wasme-operator   1/1     1            1           50s

NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/wasme-operator-7b9c77976b   1         1         1       50s
```

### サンプルアプリのデプロイ

Wasm Filter のデプロイ対象のサンプルアプリとして Bookinfo（solo-io/wasm の e2e テスト用のマニフェストを利用）をデプロイします。

```bash
kubectl create ns bookinfo
kubectl label namespace bookinfo istio-injection=enabled --overwrite
kubectl apply -n bookinfo -f https://raw.githubusercontent.com/solo-io/wasm/v0.0.28/tools/wasme/cli/test/e2e/operator/bookinfo.yaml
```

Bookinfo のアプリ内の Envoy へのリクエスト方法は以下となります。

```bash
kubectl exec -ti -n bookinfo deploy/productpage-v1 -c istio-proxy -- curl -I http://details.bookinfo:9080/details/123
```

この時点での HTTP レスポンスヘッダーは以下となります。

```bash
$ 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, 29 Oct 2020 09:40:51 GMT
content-length: 180
x-envoy-upstream-service-time: 1
x-envoy-peer-metadata: Ch0KDElOU1RBTkNFX0lQUxINGgsxNzIuMTcuMC4xMwrVAQoGTEFCRUxTEsoBKscBChAKA2FwcBIJGgdkZXRhaWxzCiEKEXBvZC10ZW1wbGF0ZS1oYXNoEgwaCjZmYzU1ZDY1YzkKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2RldGFpbHMKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjEKDwoHdmVyc2lvbhIEGgJ2MQoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKJQoETkFNRRIdGhtkZXRhaWxzLXYxLTZmYzU1ZDY1YzkteHBoemwKFwoJTkFNRVNQQUNFEgoaCGJvb2tpbmZvCk8KBU9XTkVSEkYaRGt1YmVybmV0ZXM6Ly9hcGlzL2FwcHMvdjEvbmFtZXNwYWNlcy9ib29raW5mby9kZXBsb3ltZW50cy9kZXRhaWxzLXYxCiUKD1NFUlZJQ0VfQUNDT1VOVBISGhBib29raW5mby1kZXRhaWxzCh0KDVdPUktMT0FEX05BTUUSDBoKZGV0YWlscy12MQ==
x-envoy-peer-metadata-id: sidecar~172.17.0.13~details-v1-6fc55d65c9-xphzl.bookinfo~bookinfo.svc.cluster.local
x-envoy-decorator-operation: details.bookinfo.svc.cluster.local:9080/*
```

### Wasm Filter のデプロイ

今回は前回の記事で開発した HTTP レスポンスに `hello` というヘッダーを追加する Wasm Filter である [webassemblyhub.io/ryysud/custom-header:v0.1](https://webassemblyhub.io/user/organizations/227) を Istio 内の Envoy にデプロイしていきます。

`FilterDeployment` リソースは以下のようになります。

```yaml
apiVersion: wasme.io/v1
kind: FilterDeployment
metadata:
  name: bookinfo-custom-filter
  namespace: bookinfo
spec:
  deployment:
    istio:
      kind: Deployment
  filter:
    image: webassemblyhub.io/ryysud/custom-header:v0.1
```

上記のリソースを作成します。

```bash
cat << EOF | kubectl apply -f -
apiVersion: wasme.io/v1
kind: FilterDeployment
metadata:
  name: bookinfo-custom-filter
  namespace: bookinfo
spec:
  deployment:
    istio:
      kind: Deployment
  filter:
    image: webassemblyhub.io/ryysud/custom-header:v0.1
EOF
```

リソースが作成されると `wasme-cache` による Wasm Filter イメージのキャッシュが完了した後に、`wasme-operator` による Envoy への Wasm Filter のデプロイが開始されます。

Wasm Filter のデプロイは、Node にキャッシュされたイメージを Envoy コンテナに hostPath でマウントするために Istio 仕様の Annotation である sidecar.istio.io/userVolume と sidecar.istio.io/userVolumeMount をアプリの Deployment マニフェストに追加（このタイミングでアプリの Pod がローリングアップデートする）して、Wasm Filter を Envoy で使用する設定を定義した `EnvoyFilter` リソースを子リソースとして作成するという流れになります。

以下は `FilterDeployment` リソースの子リソースとして作成された `EnvoyFilter` リソースとなります。Spec から Wasm Filter が Envoy に設定されることがわかります。

```bash
$ kubectl tree -n bookinfo filterdeployment bookinfo-custom-filter
NAMESPACE  NAME                                                          READY  REASON  AGE
bookinfo   FilterDeployment/bookinfo-custom-filter                       -              5m54s
bookinfo   ├─EnvoyFilter/details-v1-bookinfo-custom-filter.bookinfo      -              5m24s
bookinfo   └─EnvoyFilter/productpage-v1-bookinfo-custom-filter.bookinfo  -              5m24s

$ kubectl get envoyfilters.networking.istio.io -n bookinfo
NAME                                             AGE
details-v1-bookinfo-custom-filter.bookinfo       3m12s
productpage-v1-bookinfo-custom-filter.bookinfo   3m12s

$ kubectl get envoyfilters.networking.istio.io -n bookinfo details-v1-bookinfo-custom-filter.bookinfo -o yaml | kubectl neat
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: details-v1-bookinfo-custom-filter.bookinfo
  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: bookinfo-custom-filter.bookinfo
            rootId: add_header
            vmConfig:
              code:
                local:
                  filename: /var/local/lib/wasme-cache/a515a5d244b021c753f2e36c744e03a109cff6f5988e34714dbe725c904fa917
              runtime: envoy.wasm.runtime.v8
              vmId: bookinfo-custom-filter.bookinfo
        name: envoy.filters.http.wasm
  workloadSelector:
    labels:
      app: details
      version: v1
```

また、Wasm Filter のデプロイ状況は作成した `FilterDeployment` リソースの Status からも確認できます。

```bash
# 余計な情報は削除しています
$ kubectl get filterdeployments.wasme.io -n bookinfo -o yaml bookinfo-custom-filter
apiVersion: wasme.io/v1
kind: FilterDeployment
metadata:
  name: bookinfo-custom-filter
  namespace: bookinfo
spec:
  deployment:
    istio:
      kind: Deployment
  filter:
    image: webassemblyhub.io/ryysud/custom-header:v0.1
status:
  observedGeneration: "1"
  workloads:
    details-v1:
      state: Succeeded
    productpage-v1:
      state: Succeeded
```

最後に Wasm Filter がデプロイされたかを確認します。

```bash
$ 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, 29 Oct 2020 09:55:32 GMT
content-length: 180
x-envoy-upstream-service-time: 1
hello: world! # <----- Wasm Filter によって `hello` ヘッダーが追加されている
x-envoy-peer-metadata: Ch0KDElOU1RBTkNFX0lQUxINGgsxNzIuMTcuMC4xNwrVAQoGTEFCRUxTEsoBKscBChAKA2FwcBIJGgdkZXRhaWxzCiEKEXBvZC10ZW1wbGF0ZS1oYXNoEgwaCjdiYjg0Njk5YjQKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2RldGFpbHMKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjEKDwoHdmVyc2lvbhIEGgJ2MQoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKJQoETkFNRRIdGhtkZXRhaWxzLXYxLTdiYjg0Njk5YjQtNzdqN2YKFwoJTkFNRVNQQUNFEgoaCGJvb2tpbmZvCk8KBU9XTkVSEkYaRGt1YmVybmV0ZXM6Ly9hcGlzL2FwcHMvdjEvbmFtZXNwYWNlcy9ib29raW5mby9kZXBsb3ltZW50cy9kZXRhaWxzLXYxCiUKD1NFUlZJQ0VfQUNDT1VOVBISGhBib29raW5mby1kZXRhaWxzCh0KDVdPUktMT0FEX05BTUUSDBoKZGV0YWlscy12MQ==
x-envoy-peer-metadata-id: sidecar~172.17.0.17~details-v1-7bb84699b4-77j7f.bookinfo~bookinfo.svc.cluster.local
x-envoy-decorator-operation: details.bookinfo.svc.cluster.local:9080/*
```

レスポンスを見てみると `hello` ヘッダーが追加されていることから、Wasm Filter が正常にデプロイできたことが確認できました。

## さいごに

今回は Wasme Operator を利用して Kubernetes クラスタへの Wasm Filter のデプロイを Custom Resource で宣言的に管理する方法を紹介しました。Wasm Filter の本番運用を検討している場合には Wasme Operator の導入を検討すると良いでしょう。

## 参考資料

- [Running Filters in Production - Solo.io Docs](https://docs.solo.io/web-assembly-hub/latest/tutorial_code/wasme_operator/)
- [WebAssembly Hub と WASM OCI Image Specification について - Speaker Deck](https://speakerdeck.com/ryysud/intro-webassembly-hub-and-wasm-oci-image-specification)