拡張ポイントを開けたらアップグレードが怖くなった — EnvoyFilter と WasmPlugin
目標
EnvoyFilterの4つの項目(applyTo・match.context・patch.operation・priority)を、APIサーバーがどのように強制するかを実際に確認し、同じ要求をWasmPluginで書くと何が変わるかを比較します。最後に、クラスターのすべてのEnvoyFilterを洗い出して、バージョンアップのリスクをコードで出力する点検スクリプトを作ります。
なぜ重要なのか
メッシュを長く使っていると、標準APIでは表現できない要求が必ずやってきます。そのときEnvoyFilterを使うことになりますが、このAPIは、Istioの抽象ではなく、Envoyの内部設定構造を直接いじります。そのため、Istioをバージョンアップすると、プロキシ内のフィルター名や構造が変わって、パッチが黙って適用されなくなることがあります。エラーも出ませんし、アラートも鳴りません。だからといって、使わないようにというのは、現実的ではありません。その代わり、3つのことを習慣にします。範囲を可能な限り絞り(ネームスペースとworkloadSelector)、相対位置のパッチにはpriorityを書き、どこに何を差し込んだかを、機械が読めるチェックリストとして残します。そうすれば、バージョンアップの前に何を再確認すべきかを、人が覚えていなくても済みます。
ステップ
/root/ist-envoyfilterとネームスペースext-labを作成してください。そして、クラスターに登録されている拡張APIの2つを、/root/ist-envoyfilter/ext-apis.tsvに<복수형 이름>\t<API 그룹>\t<kind>の形式で2行書いてください(プレースホルダーは複数形の名前とAPIグループです)。EnvoyFilterとWasmPluginです。名前の昇順でソートします。/root/ist-envoyfilter/ef-broken.yamlに、ext-labネームスペースのEnvoyFilterinbound-luaを書いて、3か所をわざと間違えてください。applyToはHTTP_FILTERS、match.contextはSIDECAR_IN、patch.operationはINSERT_BEFORE_ALLです。workloadSelectorはapp: checkoutにします。このファイルは適用せず、サーバー側の試験適用だけを行って、拒否文を/root/ist-envoyfilter/ef-reject.txtに保存してください。/root/ist-envoyfilter/ef-inbound.yamlに、直したEnvoyFilterinbound-luaを書いて、ext-labに実際に適用してください。applyToはHTTP_FILTER、match.contextはSIDECAR_INBOUND、patch.operationはINSERT_BEFOREで、match.listener.filterChain.filter.nameはenvoy.filters.network.http_connection_managerです。patch.valueにはname: envoy.filters.http.luaだけを書き、priorityはまだ入れないでください。- ネームスペース
istio-systemを作成して、その中にEnvoyFiltermesh-access-logを適用してください。workloadSelectorは置かず、applyToはNETWORK_FILTER、match.contextはANY、patch.operationはMERGEで、アクセスログを/dev/stdoutに付けるtyped_configを書きます。マニフェストは/root/ist-envoyfilter/ef-mesh.yamlに置いてください。そして、現在のクラスターのEnvoyFilterごとに、<네임스페이스>/<이름>\t<selector 있음 yes|no>の形式で(プレースホルダーはネームスペースと名前とselectorの有無です)、/root/ist-envoyfilter/ef-scope.tsvにソートして書いてください。 istioctl analyze -n ext-lab -o jsonの出力を/root/ist-envoyfilter/analyze-before.jsonに保存してください。ステップ3で適用したinbound-luaに、IST0151の警告が付いている必要があります。そのあと、/root/ist-envoyfilter/ef-inbound-priority.yamlにspec.priorityを10で追加したバージョンを書いてもう一度適用し、同じコマンドの出力を/root/ist-envoyfilter/analyze-after.jsonに保存してください。後者にはIST0151がない必要があります。/root/ist-envoyfilter/wasm.yamlに、ext-labのWasmPluginheader-checkを書いて適用してください。selector.matchLabelsはapp: checkout、urlはoci://registry.lab.internal/plugins/header-check:1.0、phaseはAUTHZ、priorityは20、pluginConfig.headerはx-lab-tierです。そして、/root/ist-envoyfilter/wasm-bad.yamlにheader-check-badをphase: PRE_AUTHZで書いて、サーバーの試験適用で拒否されるようにし、その文を/root/ist-envoyfilter/wasm-reject.txtに保存してください。istioctl analyze -n ext-lab -o jsonをもう一度実行して、/root/ist-envoyfilter/findings.tsvに<코드>\t<대상>の形式で1行ずつ、ソートして書いてください(プレースホルダーはコードと対象です)。対象には、分析結果のoriginの値をそのまま使います。そして、/root/ist-envoyfilter/ef-inbound.yaml(ステップ3のファイル)をistioctl validateで検査した出力を、/root/ist-envoyfilter/validate.txtに保存してください。アナライザーが検出するものと、検証ツールが検出するものが同じではないことを、目で確認します。- ネームスペース
legacy-labを作成して、EnvoyFilterlegacy-ratelimitを適用してください。workloadSelectorなし、applyToはHTTP_FILTER、match.contextはSIDECAR_INBOUND、patch.operationはINSERT_AFTER、patch.valueはname: envoy.filters.http.ratelimitだけを書きます(マニフェストは/root/ist-envoyfilter/ef-legacy.yaml)。そのあと、/root/ist-envoyfilter/ef-audit.shを作成して、クラスターのすべてのEnvoyFilterを、<네임스페이스>/<이름>\t<위험코드들>の形式でソートして(プレースホルダーはネームスペースと名前とリスクコードです)、標準出力にだけ出力するようにしてください。リスクコードは、no-selector(workloadSelectorなし)・no-priority(相対位置の操作なのにpriorityなし)・name-only(typed_configなしで名前だけ)の3つを、カンマでつないで辞書順に書き、1つもなければ-を書きます。出力を/root/ist-envoyfilter/audit-result.txtに保存してください。
参考
- applyTo・context・operation・phaseは、すべて列挙型なので、APIサーバーが許可される値の一覧を返します。
kubectl apply --dry-run=serverは、作成せずに、サーバーに問い合わせだけを行います。- IST0151は、優先度のない相対位置のパッチを指します。
istioctl analyze -o jsonで、コードだけを取り出せます。 - よくある間違い: jqで
.spec.priority // "없음"のように書くと、0もないものとして扱われます(韓国語で「なし」を意味する語です)。== nullで比較してください。 - よくある間違い: ルートネームスペースに置いたEnvoyFilterを、ネームスペース用だと勘違いしてしまうこと。そこはメッシュ全体です。
- 参考: https://istio.io/v1.24/docs/reference/config/networking/envoy-filter/
- 参考: https://istio.io/v1.24/docs/reference/config/proxy_extensions/wasm-plugin/
- 参考: https://istio.io/v1.24/docs/reference/config/analysis/
拡張APIの2つが、どこに登録されているかを確認する
/root/ist-envoyfilterとネームスペースext-labを作成してください。そして、クラスターに登録されている拡張APIの2つを、/root/ist-envoyfilter/ext-apis.tsvに<복수형 이름>\t<API 그룹>\t<kind>の形式で2行書いてください(プレースホルダーは複数形の名前とAPIグループです)。EnvoyFilterとWasmPluginです。名前の昇順でソートします。
kubectl api-resourcesが、複数形の名前・APIグループ・kindをすべて表示してくれます。2つの拡張APIは、別々のグループに入っています。1つはトラフィックAPIと同じグループ、もう1つは拡張専用のグループです。
列挙型を3つ同時に間違えてみる
/root/ist-envoyfilter/ef-broken.yamlに、ext-labネームスペースのEnvoyFilter inbound-luaを書いて、3か所をわざと間違えてください。applyToはHTTP_FILTERS、match.contextはSIDECAR_IN、patch.operationはINSERT_BEFORE_ALLです。workloadSelectorはapp: checkoutにします。このファイルは適用せず、サーバー側の試験適用だけを行って、拒否文を/root/ist-envoyfilter/ef-reject.txtに保存してください。
kubectl apply --dry-run=serverは、APIサーバーに問い合わせるだけで、何も作成しません。CRDに構造的スキーマがあれば、サーバーが許可される値の一覧まで返してくれます。3か所が一度に出るかどうかを、数えてみてください。
3か所を直して、実際に適用する
/root/ist-envoyfilter/ef-inbound.yamlに、直したEnvoyFilter inbound-luaを書いて、ext-labに実際に適用してください。applyToはHTTP_FILTER、match.contextはSIDECAR_INBOUND、patch.operationはINSERT_BEFOREで、match.listener.filterChain.filter.nameはenvoy.filters.network.http_connection_managerです。patch.valueにはname: envoy.filters.http.luaだけを書き、priorityはまだ入れないでください。
HTTPフィルターを差し込むには、どのネットワークフィルターのフィルターチェーンの中なのかも、一緒に指定する必要があります。そのため、matchにlistener.filterChain.filter.nameが入ります。priorityは、次のステップで扱います。
メッシュ全体にかかる場所と、ワークロード1つにかかる場所
ネームスペースistio-systemを作成して、その中にEnvoyFilter mesh-access-logを適用してください。workloadSelectorは置かず、applyToはNETWORK_FILTER、match.contextはANY、patch.operationはMERGEで、アクセスログを/dev/stdoutに付けるtyped_configを書きます。マニフェストは/root/ist-envoyfilter/ef-mesh.yamlに置いてください。そして、現在のクラスターのEnvoyFilterごとに、<네임스페이스>/<이름>\t<selector 있음 yes|no>の形式で(プレースホルダーはネームスペースと名前とselectorの有無です)、/root/ist-envoyfilter/ef-scope.tsvにソートして書いてください。
ルートネームスペース(デフォルトはistio-system)に置いたEnvoyFilterは、メッシュ全体にかかります。別のネームスペースに置けば、そのネームスペースだけ、そこにworkloadSelectorまで付ければ、ラベルが合うワークロードだけです。範囲は狭いほど、事故が小さくなります。
相対位置のパッチに優先度がないと、アナライザーが警告する
istioctl analyze -n ext-lab -o jsonの出力を/root/ist-envoyfilter/analyze-before.jsonに保存してください。ステップ3で適用したinbound-luaに、IST0151の警告が付いている必要があります。そのあと、/root/ist-envoyfilter/ef-inbound-priority.yamlにspec.priorityを10で追加したバージョンを書いてもう一度適用し、同じコマンドの出力を/root/ist-envoyfilter/analyze-after.jsonに保存してください。後者にはIST0151がない必要があります。
IST0151は、INSERT_BEFOREのような相対位置の操作を使いながら、priorityを書かなかったときに出ます。適用順序が保証されないので、パッチがまったく反映されないかもしれない、という意味です。-o jsonを付けると、コードと対象が、機械が読める形で出力されます。
同じことを標準の拡張APIで書くと、何が変わるのか
/root/ist-envoyfilter/wasm.yamlに、ext-labのWasmPlugin header-checkを書いて適用してください。selector.matchLabelsはapp: checkout、urlはoci://registry.lab.internal/plugins/header-check:1.0、phaseはAUTHZ、priorityは20、pluginConfig.headerはx-lab-tierです。そして、/root/ist-envoyfilter/wasm-bad.yamlにheader-check-badをphase: PRE_AUTHZで書いて、サーバーの試験適用で拒否されるようにし、その文を/root/ist-envoyfilter/wasm-reject.txtに保存してください。
WasmPluginは、Envoyの内部構造の代わりに、「どの段階に何を差し込むか」だけを書きます。段階の名前は4つだけで、APIサーバーが一覧を教えてくれます。urlがないと作成されないことも、一緒に確認してみてください。
アナライザーがこのネームスペースで何を検出するかを、表にして固める
istioctl analyze -n ext-lab -o jsonをもう一度実行して、/root/ist-envoyfilter/findings.tsvに<코드>\t<대상>の形式で1行ずつ、ソートして書いてください(プレースホルダーはコードと対象です)。対象には、分析結果のoriginの値をそのまま使います。そして、/root/ist-envoyfilter/ef-inbound.yaml(ステップ3のファイル)をistioctl validateで検査した出力を、/root/ist-envoyfilter/validate.txtに保存してください。アナライザーが検出するものと、検証ツールが検出するものが同じではないことを、目で確認します。
jq -r '.[] | .code + "\t" + .origin'なら、2列の表がそのまま出力されます。validateはファイル1枚だけを見るので、クラスターの状態からしかわからないことは検出できません。2つのツールは、代替ではなく、異なる時点の検査です。
バージョンアップの前に実行するチェックリストを、スクリプトにする
ネームスペースlegacy-labを作成して、EnvoyFilter legacy-ratelimitを適用してください。workloadSelectorなし、applyToはHTTP_FILTER、match.contextはSIDECAR_INBOUND、patch.operationはINSERT_AFTER、patch.valueはname: envoy.filters.http.ratelimitだけを書きます(マニフェストは/root/ist-envoyfilter/ef-legacy.yaml)。そのあと、/root/ist-envoyfilter/ef-audit.shを作成して、クラスターのすべてのEnvoyFilterを、<네임스페이스>/<이름>\t<위험코드들>の形式でソートして(プレースホルダーはネームスペースと名前とリスクコードです)、標準出力にだけ出力するようにしてください。リスクコードは、no-selector(workloadSelectorなし)・no-priority(相対位置の操作なのにpriorityなし)・name-only(typed_configなしで名前だけ)の3つを、カンマでつないで辞書順に書き、1つもなければ-を書きます。出力を/root/ist-envoyfilter/audit-result.txtに保存してください。
kubectl get envoyfilter -A -o jsonをjqで洗い出せば、一度に数えられます。.spec.priority // ""のようにデフォルト値の演算子を使うと、0がないものに見えるので、== nullで比較してください。相対位置の操作は、INSERT_BEFORE、INSERT_AFTER、INSERT_FIRSTの3つです。