Envoy Gateway HTTP ルーティング実践: Host、Path、Header、Traffic Splitting

9 min read

最初の 2 本で「これは何か」の土台はできました。ここからは日々実際に触るもの、つまり HTTPRoute に入ります。

HTTPRoute は、入ってくるトラフィックに対する routing script だと考えるとわかりやすいです。Nginx ルールを書いたり controller の annotation を暗記したりする代わりに、Kubernetes-native なリソースで表現します。

先に一つだけ整理しておくと、この記事が扱うのは HTTPRoute のみです。HTTP/HTTPS の意味を扱うからです。gRPC なら GRPCRoute、生の TCP なら TCPRoute、TLS passthrough なら TLSRoute が向いています。何でも HTTPRoute に押し込まないことです。YAML から「なんとか間に合わせてる感」が出てきます。

最もよくあるルーティング: Host と Path

典型例はこれです。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
spec:
  parentRefs:
    - name: eg
  hostnames:
    - "app.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-service
          port: 8080

意味はこうです。

  • Host: app.example.com のときだけこの route にマッチする
  • path は /api で始まる必要がある
  • トラフィックは api-service:8080 へ転送される

hostnames を外すと、「domain は問わず path だけで判定する route」になります。

matches で何を見られるか

matchesHTTPRoute の中心的な概念の一つです。 「どんな条件でこのルールを発火させるか」と考えるとわかりやすいです。

代表的な match 条件:

  • path
  • headers
  • queryParams
  • method

例:

matches:
  - method: GET
    path:
      type: PathPrefix
      value: /api
    headers:
      - name: version
        value: v2

意味:

  • GET リクエストのみ
  • path は /api で始まる
  • request に version: v2 header が必要

このパターンは API version の試験運用や、特定ユーザーへの段階的 rollout に向いています。

1 つの Route に複数ルールを持たせる

1 つの HTTPRoute で、異なる path を別 service へ振り分けることもできます。

rules:
  - matches:
      - path:
          type: PathPrefix
          value: /api
    backendRefs:
      - name: api-service
        port: 8080
  - matches:
      - path:
          type: PathPrefix
          value: /admin
    backendRefs:
      - name: admin-service
        port: 8080

これは 1 domain 配下のサブパス分割にとても便利です。たとえば:

  • /api は API service へ
  • /admin は admin backend へ

ルーティングロジックがそのまま見えます。途中まで読んだら自分の存在意義まで疑い始めるような古い設定ファイルより、ずっと健全です。

Header、Method、Query を条件にする

HTTPRoute は path だけではなく、request header、method、query param でもマッチできます。

たとえば version: v2 が付いた request だけ新 service へ送るなら:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: versioned-route
spec:
  parentRefs:
    - name: eg
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
          headers:
            - name: version
              value: v2
      backendRefs:
        - name: api-v2
          port: 8080
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-v1
          port: 8080

このパターンは次に向いています。

  • Canary release
  • 特定顧客だけ新 version を先に試す
  • 新機能の手動検証

新しい domain を丸ごと切るより、header ベース routing のほうが粒度が細かく、実験もしやすいです。

filters: 転送前に request を加工する

HTTPRoute は単に「match して forward する」だけではありません。転送前後に変換もできます。それが filters です。

よくある用途は header の追加です。

rules:
  - filters:
      - type: RequestHeaderModifier
        requestHeaderModifier:
          add:
            - name: x-env
              value: prod
    backendRefs:
      - name: api-service
        port: 8080

これは次に便利です。

  • backend に渡す前に tracing metadata を差し込む
  • 入口層で固定 header を付与する
  • request/response の変換

ただし、filters は強力でも、入口層を巨大 middleware の塊にしてよい免罪符ではありません。route にロジックを盛りすぎると、デバッグがそれ専用のカウンセリングを必要とする感じになります。

Weighted Traffic Splitting: 最もよくある Canary パターン

旧 version に 90%、新 version に 10% 流したいなら weight を使います。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: canary-route
spec:
  parentRefs:
    - name: eg
  hostnames:
    - "app.example.com"
  rules:
    - backendRefs:
        - name: api-v1
          port: 8080
          weight: 90
        - name: api-v2
          port: 8080
          weight: 10

これは基本的な canary / traffic splitting です。

トラフィック実験をするたびに完全な service mesh が必要なわけではありません。多くの API service では、これで十分以上ですし、設定もかなりきれいです。

💡 weight は相対値です。合計が 100 である必要はありません。91 でも 9010 と同じ比率を表します。

timeouts: request を永遠にぶら下げない

HTTPRoute は rule 単位で timeout を設定できます。これは重要です。入口層に制限がないと、悪い request が永遠に居座る迷惑客みたいになります。

rules:
  - matches:
      - path:
          type: PathPrefix
          value: /reports
    timeouts:
      request: 10s
      backendRequest: 2s
    backendRefs:
      - name: report-service
        port: 8080

2 つの field の意味:

  • request: クライアント request 全体の最大待機時間
  • backendRequest: backend への 1 回の request に対する最大待機時間

backendRequestrequest を超えるべきではありません。全行程 10 分なのに、途中のバス待ちが 20 分まで可、みたいな話になって宇宙が混乱します。

よくある 2 つの注意点

1. parentRefs は特定 listener を指定できる

Gateway に複数 listener がある場合、1 つだけに bind できます。

parentRefs:
  - name: eg
    sectionName: http

これは route が http listener にだけ attach されることを意味します。 複数 listener がある構成では、全部にぶら下げるよりずっと予測しやすいです。

2. matches がない = 全部にマッチする

rule に matches がない場合、デフォルトでは / にマッチする、つまり catch-all です。

便利ではありますが、自分ではより具体的な rule があるつもりでも、静かに飲み込んでしまうことがあります。面倒だから省略して、1 時間後に「なぜ精密な rule が動かないんだ」と悩まないようにしてください。

ルーティングが効いていることを確認する

route を設定したら curl で検証します。

curl -H "Host: app.example.com" http://$GATEWAY_HOST/api/users
curl -H "Host: app.example.com" -H "version: v2" http://$GATEWAY_HOST/api/users

HTTPRoute status を見るには:

kubectl get httproute app-route -o yaml

特に注目するのは:

  • status.parents
  • Accepted condition

YAML が正しそうでも、Gateway 側が reject していることがあります。応募しただけで採用されたと思い込むのと同じです。

一文まとめ

HTTPRoute の核心は「まず match し、その後で forward する」です。host、path、header、weight を押さえれば、日常的な HTTP 入口要件の 8 割くらいは処理できます。

次の一歩

次の記事では、本番で必ず出てくるテーマに進みます。HTTPS、証明書、TLS termination、そしてセキュリティです。 👉 TLS とセキュリティ