前回の記事では最近作成した Wasm module を Envoy 上で動かす部分を解説しました。この記事ではそれを Istio 上で動かす部分を解説します。
Istio の Wasm 周りの歴史
実際の設定に入っていく前に Istio と Wasm の歴史を軽くおさらいしたいと思います。
Istio で Wasm の話がアナウンスに入ったのは 1.5 のアナウンス の認識です。1.5 の前は Mixer を使い外部のテレメトリやモニタリングシステムとの連携を行っていました。しかし Mixier にはパフォーマンスやレイテンシなどの問題があったようです。そのような状況下で Envoy で Wasm を使用した拡張をサポートする議論がはじまりランタイムとして V8 Engine が選択され実装が開始されました。そして 1.5 のアナウンスにあるように Mixier での拡張を Wasm に統合することが発表されました。このあたりの話は公式の「Redefining extensibility in proxies - introducing WebAssembly to Envoy and Istio」が参考になります。
そして Istio 1.9 のリリース では Experimental ではありますが開発者も Wasm module を HTTP で fetch できるようになり module の reload も proxy の restart なしで実現できるようになりました。またこの時点では開発者は EnvoyFilter API を使用して Wasm module を使用する必要がありました。このあたりの話は公式の「Istio and Envoy WebAssembly Extensibility, One Year On」が参考になります。
1.5 がリリースされたから約1年半後ついに 1.12 のリリース で Wasm を使用するためのファーストクラスの WasmPlugin API が導入されました!またこのタイミングで OCI Image のサポートもされたため開発者は OCI 互換のある Docker image を Docker Hub や ECR、GCR などに用意することで今までのように HTTP のエンドポイントを用意する必要もなくなりました。このあたりの話は公式の「Announcing the alpha availability of WebAssembly Plugins」が参考になります。
Istio 上で Wasm module を動かす
前回の記事に書いたように今回作ったものは下記の画像のように main, worker の各スレッドで動かす必要があるので Istio でどのように設定するのかを順番に解説していきます。今回の Istio は 1.13 を使用しています。
worker スレッドで Wasm module を使用する
Worker では WasmPlugin API を使用します。Wasm module の fetch 先として OCI イメージを使用します。そのためまずは イメージの作成を行います。しかし特に複雑なことはなく下記に記載されているように通常の Docker image 作るときのように Dockerfile を記載します。実際に作成した Dockerfile はこちらです。 github.com
作成したイメージは Github Container Registry に push しました。 Package aws-cloudwatch-proxy-wasm/oci-sender · GitHub
ちなみに仕様にそったイメージを作成しない場合、例えば OCI イメージではレイヤーを 1つしか指定できないので base image には scratch を使うのですが、他の alpine などを指定して2つ以上重ねてしまうと下記のようなエラーが表示されます。
could not fetch Wasm OCI image: could not extract Wasm file from the image as Docker container number of layers must be 1 but got 2
最後に WasmPlugin リソースを作成します。 spec.selector
でどの workload に対してこの WasmPlugin を使用するかを指定します。次に url に今回 push した OCI のイメージを指定します。そして最後に Wasm module にわたす設定を spec.PluginConfig
に指定します。spec.PluginConfig
には YAML を指定していますが Wasm module に渡されるときには JSON へと変換されます。
apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: aws-cloudwatch-proxy-wasm-sender namespace: istio-system spec: selector: matchLabels: app: istio-ingressgateway istio: ingressgateway url: oci://ghcr.io/hatappi/aws-cloudwatch-proxy-wasm/oci-sender:v0.1.0 imagePullPolicy: IfNotPresent pluginConfig: receiver_vm_id: aws-cloudwatch-proxy-wasm-receiver
これで worker 側は完了です。今回は public イメージを使用しましたが、private イメージを使用する場合は注意が必要です。1.13のドキュメント には imagePullSecret を指定できるようになっているのですが、実際は 1.13 では未実装となっています。そのため指定しても credential 周りのエラーが表示されて fetch できず悩むことになります。1.14 で導入されたようなので、private イメージを使用したいときは 1.14 を使う必要があります。
main スレッドで Wasm module を使用する
次は main スレッドでの設定です。こちらも WasmPlugin を使用したいところですが、現時点では使用できません。というのも前回の記事に書いたように main スレッド上で動かすために Boostrap configuration の Wasm - envoy.bootstrap.wasm を使用する必要があります。しかし現時点では WasmPlugin は HTTP filter の Wasm を設定するための API のため Bootstrap configuration のためには使用できません。
WasmPlugin は使用できないので今回は EnvoyFilter API を使用します。また EnvoyFilter API で使用する際は OCI image の fetch をサポートしてないので、 HTTP リクエストかファイルで指定する必要があります。今回はファイルで指定する方法を選択しました。
Wasm module をコンテナ経由でわたす
EnvoyFilter で Wasm module をファイルパスで指定するためにはそのファイルをコンテナへ渡す必要があります。今回は Wasm module の入った Docker image を用意して initContainers と emptyDir を使用してそれを実現しました。
containers: ~~~ volumeMounts: - name: aws-cloudwatch-proxy-wasm-vol mountPath: /aws-cloudwatch-proxy-wasm readOnly: true initContainers: - name: aws-cloudwatch-proxy-wasm image: ghcr.io/hatappi/aws-cloudwatch-proxy-wasm:v0.1.0 command: - cp - ./receiver.wasm - /aws-cloudwatch-proxy-wasm/ volumeMounts: - name: aws-cloudwatch-proxy-wasm-vol mountPath: "/aws-cloudwatch-proxy-wasm" volumes: - name: aws-cloudwatch-proxy-wasm-vol emptyDir: {}
monitoring.ap-northeast-1.amazonaws.com を Cluster に登録
main thread では monitoring.ap-northeast-1.amazonaws.com に対して HTTP リクエストを行いメトリクスを送る処理があります。これを実現するためにはこのエンドポイントを Envoy の Cluster を追加する必要があります。
Istio の場合は ServiceEntry resource で実現することができます。また今回はリクエスト先が HTTPS のため DestinationRule も用意します。
apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: monitoring-ap-northeast-1-amazonaws-com namespace: istio-system spec: hosts: - monitoring.ap-northeast-1.amazonaws.com location: MESH_EXTERNAL ports: - number: 443 name: https protocol: TLS resolution: DNS --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: monitoring-ap-northeast-1-amazonaws-com namespace: istio-system spec: host: monitoring.ap-northeast-1.amazonaws.com trafficPolicy: tls: mode: SIMPLE sni: monitoring.ap-northeast-1.amazonaws.com
EnvoyFilter API の作成
あとは下記のような EnvoyFilter resource を作成すれば完了です。設定自体はほとんど前回の記事で紹介した Envoy で動かす時に使用した設定をほぼそのまま使い EnvoyFilter API 固有の適用先の workload などの指定を行います。
apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: aws-cloudwatch-proxy-wasm-receiver namespace: istio-system spec: workloadSelector: labels: app: istio-ingressgateway istio: ingressgateway configPatches: - applyTo: BOOTSTRAP patch: operation: MERGE value: bootstrap_extensions: - name: envoy.bootstrap.wasm typed_config: "@type": type.googleapis.com/envoy.extensions.wasm.v3.WasmService singleton: true config: name: aws-cloudwatch-proxy-wasm-receiver vm_config: vm_id: "aws-cloudwatch-proxy-wasm-receiver" runtime: "envoy.wasm.runtime.v8" code: local: filename: "/aws-cloudwatch-proxy-wasm/receiver.wasm" environment_variables: host_env_keys: - ACPW_AWS_ACCESS_KEY_ID - ACPW_AWS_SECRET_ACCESS_KEY configuration: "@type": type.googleapis.com/google.protobuf.StringValue value: | { "cloud_watch_region": "ap-northeast-1", "cloud_watch_cluster_name": "outbound|443||monitoring.ap-northeast-1.amazonaws.com" }
Bootstrap configuration に追加した場合 HTTP filter とは違い Pod の restart が必要になります。また Istio はデフォルトの設定で bootstrap configuration を受け取らないようになっています。そのため BOOTSTRAP_XDS_AGENT
に true
を設定する必要があります。
これで main thread での作業は完了です。
最後に
今回は前回の記事で解説した Wasm module を Istio で動かす方法について記載しました。 main thread で Wasm module を動かしたい時は Bootstrap configuration に追加する必要があるため、少し手間が必要でした。一方 HTTP filter に追加したい場合は WasmPlugin API を使用するだけなので気軽に使えることが分かりました。
今回で拡張の楽しさを感じられたので今後の発展に期待したいです!!