TT Lab
はじめる
学ぶ 学習パス コース

Istioサービスメッシュ

VirtualServiceとDestinationRule — 役割が分かれる理由

TT Labで続きを見る

一言でいうと

VirtualServiceはどのリクエストをどこへ送るかを、DestinationRuleはそこに到着したあとどう扱うかを決めます。

なぜ必要なのか

KubernetesのServiceは、リクエストをPodの集合へ投げるところまでしか行いません。それ以上はありません。「このヘッダーが付いたリクエストだけを新バージョンへ」「このサービスは3秒以内に応答がなければ諦める」「連続して5xxを返すインスタンスは30秒間外す」といった要求は、すべてアプリケーションか前段のゲートウェイが引き受けなければなりませんでした。

Istioはこの要求を2つのリソースに分けます。分けた理由が重要です。理由は、ルーティング規則は頻繁に変わり部署ごとに異なる一方で、対象の性質(どのラベルがどのバージョンか、コネクションプールをいくつにするか)は比較的安定しているからです。そのため、デプロイパイプラインが毎回のデプロイで触るのはVirtualServiceだけで済み、DestinationRuleはサービスの所有者が一度決めれば長く保たれます。

どう動くのか

2つのリソースがEnvoyのどこに変換されるかを見ると、役割の分離がはっきりします。

Istioリソース Envoyでの対応 決めるもの
VirtualService RouteConfiguration マッチ条件、重み、書き換え、タイムアウト、リトライ、障害注入、ミラーリング
DestinationRule Cluster subset(ラベルで分けた対象)、ロードバランシング、コネクションプール、外れ値検出、クライアントTLS

Envoyのクラスター名は방향|포트|서브셋|FQDNの形式で(プレースホルダーは方向、ポート、サブセット、FQDNです)、outbound|9080|v2|reviews.mesh-lab.svc.cluster.localのような見た目になります。subset名がその位置にそのまま入るという点だけ覚えておくと、ログを読むのがずっと楽になります。

マッチングには順序があります。spec.httpは配列で、Envoyは上から順に下りてきて、最初に当てはまるものを使います。そのため、条件のないcatch-allルートを一番上に置くと、その下の規則は永遠に実行されません。実務上の規則は1つで、条件が狭い規則ほど上に、条件のない既定経路は必ず一番下に置くことです。そして、既定経路がまったくないと、マッチングに失敗したリクエストはNR(no route)フラグ付きの503になります。

subsetは名前ではなくラベルでPodを選びます。DestinationRuleにname: v2, labels: {version: v2}と書くと、そのsubsetはversion=v2ラベルが付いたPodだけを指します。名前とラベルを混同してラベルを書き忘れたりタイプミスをしたりすると、そのsubsetにはエンドポイントがなく、トラフィックはUH(no healthy upstream)の503になります。メッシュで最もよくある503の原因は、まさにこのラベルの不一致です。

プロトコルの判別も見落としやすい点です。メッシュは、Serviceのポート名でL7処理をするかどうかを決めます。ポート名がhttp、http-api、grpc、tcpのように始まって初めて、HTTPルーティングが働きます。名前がなかったり的外れだったりするとTCPとして扱われ、ヘッダーマッチングがまるごと無視されます。

レジリエンス設定は、数値の根拠を持って決める必要があります。

現場での姿

第一に、リトライストームです。A → B → Cのチェーンで各段階がリトライ2回を持っていると、Cが受けるリクエストは最悪の場合9倍になります。4段なら27倍です。死にかけているサービスにとどめを刺すようなもので、リトライはチェーンの1か所(できれば外側)にだけ置き、内側は0–1にします。

第二に、タイムアウトの入れ子です。外側のタイムアウトが内側より短いと、内側が誠実に処理している最中に外側が切断してリトライします。内側のサービスは「決して完了できない作業」を繰り返します。デッドラインは、外側から内側へ行くほど短くなければなりません。

第三に、503はフラグから見ます。UHはエンドポイントなし(subsetラベルを確認)、UOはコネクションプール超過(サーキットブレーカーが動作中)、UFは接続失敗(片側だけSTRICTのmTLS不一致が常連)、NRはルートなし(catch-allを確認)、URXはリトライ枯渇です。この5つを知っているだけでも、デバッグ時間が半分に減ります。

次のラボですること

フィクスチャのreviews v1/v2ワークロードを立ち上げてsubsetを定義したあと、既定経路はv1にし、特定のヘッダーが付いたリクエストだけをv2へ送るVirtualServiceを作ります。ratingsにはパスのプレフィックスマッチングと書き換えを設定し、タイムアウトとリトライを付けたうえで、外れ値検出と障害注入まで重ねます。最後にistioctl analyzeで設定を検証し、ルートの順序をJSONにまとめます。トラフィックが実際に流れるわけではありませんが、ルート配列の順序とsubsetのラベルの一致は、静的分析だけでも正確に判定できます。