EnvoyFilter は翻訳ではなくパッチである
一言でいうと
EnvoyFilterは、変換ではなくパッチです。istiodが他のリソースからEnvoyの設定をすべて作った後、その結果物の1か所をapplyTo・context・matchで選び、operationで断片を差し込んだり直したりします。差し込む値はEnvoyの設定そのままなので、istioctlが通したパッチを、Envoyが拒否することが起こります。
なぜ必要なのか
VirtualService・DestinationRule・AuthorizationPolicyは、Envoyにできることの一部だけを公開しています。ローカルのレート制限、特定のヘッダーを扱うLua、メッシュがまだAPIで包んでいない新しいフィルターのように、その外側が必要になることがあります。IstioがEnvoyのすべての機能のためにAPIを新しく作ることはできないので、生成された設定に直接手を入れる非常口として残したのが、EnvoyFilterです。
非常口には代償があります。他のリソースは、istiodが意味を理解して責任を持って変換しますが、EnvoyFilterの値は、istiodにとって意味のわからない塊です。istiodの変換結果(フィルター名・順序・形)はバージョンごとに変わりうるのに、パッチはその結果に依存しています。公式ドキュメントが冒頭で「誤って書くとメッシュ全体を不安定にしかねない」と警告する理由です。実際に、istioctl 1.24.2は、正しいEnvoyFilterにも、「EnvoyFilter exposes internal implementation details that may change at any time」という警告を付けます。
どう動くのか
パッチ1つは、「どこに」と「何を」の組です。
| 項目 | 選ぶもの | 例 |
|---|---|---|
applyTo |
対象となるEnvoyオブジェクトの種類 | LISTENER・FILTER_CHAIN・NETWORK_FILTER・HTTP_FILTER・CLUSTER・ROUTE_CONFIGURATION |
match.context |
どのプロキシ・どの方向か | SIDECAR_INBOUND・SIDECAR_OUTBOUND・GATEWAY・ANY |
match.listener |
その種類の中のどれか1つ | ポート、filterChain.filter.name(ネットワークフィルター)、subFilter.name(HTTPフィルター) |
match.proxy.proxyVersion |
どのバージョンのプロキシか | ^1\.24.*のような正規表現 |
patch.operation |
どうするか | INSERT_BEFORE・INSERT_AFTER・INSERT_FIRST・MERGE・REPLACE・REMOVE・ADD |
HTTP_FILTERにsubFilter: envoy.filters.http.routerとINSERT_BEFOREを指定すると、受信側のHTTP接続マネージャーのフィルター一覧で、routerのすぐ前に、valueがそのまま項目1つとして入ります。そのため、valueはIstioの文法ではなく、Envoyの文法です。VirtualServiceのpercentage: { value: 100 }を書き写すと、Envoyのfaultフィルター(分子・分母を使うFractionalPercent)は理解できません。
順序にはルールがあります。ルートネームスペース(istio-system)のEnvoyFilterが先に、ワークロードのネームスペースのものが後に適用され、同じワークロードに複数あれば、作成時刻の順です。互いに衝突すると、結果は決まっていません。REPLACEは、名前が合う対象がなければ何もしません。エラーが出ずに、黙って抜け落ちます。範囲はworkloadSelectorが決めます。あればラベルが合うワークロード、なければそのネームスペース全体、ルートネームスペースに置けばメッシュ全体です。
検査がどこまで行われるかも、知っておく必要があります。istioctlは、EnvoyFilterの外側の構造を検査し、値をEnvoyの型として読み解いてみます。@typeの名前がまったくなければエラー(rc=1)で止めますが、その型にないフィールドは警告を出すだけで、rc=0です。そして、「routerはリストの最後でなければならない」のようなEnvoyの組み立てルールは、まったく知りません。最終的な判定は、Envoyが行います。
現場での姿
EnvoyFilterを入れたのに、何も変わらない。本番でEnvoyがパッチされたリスナーを拒否すると、Podは普通に動き続け、古い設定がそのまま残ります(xDS NACK)。istioctl proxy-statusで、そのプロキシだけ設定がずれており、istiodのログに拒否の理由が出力されます。routerの後ろにフィルターを入れたパッチが典型です。istioctl analyzeが、優先順位のない相対位置の操作にIST0151の警告を出すのも、同じ文脈です。基準となるフィルターがまだなければ、パッチが適用されません。
CIは緑なのに、デプロイ後に壊れる。パイプラインがistioctl validateの終了コードだけを見ていると、値の中の誤ったフィールドは、警告として流れていきます。EnvoyFilterに限っては、警告を失敗として扱うか、同じ値を入れたEnvoy設定をenvoy --mode validateでもう一度検査するほうが安全です。
アップグレードの日に、メッシュの半分がおかしくなる。フィルター名が変わったり、istiodが作る形が変わったりすると、古いパッチが見当違いの場所に付いたり、拒否されたりします。proxyVersionでバージョンを絞っておくと、新しいバージョンのプロキシにはパッチが付かないので、壊れる代わりに、機能が抜けた状態で起動します。新しいバージョン用のパッチを別に作って一緒に置いてから進めるのが、定石です。
公式ドキュメント: EnvoyFilter・Envoy HTTP filters
次のラボですること
faultフィルターをrouterの前に差し込むEnvoyFilterを書き、パッチが適用された結果を手で組み立てて、418を受け取ります。同じフィルターをrouterの後ろに差し込んだものと、VirtualService式のパーセントを書き写したものを作り、istioctlは通すのにEnvoyが拒否する隙間を、2回見ます。最後に、バージョンを絞る正規表現を実際のプロキシのバージョンに当ててみて、セレクターのないリクエスト制限フィルターで範囲を確認します。