iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
Kubernetes

Kubernetes ingress架構系列 第 2

# Day 2|入口流量的分層模型:從 L4 Service 到 L7 Gateway

  • 分享至 

  • xImage
  •  

本篇要解決什麼

Gateway API 的資源模型初看是複雜的:三種資源分屬三個層級、Route 要主動宣告 parentRefs、跨 namespace 引用還要 ReferenceGrant

這些設計都不是憑空長出來的,每一項都在回應一個具體的架構約束。本篇先把約束攤開:

  1. Kubernetes 原生的 Service 抽象停在哪一層,為什麼那一層不足以承載對外入口
  2. 一個 L7 入口規格必須具備哪些能力,才不會在規模化時崩潰
  3. 規格與實作的分離、控制平面與資料平面的分離,各自解決什麼問題

第一層:Service 的能力邊界

Kubernetes 內建的 Service 提供四種型別,它們的共同點比差異更重要——全部工作在 L4

型別 層級 對外可達 多服務共用入口 依 Host / Path 分流
ClusterIP L4
NodePort L4 ✓(埠號 30000+)
LoadBalancer L4 ✗(一服務一台 LB)
ExternalName DNS

L4 的抽象單位是 IP 位址與埠號。這一層看不見 HTTP 的 Host header,也看不見 URL path——對它而言,一條 TCP 連線就是一條 TCP 連線。

這個邊界造成的成本

假設一個系統有 30 個對外微服務。只用 Service 的話,唯一可行的做法是開 30 個 LoadBalancer

項目 數量
雲端負載平衡器 30 台
對外 IP 30 個
TLS 憑證與續期流程 30 份
DNS 記錄 30 筆
每月固定費用 30 份

成本只是表象,真正的問題是沒有共用的策略施加點。限流、驗證、標頭改寫、金絲雀分流這些橫切關注點,在 30 個各自獨立的 L4 入口上無處安放,只能退回到每個應用自己實作。

共用入口的需求,本質上是「需要一個能理解 HTTP 語意的策略施加點」。


第二層:一個 L7 入口規格需要什麼

跨過 L4 到 L7 之後,規格必須明確定義下列能力。這份清單就是評估任何入口規格的標準。

1. 匹配語意必須明確定義

L7 分流的第一步是「這個請求該由哪條規則處理」。匹配維度至少要有:

維度 用途
Host 依網域分流
Path 依路徑分流
Header 依標頭分流(版本、租戶、灰度標記)
Query 參數 依查詢字串分流
Method 依 HTTP 方法分流

關鍵不在於支援哪些維度,而在於語意是否被規格明文定義。以路徑前綴為例,/foo 是否匹配 /foobar

  • 若定義為「字串前綴」,答案是匹配
  • 若定義為「以 / 分隔的路徑元素逐段比對」,答案是不匹配

兩種都是合理的設計,但規格必須擇一寫死。一旦留下「行為由實作決定」的空間,同一份設定換一個實作就會產生不同的路由結果,可攜性隨即歸零。

Gateway API 在這點上採取的立場是把每種匹配型別的語意寫進規格,並以一致性測試強制實作遵守。PathPrefix 明確定義為逐段比對,RegularExpression 則明確標示為選配能力。

規則衝突時的裁決順序同樣必須由規格定義,否則「兩條規則都匹配」的情境會變成各實作各自表述。

2. 設定必須是結構化且可驗證的

進階行為——路徑改寫、標頭注入、逾時、重試、限流——需要一個承載設定的地方。承載方式有兩種極端:

承載方式 型別檢查 schema 驗證 編輯器補全 kubectl explain
自由字串
結構化欄位(CRD)

差異在錯誤何時被發現。以限流設定為例,若值以字串承載,把 10 誤植為 1O(字母 O)不會有任何人反對,設定會被接受、規則靜默失效,直到流量壓垮後端才會暴露。

結構化欄位則在 kubectl apply 當下就被 API Server 依 schema 擋下。把錯誤從執行期推到套用期,是這個設計選擇的全部價值。

Gateway API 的做法是把路由行為定義成 filters 陣列,每種 filter 有自己的型別與欄位:

filters:
  - type: URLRewrite
    urlRewrite:
      path:
        type: ReplacePrefixMatch
        replacePrefixMatch: /

3. 擴充機制必須存在,且與核心規格解耦

規格不可能涵蓋所有功能。限流演算法、JWT 驗證、熔斷策略、WAF 規則——這些是各實作競爭的領域,寫進規格反而會扼殺創新。

但擴充也不能沒有章法。Gateway API 的解法是 Policy Attachment:規格定義「Policy 如何附著到資源上」的通用機制,各實作定義自己的 Policy CRD。

spec:
  targetRefs:              # 規格定義的附著機制
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: some-route
  rateLimit:               # 實作定義的內容
    ...

這個切法的取捨很明確:核心路由邏輯可攜,進階功能不可攜。 Policy CRD 有 schema、有型別檢查,但換一個實作就不能用。

4. 權限邊界必須落在資源邊界上

這是規模化之後最容易被低估的一項。

一個典型的組織會有平台團隊與多個應用團隊。它們關心的設定截然不同:

角色 負責的設定
叢集管理員 叢集提供哪幾種入口、由誰實作
平台團隊 對外埠號、TLS 憑證、網域邊界、誰能綁上來
應用團隊 自己服務的路徑匹配與後端指向

若這些設定共存於同一個資源,RBAC 就失去作用——RBAC 的最小授權單位是資源,而衝突發生在同一資源的不同欄位。給了寫入權限,應用團隊就同時能改 TLS 設定、能搶用其他團隊的網域。

繞道方案不是沒有(准入 webhook、政策引擎、自製 CRD 再生成底層資源),但它們都是在補規格的洞,且各自引入新的維運負擔。

正解是把資源拆開,讓權限邊界與資源邊界重合。 Gateway API 拆成三層:

GatewayClass   ← 叢集管理員
     ↑ 被引用
  Gateway      ← 平台團隊
     ↑ 被綁定
 HTTPRoute     ← 應用團隊

拆開之後還需要一個雙向同意機制,否則任何 Route 都能綁到任何 Gateway 上。Gateway API 的做法是兩邊各表態:Route 用 parentRefs 主動指名要綁誰,Gateway 用 allowedRoutes 宣告允許哪些 namespace 綁進來,兩邊都同意才成立

跨 namespace 引用後端服務時,同樣需要被引用方以 ReferenceGrant 明示同意。

5. L4 必須是一等公民(First-class citizen / First-class object)

對外開放的不會只有 HTTP。資料庫、快取、DNS、自訂協定——這些都需要對外入口,而它們不是 HTTP。

若規格只定義 HTTP 路由,L4 需求就只能由各實作以自訂資源補足,於是每換一個實作就要重學一套 CRD。

Gateway API 的解法是把 Route 做成一個家族,共用同一套 listener 與權限模型:

資源 用途 發布通道
HTTPRoute HTTP / HTTPS 路由 Standard
GRPCRoute gRPC,理解 service/method 語意 Standard
TLSRoute 依 SNI 路由,不解密 Experimental
TCPRoute 任意 TCP Experimental
UDPRoute 任意 UDP Experimental

它們全部綁到同一個 Gateway,共用同一套 allowedRoutes 權限判定。


第三層:兩組關鍵分離

上面五項能力決定規格「能表達什麼」。接下來兩組分離則決定整套系統「怎麼運轉」。

分離一:規格與實作

Kubernetes 的核心元件中不包含任何 Gateway API 的實作。CRD 定義了資源型別,但沒有任何程式會對這些資源做出反應——直到叢集裡裝了一個實作。

這個分離透過 GatewayClass.controllerName 這個字串完成媒合:

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: eg
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller

每個實作宣告自己負責哪個 controllerName,只處理匹配的 GatewayClass,其餘一律忽略。因此同一個叢集可以並存多個實作,各管各的入口,互不干擾。

分離二:控制平面與資料平面

實作內部還有一組分離,這組決定了故障半徑。

┌────────────────────────────────────────┐
│  控制平面                               │
│  ─ watch Gateway API 資源               │
│  ─ 翻譯成資料平面的原生設定              │
│  ─ 熱套用                               │
└──────────────┬─────────────────────────┘
               │ 產生 / 更新設定
┌──────────────▼─────────────────────────┐
│  資料平面                               │
│  ─ 實際處理每一個封包                    │
└────────────────────────────────────────┘

兩種部署形態各有取捨:

形態 故障半徑 資源開銷 代表
合一(同一個 Pod 同時做兩件事) 控制平面故障直接影響流量 較低 早期 L7 代理
分離(各自獨立部署) 控制平面重啟不中斷既有流量 較高 Envoy Gateway、Istio

分離形態還帶來一個延伸能力:一個 Gateway 資源可以生出一組專屬的資料平面。兩個團隊的 Gateway 各跑各的代理程序,一邊被打爆不會波及另一邊。

所有 Gateway API 實作做的事情都是同一個迴圈——watch API 資源 → 翻譯成資料平面的原生設定 → 熱套用。Envoy Gateway 翻成 xDS,Traefik 翻成自家動態設定,Istio 也翻成 xDS。格式不同,模式相同。


狀態回報:第六項要求

前面五項談的是「能表達什麼」,但還有一項屬於維運面:當設定沒有生效時,系統要能說明原因。

宣告式 API 的常見失敗模式是「套用成功,但沒有作用」。資源建立了、kubectl get 看得到,流量卻不通。若規格沒有定義狀態回報,排查就只能靠猜。

Gateway API 把狀態回報訂為規格的一部分,三個核心 condition 貫穿所有資源:

Condition 意義 False 時的常見原因
Accepted 設定語意有效,實作願意接手 引用的 GatewayClass 不存在、listener 設定衝突、網域無交集
Programmed 設定已實際生效到資料平面 資料平面尚未就緒、憑證 Secret 不存在
ResolvedRefs 所有引用都解析成功 後端 Service 不存在、跨 namespace 缺少 ReferenceGrant

每個 condition 都帶有 reasonmessage,前者是機器可讀的列舉值,後者是人類可讀的說明。


小結

  • Kubernetes 原生 Service 的四種型別全部停在 L4,抽象單位是 IP 與埠號,看不見 HTTP 語意。共用 L7 入口的需求,本質是需要一個能理解 HTTP 的策略施加點
  • 一個 L7 入口規格需要滿足六項要求:匹配語意明確設定結構化擴充解耦權限邊界對齊資源邊界L4 一等公民失效可解釋
  • 其中「匹配語意明確」是可攜性的前提。規格一旦留下由實作決定的空間,同一份設定換實作就會產生不同結果
  • 「權限邊界對齊資源邊界」是規模化的前提。RBAC 的最小授權單位是資源,把不同角色的設定放進同一個資源,RBAC 就失效
  • 規格與實作分離透過 controllerName 媒合,使同叢集並存多個實作成為可能,也使「換實作不改路由資源」成為可能
  • 控制平面與資料平面分離決定故障半徑,並讓每個 Gateway 能擁有專屬的資料平面以取得隔離性

上一篇
# Day 1|Gateway API:Kubernetes 入口流量的規格層答案
系列文
Kubernetes ingress架構2
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言