Skip to content
Published on

Istioトラフィック管理の実践 — VirtualServiceからカナリア自動化まで

Authors

はじめに

Istioを導入する理由を一つだけ挙げるとすれば、ほとんどのチームがトラフィック管理を挙げます。コードのデプロイとトラフィックの切り替えを分離すること — つまり新バージョンをクラスタに配置した上で、トラフィックは1%だけ流してみること — は、デプロイ事故を構造的に減らす最も効果的な方法です。しかし実際の現場では、VirtualServiceとDestinationRuleの役割分担からして混乱し、リトライ設定を一つ間違えて障害を増幅させ、503エラーの原因を何日も追跡するといったことが繰り返されています。

この記事は、Istioのトラフィック管理を「動作するYAML」中心に整理した実践ガイドです。API間の関係図から始めて、ルーティングのマッチングルール、カナリア自動化(Flagger、Argo Rollouts)、ミラーリング、フォールトインジェクション、レジリエンスパターンの設定値算定ロジックまでを扱い、最後にリトライストーム・タイムアウトの入れ子・503デバッグという三大落とし穴を取り上げます。例はサイドカーモード基準で、Ambientモードでは同じAPIがwaypointプロキシで執行されるという点だけ覚えておけば大丈夫です。

トラフィックAPI関係図 — 誰が何を決めるのか

Istioトラフィック管理の四大リソースは、Gateway、VirtualService、DestinationRule、ServiceEntryです。それぞれの責務をまず図で見てみましょう。

                       (メッシュ外部から入るトラフィック)
                                  |
                                  v
                   +-----------------------------+
                   | Gateway                     |
                   | 「どのポート/ホスト/TLSで     |
                   |  トラフィックを受けるか」     |
                   +-----------------------------+
                                  |  (ホストマッチングで接続)
                                  v
+---------------------------------------------------------------+
| VirtualService                                                |
| 「受けたリクエストをどこへ、どのように送るか」                     |
|  - マッチング: パス/ヘッダー/メソッド/クエリ                     |
|  - 動作: 重み付け分配、リダイレクト、リライト、リトライ、          |
|          タイムアウト、フォールトインジェクション、ミラーリング     |
+---------------------------------------------------------------+
            |                                   |
            | (ホストのsubsetを指定)              | (外部ホストへルーティング)
            v                                   v
+---------------------------+      +---------------------------+
| DestinationRule           |      | ServiceEntry              |
| 「宛先に到着した後のポリシー」 |      | 「メッシュ外部サービスを     |
|  - subset定義(バージョンラベル)|     |  サービスレジストリに登録」  |
|  - ロードバランシングアルゴリズム|    |  - 外部API、レガシーDBなど  |
|  - コネクションプール、外れ値検出|    +---------------------------+
|  - TLS設定(アップストリーム)  |
+---------------------------+

混乱を減らす一行要約はこうです。VirtualServiceはルーティング(どこへ送るか)、DestinationRuleは宛先ポリシー(到着したトラフィックをどう扱うか)です。カナリアを作るとき「v1に90%、v2に10%」という分配はVirtualServiceに書きますが、v1とv2というsubset自体はDestinationRuleが定義します。どちらか一方だけ作っても動作しない理由がここにあります。

ルーティングルール詳説 — マッチングと分配

基本: subset定義と重み付け分配

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: reviews
  namespace: bookinfo
spec:
  host: reviews.bookinfo.svc.cluster.local
  subsets:
    - name: v1
      labels:
        version: v1     # Podラベルとマッチング
    - name: v2
      labels:
        version: v2
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews
  namespace: bookinfo
spec:
  hosts:
    - reviews.bookinfo.svc.cluster.local
  http:
    - route:
        - destination:
            host: reviews.bookinfo.svc.cluster.local
            subset: v1
          weight: 90
        - destination:
            host: reviews.bookinfo.svc.cluster.local
            subset: v2
          weight: 10

ヘッダー・パス・メソッドのマッチング

マッチングルールは上から下へ評価され、最初にマッチしたルールが適用されます。したがって具体的なルールを上に、包括的なルール(catch-all)を一番下に置くのが鉄則です。

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews-routing
  namespace: bookinfo
spec:
  hosts:
    - reviews.bookinfo.svc.cluster.local
  http:
    # ルール1: 社内QAヘッダーがあれば常にv2へ (最も具体的 — 一番上)
    - match:
        - headers:
            x-qa-user:
              exact: "true"
      route:
        - destination:
            host: reviews.bookinfo.svc.cluster.local
            subset: v2
    # ルール2: 特定パス + GETのみv2候補に
    - match:
        - uri:
            prefix: /api/v2/
          method:
            exact: GET
      route:
        - destination:
            host: reviews.bookinfo.svc.cluster.local
            subset: v2
    # ルール3: catch-all — 必ず最後に
    - route:
        - destination:
            host: reviews.bookinfo.svc.cluster.local
            subset: v1

マッチング演算子はexact(完全一致)、prefix(前方一致)、regex(正規表現)をサポートします。regexは強力ですが、EnvoyのRE2構文に従い評価コストがあるため、prefixで十分な場所にregexを乱用しないのが良いです。

同じmatchブロック内はAND、ブロック間はOR

よく間違える部分なので明示的に取り上げます。一つのmatch項目の中にuriとheadersを一緒に書くと両方を満たす必要があり(AND)、match配列に項目を複数置くとどれか一つを満たせば(OR)マッチします。

# AND: パスが/adminで、かつヘッダーがあればマッチ
    - match:
        - uri:
            prefix: /admin
          headers:
            x-role:
              exact: admin
# OR: パスが/adminであるか、またはヘッダーがあればマッチ
    - match:
        - uri:
            prefix: /admin
        - headers:
            x-role:
              exact: admin

カナリアデプロイ実践 — 重み付けステップとメトリクスゲート

手動カナリアの限界

重みを10 → 30 → 50 → 100へと人が手で上げていく手動カナリアは、出発点としては良いものの、二つの弱点があります。第一に、各ステップでメトリクスを人が見て判断する必要があるため夜間デプロイが困難です。第二に、悪い指標を見てもロールバックをためらう人間的なバイアスが入り込みます。そのため、メトリクスゲート(エラー率・レイテンシが基準を超えたら自動ロールバック)を備えた自動化ツールを組み合わせるのが定石です。

Flaggerでカナリア自動化

FlaggerはCanaryというCRD一つで、VirtualServiceとDestinationRuleを自動生成・調整します。

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: payments-api
  namespace: payments
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: payments-api
  service:
    port: 8080
    gateways:
      - istio-system/public-gateway
    hosts:
      - payments.example.com
  analysis:
    interval: 1m          # 1分ごとにメトリクスを評価
    threshold: 5          # 5回連続失敗でロールバック
    maxWeight: 50         # カナリアの最大重み
    stepWeight: 10        # ステップごとに10%ずつ増加
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99         # 成功率99%未満なら失敗カウント
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500        # p99が500ms超過なら失敗カウント
        interval: 1m
    webhooks:
      - name: load-test           # トラフィックが少ない時間帯に備えた合成負荷
        url: http://flagger-loadtester.test/
        metadata:
          cmd: "hey -z 1m -q 10 -c 2 http://payments-api-canary.payments:8080/healthz"

Flaggerが進行するステップは次のとおりです。

1. 新イメージ検知 → payments-api-canary Deploymentを作成
2. 重み10% → 1分間成功率/レイテンシを評価 → 通過なら20% → ... → 50%
3. 全ステップ通過 → primaryを新バージョンに置き換え、カナリアの重みを0%に
4. どのステップでもthreshold(5回)連続失敗 → 重み0%へ即時ロールバック
   → Deploymentはそのまま残すので原因分析が可能

Argo Rolloutsを使う場合

Argo CDベースのGitOps組織であれば、Argo Rolloutsが自然です。RolloutsはDeploymentをRollout CRDに置き換え、trafficRoutingにIstioを指定するとVirtualServiceの重みを直接操作します。

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: payments-api
  namespace: payments
spec:
  replicas: 5
  strategy:
    canary:
      trafficRouting:
        istio:
          virtualService:
            name: payments-api      # 既存VirtualServiceをRolloutsが操作
            routes:
              - primary
      steps:
        - setWeight: 10
        - pause: { duration: 5m }   # 5分間観察
        - analysis:                  # AnalysisTemplateでメトリクスゲート
            templates:
              - templateName: success-rate
        - setWeight: 30
        - pause: { duration: 5m }
        - setWeight: 60
        - pause: { duration: 5m }
  selector:
    matchLabels:
      app: payments-api
  template:
    metadata:
      labels:
        app: payments-api
    spec:
      containers:
        - name: app
          image: registry.example.com/payments-api:v2

Flaggerは「既存Deploymentをそのままにして横から調律する」非侵襲型、Argo Rolloutsは「DeploymentをRolloutに置き換える」侵襲型です。すでにGitOpsパイプラインがArgoエコシステムならRollouts、既存マニフェストに手を入れにくいならFlaggerが無難な選択です。

トラフィックミラーリング — 本番トラフィックでシャドーテスト

ミラーリング(シャドーイング)は、本番トラフィックのコピーを新バージョンへ流しつつ、応答は捨てる手法です。ユーザーへの影響なしに、本番のトラフィックパターンで新バージョンを検証できます。

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: orders
  namespace: commerce
spec:
  hosts:
    - orders.commerce.svc.cluster.local
  http:
    - route:
        - destination:
            host: orders.commerce.svc.cluster.local
            subset: v1
          weight: 100          # 実際の応答はすべてv1が処理
      mirror:
        host: orders.commerce.svc.cluster.local
        subset: v2             # コピーはv2へ
      mirrorPercentage:
        value: 20.0            # トラフィックの20%だけミラーリング

注意点が二つあります。第一に、ミラーリングされたリクエストも副作用(DB書き込み、外部API呼び出し)を引き起こします。書き込み経路をミラーリングするには、v2がシャドーモード(書き込みを無視するか別ストレージに記録)で動作するよう、アプリケーション側の準備が必要です。第二に、ミラーリクエストのHostヘッダーには-shadowサフィックスが付くため、ログでシャドートラフィックを区別できますし、区別すべきです。

フォールトインジェクション — メッシュ次元のカオステスト

レジリエンス設定(リトライ、タイムアウト、サーキットブレーカー)は、障害が起きて初めて検証されます。fault injectionはコード修正なしに、メッシュ次元で遅延とエラーを注入します。

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: ratings-fault
  namespace: bookinfo
spec:
  hosts:
    - ratings.bookinfo.svc.cluster.local
  http:
    - match:
        - headers:
            x-chaos-test:        # テストトラフィックにのみ注入 (全体注入は禁止)
              exact: "true"
      fault:
        delay:
          percentage:
            value: 50.0          # マッチしたトラフィックの50%に
          fixedDelay: 3s         # 3秒の遅延を注入
        abort:
          percentage:
            value: 10.0          # 10%にはHTTP 503を返す
          httpStatus: 503
      route:
        - destination:
            host: ratings.bookinfo.svc.cluster.local
            subset: v1
    - route:                      # 通常トラフィックはそのまま
        - destination:
            host: ratings.bookinfo.svc.cluster.local
            subset: v1

実践的なヒント: 本番クラスタで全トラフィックにfaultをかけるのはカオステストではなく障害です。必ずヘッダーマッチングで合成トラフィックにのみ注入し、注入状態をダッシュボードに表示して「いまfaultがかかっている」という事実をチームが認識できるようにしてください。上の例のように「ratingsが3秒遅くなったら、上位サービスreviewsのタイムアウト・フォールバックが意図どおり動作するか」を検証するのが標準シナリオです。

レジリエンスパターン — 設定値をどう決めるか

タイムアウトとリトライ

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: inventory
  namespace: commerce
spec:
  hosts:
    - inventory.commerce.svc.cluster.local
  http:
    - route:
        - destination:
            host: inventory.commerce.svc.cluster.local
      timeout: 3s                # リクエスト全体のデッドライン (リトライ込み)
      retries:
        attempts: 2              # 最大2回リトライ
        perTryTimeout: 1s        # 1回の試行あたり1秒
        retryOn: 5xx,reset,connect-failure,retriable-4xx

設定値算定のロジックは次のとおりです。

1. perTryTimeout: 対象サービスの正常時p99レイテンシ x 1.5~2倍
   (例: p99が400msならperTryTimeoutは1s)
2. attempts: 冪等リクエスト(GET)は2、非冪等(POST)は0~1
   非冪等リクエストのリトライは重複処理リスク — 冪等性キーがなければ禁止
3. timeout(全体): perTryTimeout x (attempts + 1) 以上でないと
   リトライが実際に意味を持たない。1s x 3 = 3s
4. retryOnのreset/connect-failureは安全 (リクエスト到達前の失敗)、
   5xxは非冪等リクエストでは慎重に

サーキットブレーカー — outlierDetectionとconnectionPool

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: inventory-cb
  namespace: commerce
spec:
  host: inventory.commerce.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100          # インスタンスごとではなくエンドポイントプール基準
      http:
        http1MaxPendingRequests: 50  # 待ち行列の上限 — 超過分は即503
        http2MaxRequests: 200        # 同時リクエスト上限
        maxRequestsPerConnection: 0  # 0 = 無制限 (keep-alive維持)
    outlierDetection:
      consecutive5xxErrors: 5        # 連続5xxが5回なら
      interval: 10s                  # 10秒周期で評価
      baseEjectionTime: 30s          # 30秒間プールから排除 (繰り返すと倍数増加)
      maxEjectionPercent: 50         # 同時排除は全体の50%まで
      minHealthPercent: 30           # 健全なインスタンスが30%未満なら排除停止

算定ガイドです。

- maxConnections / http2MaxRequests:
  平常時ピークの同時リクエスト数 x 1.5~2倍。小さすぎると平常時でも503、
  大きすぎるとサーキットブレーカーが事実上オフと同じ。
  Grafanaでistio_requests_totalベースに同時性を推定してから設定。
- http1MaxPendingRequests:
  「待たせるくらいなら早く失敗させたい上限」。レイテンシに敏感なサービスは
  小さく(10~50)、バッチ性トラフィックは大きく。
- consecutive5xxErrors + baseEjectionTime:
  デプロイ直後のウォームアップ中の一時エラーで過剰排除されないよう、
  consecutive5xxErrorsを小さすぎる値(1~2)にしないこと。
- maxEjectionPercent:
  インスタンスが3つしかないサービスで100%にすると全排除で
  かえって全面障害。50%以下 + minHealthPercentで安全弁を。

ロードバランシングとlocality

DestinationRuleのloadBalancerでアルゴリズムを選択します。

アルゴリズム動作適した場合
LEAST_REQUEST (デフォルト)アクティブリクエストが少ないエンドポイントを優先ほとんどの場合 — デフォルト維持を推奨
ROUND_ROBIN順次分配リクエストコストが均一なとき
RANDOMランダムヘルスチェックなしの単純分配
consistentHashハッシュキーベースの固定セッションアフィニティ、ローカルキャッシュ命中率
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: search-locality
  namespace: commerce
spec:
  host: search.commerce.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      simple: LEAST_REQUEST
      localityLbSetting:
        enabled: true
        failoverPriority:        # 同じzone → 同じregion → その他の順で優先
          - "topology.kubernetes.io/zone"
          - "topology.kubernetes.io/region"
    outlierDetection:            # localityフェイルオーバーにはoutlierDetectionが必須
      consecutive5xxErrors: 5
      interval: 10s
      baseEjectionTime: 30s

localityロードバランシングで最も多くはまる落とし穴: outlierDetectionなしでlocalityLbSettingだけ有効にするとフェイルオーバーが動作しません。Envoyは「このzoneのエンドポイントは不健全だ」という判断根拠があって初めて次の優先順位へ移るのですが、その判断こそがoutlierDetectionだからです。

ServiceEntry — 外部サービスをメッシュの統制下に

デフォルト設定では、メッシュの外へ出ていくトラフィックは統制なしに通過します(ALLOW_ANY)。外部依存をメッシュの可視性・ポリシーの下に置くにはServiceEntryで登録し、必要に応じてegressモードをREGISTRY_ONLYでロックします。

apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: external-payment-gateway
  namespace: payments
spec:
  hosts:
    - api.pgprovider.example
  location: MESH_EXTERNAL
  ports:
    - number: 443
      name: https
      protocol: TLS
  resolution: DNS
---
# 登録済みの外部ホストにもタイムアウト/リトライを適用可能
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: external-payment-gateway
  namespace: payments
spec:
  hosts:
    - api.pgprovider.example
  tls:
    - match:
        - sniHosts:
            - api.pgprovider.example
      route:
        - destination:
            host: api.pgprovider.example
# メッシュ全体で未登録の外部トラフィックを遮断 (IstioOperatorまたはMeshConfig)
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    outboundTrafficPolicy:
      mode: REGISTRY_ONLY    # ServiceEntryにない外部ホストは遮断

セキュリティ要件が高い環境(金融機関のネットワーク分離の補完など)では、これにegress gatewayを加えて「外部へ出るすべてのトラフィックが指定された出口ノードを通過する」よう強制できます。egress gatewayは外部呼び出しの単一監査ポイントとなり、ファイアウォールポリシーをワークロードIPではなくゲートウェイIPに固定できるようにしてくれます。

Gateway APIとの関係 — マイグレーションの観点

Kubernetes Gateway APIはIngressの後継標準であり、Istioはこの標準の代表的な実装の一つです。2026年現在、方向性は明確です。新規構築でイングレス(外部流入)層はGateway API(Gateway + HTTPRoute)で書くのが推奨で、Istio固有のGateway/VirtualServiceは引き続きサポートされるものの、新しい標準機能はGateway API側に先に載ります。AmbientのwaypointがGateway APIリソースとして宣言される点も、この方向性を示しています。

# Istio Gateway/VirtualService構成のGateway API等価物
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public-gateway
  namespace: istio-ingress
spec:
  gatewayClassName: istio
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      hostname: shop.example.com
      tls:
        mode: Terminate
        certificateRefs:
          - name: shop-example-com-cert
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop-route
  namespace: commerce
spec:
  parentRefs:
    - name: public-gateway
      namespace: istio-ingress
  hostnames:
    - shop.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api/
      backendRefs:
        - name: shop-api
          port: 8080
          weight: 90
        - name: shop-api-canary
          port: 8080
          weight: 10

マイグレーションの優先順位はイングレス層からです。メッシュ内部(east-west)ルーティングはVirtualServiceが依然として表現力で先行する部分(ミラーリングなど一部機能)があるため、内部ルーティングまで一度に変えようとせず、Gateway APIの機能成熟度を見ながら段階的に移すのが安全です。

よくある落とし穴とアンチパターン

落とし穴1: リトライストーム (retry storm)

呼び出しチェーンA → B → Cで各段階がリトライ2回を持つと、Cの障害1件が最悪の場合A基準で9倍のトラフィックに増幅されます。

リトライ増幅の算数:
  AがBを呼ぶ: 1 + リトライ2回 = 最大3回
  BがCを呼ぶ: それぞれ1 + 2回 = 最大3回
  → Cが受けるリクエスト: 3 x 3 = 最大9回 (本来は1回)

チェーンが4段なら27倍。Cが過負荷で死にかけているなら、
リトライが棺に釘を打つようなもの。

原則:
- リトライはチェーンの一箇所(できるだけ最も外側の層)にのみ
- 内部の深い層はattempts: 0または1
- outlierDetectionと併用して、死にかけのインスタンスへのリトライを遮断

落とし穴2: タイムアウトの入れ子

上位のタイムアウトが下位より短いと、下位が誠実に処理中でも上位が先に切ってリトライします。結果として下位サービスは「絶対に完了できない作業」を繰り返し実行することになります。

誤った構成:
  A --(timeout 1s)--> B --(timeout 3s)--> C
  BがCの応答を2.5秒で受け取っても、Aはすでに1秒で切ってリトライ済み

原則: 外側のタイムアウト >= 内側のタイムアウト合計 + 余裕
  A --(timeout 5s)--> B --(timeout 3s)--> C (各リトライ込みで計算)
  デッドラインは外側から内側へ行くほど短くならなければならない

落とし穴3: 503デバッグフロー

Istioの503は原因が多様で、体系なしに探すと時間を失います。レスポンスフラグ(response flags)基準のデバッグ手順を推奨します。

503デバッグフロー:

1. アクセスログでRESPONSE_FLAGSを確認
   kubectl logs deploy/app -c istio-proxy | grep ' 503 '

2. フラグごとの分岐:
   UH  (no healthy upstream)
       → エンドポイントなし。DestinationRule subsetラベルとPodラベルの
         不一致が定番の原因
       → istioctl proxy-config endpoints deploy/app | grep 対象サービス
   UO  (upstream overflow)
       → connectionPool上限超過。サーキットブレーカーが意図どおり動作中か、
         上限が小さすぎるかを判断
   UF  (upstream connection failure)
       → 接続自体が失敗。mTLS設定の不一致(片側のみSTRICT)または
         ポート/プロトコルのミスマッチ
   URX (max retries reached)
       → リトライ枯渇。根本原因は他のフラグとともに追跡
   NR  (no route)
       → VirtualServiceマッチング漏れ。catch-allルールの存在を確認

3. 設定と実際の状態を照合:
   istioctl analyze -n ネームスペース
   istioctl proxy-config cluster deploy/app --fqdn 対象FQDN -o json

その他のアンチパターン

  • VirtualServiceの一ホスト分散定義: 同じホストに対するVirtualServiceを複数のネームスペース/ファイルに分割定義すると、マージ順序が保証されません。ホストごとに一つのVirtualServiceで管理してください。
  • subsetだけ作ってラベル漏れ: DestinationRule subsetのラベルと実際のPodラベルが異なるとUHの503が出ます。デプロイパイプラインでラベル一致を検証しましょう。
  • catch-allのないマッチングルール: すべてのmatchが失敗するとNRになります。最後のルールは常に無条件ルートに。

運用検証 — istioctl proxy-configの読み方

Envoyに実際に配られた設定を確認することが、あらゆるデバッグの出発点です。

# 1) リスナー: このプロキシがどのポートを聴いているか
istioctl proxy-config listeners deploy/app -n commerce

# 2) ルート: VirtualServiceがどのルーティングテーブルに翻訳されたか
istioctl proxy-config routes deploy/app -n commerce --name 8080 -o json

# 3) クラスタ: DestinationRule(サーキットブレーカー、TLS)が反映されたか
istioctl proxy-config cluster deploy/app -n commerce \
  --fqdn inventory.commerce.svc.cluster.local -o json

# 4) エンドポイント: subsetに実際のPod IPが載っているか
istioctl proxy-config endpoints deploy/app -n commerce \
  | grep inventory

# 5) 全体の同期状態: istiodとプロキシ間の設定伝播を確認
istioctl proxy-status

読む順序はトラフィックの流れと同じです。listener(流入) → route(マッチング) → cluster(宛先ポリシー) → endpoint(実際のIP)。どの段階で期待とずれているかを見つけたら、その段階に対応するリソース(Gateway/VirtualService/DestinationRule/Podラベル)を直せばよいのです。proxy-statusでSTALEが見えたら、istiodが設定を配布できていないということなので、istiodのログから確認します。

チェックリスト

  • ホストごとにVirtualServiceを一つに保ち、catch-allルートを置いた
  • DestinationRule subsetラベルとPodラベルの一致をパイプラインで検証している
  • リトライは呼び出しチェーンの一層にのみ置き、非冪等リクエストのリトライを禁止した
  • タイムアウトが外側から内側へ行くほど短くなるよう、チェーン全体を点検した
  • サーキットブレーカー上限を実測の同時性に基づいて算定した (デフォルト放置禁止)
  • localityロードバランシングにoutlierDetectionを併せて設定した
  • カナリアにメトリクスゲート(FlaggerまたはArgo Rollouts)を接続した
  • 外部依存をServiceEntryで登録し、REGISTRY_ONLY適用を検討した
  • fault injectionはヘッダーマッチングで合成トラフィックにのみ適用する
  • 503対応ランブックにresponse flagsの分岐表を含めた
  • 新規イングレスはGateway APIで書く方針を定めた

おわりに

Istioトラフィック管理の本質は「デプロイとリリースの分離」です。コードをクラスタに載せること(デプロイ)と、ユーザートラフィックをそのコードへ送ること(リリース)を独立に制御できて初めて、金曜の午後でも恐れずにデプロイできるようになります。VirtualServiceとDestinationRuleの役割分担、リトライ・タイムアウトのチェーン全体視点での設計、そしてメトリクスゲート付きのカナリア自動化 — この三つが柱です。

設定値はコピーして終わりではなく、実測指標に基づいて算定しfault injectionで検証すべきだという点、そしてあらゆるデバッグはistioctl proxy-configで「Envoyが実際に受け取った設定」を見るところから始まるという点を覚えておけば、Istioトラフィック管理は複雑な魔法ではなく予測可能な道具になります。

参考資料