Envoy Gateway HTTP 路由實戰:Host、Path、Header 與流量分流

8 min read

前兩篇先讓你知道「這套東西是什麼」。這篇開始進入大家最常真的會改的地方:HTTPRoute

你可以把 HTTPRoute 想成入口流量的分流腳本,只是這次不是寫 Nginx 規則,也不是背 controller annotation,而是用 Kubernetes 原生資源來描述。

先講一個容易混淆的點:這篇只講 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
  • 且路徑要符合 /api 開頭
  • 最後送到 api-service:8080

如果你把 hostnames 拿掉,這條 route 就會更像「只看 path,不看網域」。

matches 到底能匹配什麼

HTTPRoute 最核心的概念之一就是 matches
你可以把它理解成「什麼樣的請求會命中這條 rule」。

常見可匹配條件有:

  • path
  • headers
  • queryParams
  • method

例如:

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

意思就是:

  • 只接受 GET
  • path 要以 /api 開頭
  • header 還得帶 version: v2

這種規則非常適合做 API 版本試跑或客戶分批放量。

一條 Route 可以寫多組規則

同一個 HTTPRoute 裡,可以根據不同路徑把流量送去不同服務:

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

這很適合一個網域底下切不同子路徑。像:

  • /api 給 API service
  • /admin 給後台 service

這種寫法的好處是,一眼就能看出路由邏輯,不像某些老配置檔,讀到一半會懷疑自己是不是在看 RPG 技能樹。

Header、Method、Query 也能當條件

HTTPRoute 不只看 path,還能看 request header、method、query param。

例如只有帶 version: v2 的請求才進新版服務:

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

這個模式很適合:

  • 灰度釋出
  • 特定客戶先試用新版本
  • 手動驗證新功能

比起直接切一個全新網域,header-based routing 通常更細,也更方便做實驗。

filters:匹配後還能先加工

HTTPRoute 不只是「符合就轉送」,它還能在轉送前後做一些處理,這就是 filters

常見用法像是加 header:

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

這種做法常見於:

  • 往後端補追蹤資訊
  • 在入口層加固定 header
  • 做 request/response 調整

不過要記得,filter 雖然好用,但它不是拿來把入口層寫成一個巨大神奇中介層的。太多行為堆在 route 上,之後除錯會很想把鍵盤拿去餵水獺。

權重分流:最常見的流量切分

如果你想把 90% 流量送舊版、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 服務來說,這已經夠用了,而且設定乾淨很多。

💡 weight 是相對值,不一定非得加到 100。91 也能表達一樣的比例。

timeouts:不要讓請求掛到天荒地老

HTTPRoute 也能在 rule 層設定 timeout。這點很重要,因為入口層如果沒有邊界,爛請求就會像奧客一樣坐著不走。

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

可以這樣理解:

  • request:整個 client 請求最多等多久
  • backendRequest:Gateway 跟後端單次請求最多等多久

通常 backendRequest 不能比 request 還大,不然邏輯上就很像說「整趟旅程 10 分鐘內要到,但其中一段公車我可以等 20 分鐘」,宇宙會先困惑。

兩個常見細節

1. parentRefs 可以指定 listener

如果一個 Gateway 有多個 listener,你可以只綁其中一個:

parentRefs:
  - name: eg
    sectionName: http

這代表這條 route 只掛到 http 這個 listener。
對有多個入口設定的場景,這會比全部亂掛更可控。

2. 沒寫 matches,等於全匹配

如果某條 rule 沒寫 matches,預設效果接近匹配 /
也就是說,它很像預設規則,所有請求都可能進來。

這很方便,但也很容易蓋掉你以為更精準的規則,所以不要因為偷懶少寫,最後把自己坑進去。

驗證路由有沒有生效

路由配置完,可以直接用 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 狀態:

kubectl get httproute app-route -o yaml

特別注意:

  • status.parents
  • Accepted condition

有時候 YAML 看起來很正確,但 Gateway 沒接受,那就像投履歷沒回信,不能當成入職成功。

一句話總結

HTTPRoute 的核心就是「先匹配,再轉送」。你掌握了 host、path、header、weight,日常 80% 的 HTTP 入口需求就差不多能處理。

下一步

下一篇來處理正式上線一定會碰到的事:HTTPS、憑證、TLS 終止與安全設定: 👉 TLS 與安全