WASM FilterによるIstio Proxy (Envoy) の拡張で押さえておきたい基礎知識

これは[ZOZO #1 Advent Calendar 2021](https://qiita.com/advent-calendar/2021/zozo) 18日目の記事。

本記事では、WebAssembly (以下、WASM)を活用したIstio Proxy (Envoy)の拡張で押さえておきたい基礎知識を解説する。途中、サンプルWASM Filterのデプロイを通じて、関連する仕組みやカスタムリソースについても解説する。
なお、基礎知識といっても、著者がWASM Filterのクイックスタート記事などを参考に実際にIstioで動かしてみたときに調べたことを独断と偏見でまとめた記事に過ぎないので、体系的に整理されていなこと予めご理解いただければと思う。

# WASM Filterを理解する


## Filterによる機能拡張の選択肢

Filterによる機能拡張には複数の選択肢がある。WASM Filterもその中の１つ。

|方法|内容|
|:--|:--|
|ビルトインFiltersの利用|WASM Filterで拡張を考えている人には、まず[ビルトインなFilters](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/http_filters)にどういうものがあるのか理解しておくことをオススメする。すでにある機能は利用し、車輪の再発明を避けよう。|
|Lua FilterでLuaスクリプト実行|ビルトインFiltersの中に[HTTP Lua Filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter)というFilter設定のYAMLファイルにLuaスクリプトをインラインで記述可能なFilterがある。スクリプトの内容がシンプルな場合はこのアプローチは有効かもしれない（See 下記sample-lua-filter.yaml)　|
|C++ FilterのEnvoyへの組み込み|FilterをEnvoyのネイティブ言語であるC++で書いて、Envoyの一部としてビルドしパッケージ化するアプローチ。ネイティブなので高速であるものの、Envoyを再コンパイルし、独自バージョンをメンテナンスしていく必要があるので運用管理コストが高くなる|
|WASM Filter　（記事の本題）| 独立したWASMモジュールをEnvoyがダイナミックにロードする方式。本記事で詳細を後述 |


```YAML:sample-lua-filter.yaml
name: envoy.filters.http.lua
typed_config:
  "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
  inline_code: |
    -- Called on the request path.
    function envoy_on_request(request_handle)
      -- Do something.
    end
    -- Called on the response path.
    function envoy_on_response(response_handle)
      -- Do something.
    end
# sample code from:
# https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter
```

## WASMとは？

WebAssembly(WASM)とは、Webブラウザでプログラムを、高速、高効率、そしてポータブルに実行することを目指し策定されたバイナリフォーマットである。開発者は好みの言語でコーディングし、それをWASMモジュールにコンパイルして実行する。 また、WASM はVirtual Machine(以下VM)と呼ばれるサンドボックス化されたメモリーセーフな実行環境で動作するように設計されている。

https://developer.mozilla.org/ja/docs/WebAssembly/Concepts

WASMは、もともとWebブラウザで実行するよう設計されたものであるが、VMを他のホストアプリケーションに埋め込んで実行することもできる。WASM Filterは、このVMをEnvoyに埋め込んで実行する。Envoyは、V8 Wasm Runtime VM（以下、V8）を組み込んでいる。 V8は、C++で記述された高性能のJavaScriptおよびWASMエンジンであり、Chrome、Node.js、その他のプラットフォームで使用されているという背景がある。

https://istio.io/latest/docs/concepts/wasm/

## なぜWASM Filterなのか?

WASM Filterを選択する理由について、以下のような事が挙げらる。

- アジリティの高さ - WASM Filterは、停止や再コンパイルすることなく、実行中のEnvoyプロセスに動的にロードが可能
- 運用管理性 - 独立したWasmモジュールをダイナミックにロードする仕組みであるため機能拡張のためにEnvoyのコードベースを変更する必要がない
- 言語選択肢の豊富さ - C++、Rust、Go言語などメジャーなプログラミング言語をWASMにコンパイルできる。開発者はこれらプログラミング言語を使用してFilterの実装が可能

一方、気になるWASMのパフォーマンスについては、[Istio公式ページ](https://istio.io/latest/docs/reference/config/proxy_extensions/wasm_telemetry/)に以下の記述があり、改善の余地がある状況のようだ。これは実際に負荷試験をして確かめてみたいところ。

>There are several known limitations with Wasm-based telemetry generation:
>
>Proxy CPU usage will spike during Wasm module loading time (i.e. when the aforementioned configuration is >applied). Increasing proxy CPU resource limit will help to speed up loading.
>Proxy baseline resource usage increases. Based on preliminary performance testing result, comparing to the >default installation, running Wasm-based telemetry will cost 30%~50% more CPU and double the memory usage.
>The performance will be continuously improved in the following releases.

## Filter Chainについて

本記事の後半では実際にWASM Filterをデプロイして、Kubernetes/Istio上のサンプルアプリのサイドカーであるIstio Proxy(Envoy)のHTTP Filter Chainに組み込んでみる。ここでは、WASM FilterがEnvoyのリクエスト処理メカニズムにおいてどういう位置づけにあるのか理解するためEnvoyのFilter Chainについて簡単に解説する。

そもそもFilterとは、リクエスト処理パイプライン内のモジュールのことで、Filter Chainとそのモジュール（Filter）による一連の処理の繋がりを意味する。Unixパイプで複数の処理をパイプでつなげてまとまった処理を行うが、それで言うところの１つの処理がFilterで、パイプでつながった一連の処理がFilter Chainにあたる。

Envoyでは、リクエスト処理を行う部分は次の２つサブシステムに分けられる。

- Listener: ダウンストリームからのリクエストを処理するサブシステム。ダウンストリームからのリクエストのライフサイクル管理、クライアントへの応答パス、ダウンストリームのHTTP/2 codecなどはここで行う。
- Cluster: エンドポイントへのアップストリーム接続の選択と構成を担当するサブシステム。クラスターとエンドポイントの正常性、負荷分散、および接続プールなどの管理はここで行う。
　

> ＜Envoyのイベントドリブンのワーカースレッドモデルについて＞
> Envoyは[イベントドリブンのスレッドモデル](https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310)を採用しており、メインスレッドは、サーバーのライフサイクル、構成、統計情報などを管理し、それ以外のワーカースレッドでリクエストを処理を行う。すべてのスレッドは[libevent](https://libevent.org/)によるイベントループで運用され、あるダウンストリームからのTCP接続につき1つのワーカースレッドで処理される。


```:Listener-FilterChain-Clusterの関係
[Downstream]　-> [Listener] -> [Filters] -> [Cluster] -> [Upstream]
```

```:Filtersの構成とFilterChain（FC）
[Filters] = [Listener FC] -> [Network FC] -> [HTTP FC]

各[Filter Chain] = [Filter] -> [Filter] -> [Filter] -> [Filter] -> ...
```

全体の流れをざっくり説明すると、ダウンストリームからのリクエストは、上図のようにListenerで受付られ、複数のFiltersによる処理を経て、対応するClusterにマッピングされる仕組みになっている。

Filtersには大きく次の3種類があり、上図のようにListener Filter Chain、 Network Filter Chain、 HTTP Filter Chainの順番でリクエスト処理が行われる。

- Listener Filter: L4接続におけるメタデータを処理する
- Network Filter: L4接続におけるrawデータを処理する。Network Filter Chainにおける最後のNetwork FilterはHTTP connection manager (HCM)と呼ばれ、HCPはHTTP/2 codecや、HTTP Filter Chainの管理を行う
- HTTP Filter: L7接続におけるHTTPリクエスト＆レスポンスを処理する。

Envoyのリクエスト処理については、「[Envoy Life of a Request](https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request)」に詳しく書かれているので興味のある人は熟読いただければと思う。

https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request

## Proxy-Wasm

[Proxy-Wasm](https://github.com/proxy-wasm/spec)とは、WASMモジュールとホスト環境（Envoy/Istio Proxy、etc）の間のApplication Binary Interface（ABI）を定める仕様で、これによりホスト環境に依存せず、共通の仕組みでWASMモジュールによりホスト環境の拡張を可能になる。
Envoyはこれをリファレンス実装しており、また先述のとおりEnvoyはWASM Runtime VMであるV8を内部に組み込んでいる。WASMモジュールがEnvoyにロードされると、WASM Runtime（V8）を使用してインスタンス化され、イベント発生毎にProxy-Wasmインターフェースを通じてEnvoyと連携する。

![Proxy-Wasm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103420/55101b6b-5c2e-e93a-e2b4-8a1e65749239.png)
（イメージ参照元: https://github.com/proxy-wasm/spec/blob/master/docs/WebAssembly-in-Envoy.md)

Proxy-Wasmについては下記資料がとてもわかりやすく参考になるかと思う。

https://mathetake.github.io/assets/pdfs/proxy-wasm-and-its-landscape.pdf

## WASM OCIイメージ仕様

WASMモジュールをOCIイメージとしてパッケージ化して配布するための仕様が定められている。仕様には次の2つのバリエーションがある。

- [WASM Artifact Image Specification v0.0.0](https://github.com/solo-io/wasm/blob/master/spec/spec.md)　(以下、OCIイメージ)
- [Wasm Image Specification v0.0.0](https://github.com/solo-io/wasm/blob/master/spec/spec-compat.md)（以下、OCI互換イメージ）

それぞれの仕様でイメージのビルドとpushに利用できるツールに違いがあるので注意が必要となる。

- OCIイメージ: [wasme](https://github.com/solo-io/wasm/tree/master/tools)
- OCI互換イメージ:  [buildah](https://buildah.io/)や [docker](https://docs.docker.com/engine/reference/commandline/docker/) 

https://github.com/solo-io/wasm/tree/master/spec

なお、本記事後半でIstioへのWASMモジュールのデプロイ方法を紹介するが、Istioは両方の仕様をサポートしておりいづれのイメージ仕様でもデプロイが可能となっている。また、デプロイ方法の紹介では、AssemblyScriptで書かれたスクリプトをWASMモジュール（バイナリファイル）にコンパイルするが、そのイメージのビルドとPushにはdocker CLIを使用する。つまりOCI互換なイメージの利用ということになる。

# WASM FilterをIstioにデプロイしてみる

それでは実際に、サンプルWASM FilterをKuberenetes（以下, k8s）上のIstioにデプロイしてみて、動きを確かめてみる。合わせて関連する仕組みやカスタムリソースについても解説していく。 

なお、サンプル実行で使用する環境情報は以下の通り。

```
local OS: macOS 11
tools: npm 8.0.0, docker-ce 20.10.8, kubectl v1.19.13, kind v0.11.1
k8s: v1.19.11 (KIND)
istio: 1.12.1
```

## 事前準備

デプロイ先となるk8sクラスタとIstioの構築を行う。 また、そのクラスタにWASM Filterのデプロイターゲットとなるサンプルアプリのapplyも行う。

### KINDでKubernetesクラスタ作成

k8sクラスタを[kind](https://github.com/kubernetes-sigs/kind) (Kubernetes in Docker)でローカルに構築する。
まずは、次のようなcontrol planeノード x 1、 workerノード x 1のk8sクラスタのためのkindクラスタ構成ファイル(cluster.yaml)を作成する。

```bash
cat << EOF | > cluster.yaml 
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4 
nodes:
- role: control-plane
- role: worker
EOF
```

次に、kind cliコマンドで、k8s `v1.19.11`のノードイメージでk8sクラスタを作成。

```bash
K8S_NODE_IMAGE=v1.19.11
kind create cluster --name my-kind-cluster \
--image=kindest/node:${K8S_NODE_IMAGE} \
--config cluster.yaml
```

クラスタ構築が完了したら念の為にクラスタにアクセスしてみる。

```bash
kubectl version
Client Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.13", GitCommit:"53c7b65d4531a749cd3a7004c5212d23daa044a9", GitTreeState:"clean", BuildDate:"2021-07-15T20:58:11Z", GoVersion:"go1.15.14", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.11", GitCommit:"c6a2f08fc4378c5381dd948d9ad9d1080e3e6b33", GitTreeState:"clean", BuildDate:"2021-05-27T23:47:11Z", GoVersion:"go1.15.12", Compiler:"gc", Platform:"linux/amd64"}

kubectl get node
NAME                            STATUS   ROLES    AGE   VERSION
my-kind-cluster-control-plane   Ready    master   37m   v1.19.11
my-kind-cluster-worker          Ready    <none>   37m   v1.19.11
```

### Istioインストールとサンプルアプリのデプロイ

上記で作成したk8sクラスタにIstio `1.12.1`をインストールする。Istioの[Configuration profile](https://istio.io/latest/docs/setup/additional-setup/config-profiles/)には`demo`を指定。

```bash
curl -L https://git.io/getLatestIstio | ISTIO_VERSION=1.12.1 sh
cd istio-1.12.1
./bin/istioctl install --set profile=demo -y
```

Istioインストールが完了したら念の為にバージョン情報を出力。

```bash
./bin/istioctl version
client version: 1.12.1
control plane version: 1.12.1
data plane version: 1.12.1 (4 proxies)
```


次に、Istioインストールパッケージに含まれているサンプルアプリをクラスタにデプロイする。ここではnamespace `testns`に、Istioではおなじみのクライアントのリクエスト情報などを出力してくれるhttpbinと、実体はcurlコマンドのイメージであるsleepという2つのアプリをデプロイする。

```bash
# namespace testnsを作成
kubectl create ns testns
# namespace testnsのPodをIstioサイドカープロキシ (istio-proxy)注入対象とするためのラベルを付与
kubectl label namespace testns istio-injection=enabled --overwrite

# サンプルアプリ (httpbinとsleep)をtestnsにデプロイ
kubectl apply -f samples/httpbin/httpbin.yaml -n testns
kubectl apply -f samples/sleep/sleep.yaml -n testns
```

次のように、デプロイされたsleepアプリのコンテナからcurlを叩いてhttpbinアプリにリクエストを送信して、httpbinが動作しているか確認する。

```bash
SLEEP_POD=$(kubectl get pod -l app=sleep -n testns -o jsonpath={.items..metadata.name})
kubectl exec ${SLEEP_POD} -c sleep -n testns -- curl --head -s httpbin:8000/headers

# リクエストヘッダの出力結果
HTTP/1.1 200 OK
server: envoy
date: Sat, 18 Dec 2021 05:52:51 GMT
content-type: application/json
content-length: 525
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 5
```

## WASMモジュールの作成とWASM FilterのIstioへのデプロイ

ここでは、AssemblyScriptを元にWASMモジュールして、WASM FilterとしてIstioにデプロイする方法を紹介する。サンプルWASM Filterは以下の流れでデプロイする。

- AssemblyScriptをWASMモジュールにコンパイル
- WASMモジュールをOCI互換イメージとしてパッケージ化
- OCI互換のコンテナレジストリ（ここではGitHub Container Registry）にPush
- WASMモジュールのデプロイ先を定義するWasmPluginカスタムリソースのマニフェスト作成
- WasmPluginをapplyしてWASMモジュールをターゲットアプリのサイドカー(Istio Proxy/Envoy）にデプロイ


### AssemblyScript SDKによる開発用雛形の作成

AssemblyScriptでのFilter開発のためにAssemblyScript用のSDKを使用する。AssemblyScript SDKを使った開発方法については下記リンクが参考になる。

https://github.com/solo-io/proxy-runtime

ここでは[wasme](https://github.com/solo-io/wasm/tree/master/tools) CLIというWASM Filter開発のユーティリティコマンドを使って、[AssemblyScript SDK](https://github.com/solo-io/proxy-runtime)による開発用雛形を作成する方法を紹介する。

まずは、次のようにwasme CLIをインストールする。

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

次に、wasme CLIでプロジェクトの雛形を作成する。下記のようにwasme initの引数に`custom-filter`を指定する。これでcustom-filterというディレクトリに雛形が作成される。

```bash
wasme init custom-filter

# wasme initを実行するとインタラクションな選択が表示させる。ここでは下記の選択を行う
✔ assemblyscript
✔ gloo:1.3.x, gloo:1.5.x, gloo:1.6.x, istio:1.5.x, istio:1.6.x, istio:1.7.x, istio:1.8.x, istio:1.9.x
```

custom-filterディレクトリに雛形が作成されたのでファイル構造を見てみる。

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

`assembly/index.ts`がAssemblyScriptでのWASM Filterの実装にあたるファイル。内容は次の通りで、HTTPレスポンスに といヘッダーhelloを追加する処理となっている。

```javascript: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");
```
なお、[こちら](https://github.com/solo-io/proxy-runtime/tree/master/examples)にAssemblyScriptのサンプルがいくつかあるので参考にいただければと思う。

### WASMモジュールにコンパイル

下記npmコマンドを実行して、WASMモジュールにコンパイルする。

```bash
# 必要な依存パッケージをダウンロード
npm install
# WASMモジュールにコンパイル
npm run asbuild
```

ビルドが成功すると、結果はbuildフォルダに作成される。

```
$ tree build 
build
├── optimized.wasm
├── optimized.wasm.map
├── optimized.wat
├── untouched.wasm
├── untouched.wasm.map
└── untouched.wat

0 directories, 6 files
```
`untouched.wasm`、`optimized.wasm`はコンパイルされたバイナリファイル。`optimized.wasm.map`、`untouched.wasm.map`はソースマップファイル。次のステップのOCI互換イメージビルドで使うのは`optimized.wasm`。

### OCI互換イメージのビルドとコンテナレジストリへのPush

ここでは、docker CLIを使って、前ステップで生成されたWASMモジュールバイナリファイル（`optimized.wasm`）を元にOCI互換イメージを作成する。
既に、WASM OCIイメージ仕様については紹介しているが、OCI互換イメージ仕様である[Wasm Image Specification v0.0.0](https://github.com/solo-io/wasm/blob/master/spec/spec-compat.md)によると、イメージには次の2つのファイルを含む仕様となっている。

- **plugin.wasm** - (必須う) WASMバイナリ
- **runtime-config.json** - (オプショナル) ランタイム設定を記述したファイルで、イメージのメタデータとして使用される。このファイルは、OCIイメージ仕様である[WASM Artifact Image Specification v0.0.0](https://github.com/solo-io/wasm/blob/master/spec/spec.md)にて指定されている。

それでは、上記ファイル仕様に従い、OCI互換イメージを作成していく。 
まずはコンパイルされたWASMモジュールバイナリファイルをplugin.wasmとして保存する。

```bash
cp build/optimized.wasm plugin.wasm
```

次のように、plugin.wasmとruntime-config.jsonファイルの追加を定義したDockerfileを作成する。

```bash
cat << EOD > Dockerfile
FROM scratch
COPY runtime-config.json plugin.wasm ./
EOD
```

続いて、イメージのビルドとPushになるが、ここではOCIイメージのコンテナレジストリとしてGitHub Container Registry(以下、GHCR)を使用する。
> [補足] 当然ながら、Docker Hub, Amazon Elastic Container Registry、やGoogle Container Registry, Azure Container Registry, など他のOCIイメージサポートのレジストリも利用可能

まずは、GCHRにログインする（著者利用ユーザー = yokawasa）。GHCRへのログインではパスワードとしてGitHubアカウントの[Personal Access Token](https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)（以下、PAT）を利用する。

```bash
# GitHubアカウントのPATを指定
export PAT=xxxxxxxxxxxxxxxxx
# GHCRにログイン
echo $PAT | docker login ghcr.io -u yokawasa --password-stdin
Login Succeeded
```

それでは、docker CLIを使って、OCI互換イメージのビルドとGCHRへのPushを行う。

```bash
# OCI互換イメージのビルド
docker build . -t ghcr.io/yokawasa/sample-wasm-filters:0.1.0
# GCHRにPush
docker push ghcr.io/yokawasa/sample-wasm-filters:0.1.0
```

これで、ダウンロード先にghcr.io/yokawasa/sample-wasm-filters:0.1.0を指定することでイメージpullが可能となる。
なお、このイメージの公開設定はデプロイ先のKINDクラスタからpullできるようにPublic公開としている。GHCRパッケージのアクセスコントロールについては、[こちらのページ](https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility)が参考になる。


### WasmPlugin カスタムリソースの定義

ここでは、WASMモジュールのデプロイ先を定義するカスタムリソースであるWasmPluginのマニフェストを作成する。

https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/


ここでは以下の内容のWasmPluginマニフェストを作成する。主に、どのWasmモジュール（ここではOCIイメージ）を、どのPodのサイドカーで、filter chainの中のどの位置に注入するかを定義している。

```yaml:wasm-plugin.yaml
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: httpbin-custom-filter
  namespace: testns
spec:
  selector:
    matchLabels:
      app: httpbin
  pluginName: add_header
  url: oci://ghcr.io/yokawasa/sample-wasm-filters:0.1.0
  phase: STATS
```

- matchLabelsフィールドで指定したラベルに一致するPodのサイドカーがデプロイ先の対象となる
- [phase](https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/#PluginPhase)フィールドで、filter chainの中のどの位置に注入するかを定義。ここではSTATSなので、Istio stats filterの前で、Istio auth filtersの後に注入ということになる。
- urlフィールドでスキーマ指定が`oci://`なので、OCIイメージ`ghcr.io/yokawasa/sample-wasm-filters:0.1.0`をダウンロード。他にはWASMモジュールファイルのローカルからのダウンロード用の`file://`や、 リモートからのHTTP/HTTPSダウンロード用の`http[s]://`の指定が可能

この設定がapplyされると、Istiodは対応する構成をmatchLabelsフィールドで指定した`app:httpbin`ラベルに一致するサイドカーにプッシュする。サイドカーのIstioエージェントはWASMモジュールをダウンロードし、EnvoyランタイムにおいてWASMエンジンにロードし実行する。

### WASMモジュールをターゲットアプリのサイドカーにデプロイ

それでは、前ステップで作成したWasmPluginのマニフェストを次のようにクラスタにapplyする。

```bash
# WasmPluginのapply
kubectl apply -f wasm-plugin.yaml

# WasmPluginリソースの取得
kubectl get wasmplugin -n testns

NAME                    AGE
httpbin-custom-filter   23s
```

デプロイが完了後、さきほどと同じ様にhttpbinアプリにリクエスト送信すると、期待通りhelloヘッダが追加されていることが確認される。

```bash
SLEEP_POD=$(kubectl get pod -l app=sleep -n testns -o jsonpath={.items..metadata.name})
kubectl exec ${SLEEP_POD} -c sleep -n testns -- curl --head -s httpbin:8000/headers

HTTP/1.1 200 OK
server: envoy
date: Sat, 18 Dec 2021 06:51:23 GMT
content-type: application/json
content-length: 525
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 17
hello: world!              <<<<<< ヘッダが追加！!
```

ためしに、WasmPluginリソースをdeleteしてから再びhttpbinアプリにリクエスト送信すると、今度はさきほど追加されていたヘッダがなくなっていることを確認。

```bash
HTTP/1.1 200 OK
server: envoy
date: Sat, 18 Dec 2021 06:52:51 GMT
content-type: application/json
content-length: 525
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 5
```

### EnvoyのHTTP Filter Chain設定内容を確認

再びWasmPluginリソースをapplyして、デプロイしたWASM FilterがどのようにEnvoyのHTTP Filter Chainに反映されているのか確認してみる。Envoyの設定情報は次のIstio proxyのエンドポイントにリクエストして取得する。

```bash
HTTPBIN_POD=$(kubectl get pod -l app=httpbin -n testns -o jsonpath={.items..metadata.name})
kubectl exec -it ${HTTPBIN_POD} -n testns -c istio-proxy -- curl localhost:15000/config_dump > envoy_config_dump.json 
```

中身をみてみると、今回デプロイした`testns.httpbin-custom-filter`が、http_connection_managerにおけるHTTP Filtersに組み込まれており、WasmPluginリソース定義でのphaseフィールド(値はSTATS)で指定したようにIstio stats filterの前に注入されていることが分かる。

```json:envoy_config_dump.json
...omit...
  {
   "name": "envoy.filters.network.http_connection_manager",
   "typed_config": {
    "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
    "stat_prefix": "InboundPassthroughClusterIpv4",
    "route_config": {
     "name": "InboundPassthroughClusterIpv4",
     "virtual_hosts": [...],
    },
    "http_filters": [
     {
      "name": "testns.httpbin-custom-filter",
      "config_discovery": {
       "config_source": {
        "ads": {},
        "initial_fetch_timeout": "0s",
        "resource_api_version": "V3"
       },
       "type_urls": [
        "type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm"
       ]
      }
     },
     {
      "name": "istio.metadata_exchange",
      ...omit...
     },
     {
      "name": "envoy.filters.http.cors",
      ...omit...
     },
     {
      "name": "envoy.filters.http.fault",
      ...omit...
     },
     {
      "name": "istio.stats",
      ...omit...
     },
     {
      "name": "envoy.filters.http.router",
      ...omit...
     }
    ],
...omit...
```

# まとめ

WASM Filterはいいぞ。今後のさらなる発展に期待したい。

以上