Gateway API 的資源模型初看是複雜的:三種資源分屬三個層級、Route 要主動宣告 parentRefs、跨 namespace 引用還要 ReferenceGrant。
這些設計都不是憑空長出來的,每一項都在回應一個具體的架構約束。本篇先把約束攤開:
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 語意的策略施加點」。
跨過 L4 到 L7 之後,規格必須明確定義下列能力。這份清單就是評估任何入口規格的標準。
L7 分流的第一步是「這個請求該由哪條規則處理」。匹配維度至少要有:
| 維度 | 用途 |
|---|---|
| Host | 依網域分流 |
| Path | 依路徑分流 |
| Header | 依標頭分流(版本、租戶、灰度標記) |
| Query 參數 | 依查詢字串分流 |
| Method | 依 HTTP 方法分流 |
關鍵不在於支援哪些維度,而在於語意是否被規格明文定義。以路徑前綴為例,/foo 是否匹配 /foobar?
/ 分隔的路徑元素逐段比對」,答案是不匹配
兩種都是合理的設計,但規格必須擇一寫死。一旦留下「行為由實作決定」的空間,同一份設定換一個實作就會產生不同的路由結果,可攜性隨即歸零。
Gateway API 在這點上採取的立場是把每種匹配型別的語意寫進規格,並以一致性測試強制實作遵守。PathPrefix 明確定義為逐段比對,RegularExpression 則明確標示為選配能力。
規則衝突時的裁決順序同樣必須由規格定義,否則「兩條規則都匹配」的情境會變成各實作各自表述。
進階行為——路徑改寫、標頭注入、逾時、重試、限流——需要一個承載設定的地方。承載方式有兩種極端:
| 承載方式 | 型別檢查 | schema 驗證 | 編輯器補全 | kubectl explain |
|---|---|---|---|---|
| 自由字串 | ✗ | ✗ | ✗ | ✗ |
| 結構化欄位(CRD) | ✓ | ✓ | ✓ | ✓ |
差異在錯誤何時被發現。以限流設定為例,若值以字串承載,把 10 誤植為 1O(字母 O)不會有任何人反對,設定會被接受、規則靜默失效,直到流量壓垮後端才會暴露。
結構化欄位則在 kubectl apply 當下就被 API Server 依 schema 擋下。把錯誤從執行期推到套用期,是這個設計選擇的全部價值。
Gateway API 的做法是把路由行為定義成 filters 陣列,每種 filter 有自己的型別與欄位:
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
規格不可能涵蓋所有功能。限流演算法、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、有型別檢查,但換一個實作就不能用。
這是規模化之後最容易被低估的一項。
一個典型的組織會有平台團隊與多個應用團隊。它們關心的設定截然不同:
| 角色 | 負責的設定 |
|---|---|
| 叢集管理員 | 叢集提供哪幾種入口、由誰實作 |
| 平台團隊 | 對外埠號、TLS 憑證、網域邊界、誰能綁上來 |
| 應用團隊 | 自己服務的路徑匹配與後端指向 |
若這些設定共存於同一個資源,RBAC 就失去作用——RBAC 的最小授權單位是資源,而衝突發生在同一資源的不同欄位。給了寫入權限,應用團隊就同時能改 TLS 設定、能搶用其他團隊的網域。
繞道方案不是沒有(准入 webhook、政策引擎、自製 CRD 再生成底層資源),但它們都是在補規格的洞,且各自引入新的維運負擔。
正解是把資源拆開,讓權限邊界與資源邊界重合。 Gateway API 拆成三層:
GatewayClass ← 叢集管理員
↑ 被引用
Gateway ← 平台團隊
↑ 被綁定
HTTPRoute ← 應用團隊
拆開之後還需要一個雙向同意機制,否則任何 Route 都能綁到任何 Gateway 上。Gateway API 的做法是兩邊各表態:Route 用 parentRefs 主動指名要綁誰,Gateway 用 allowedRoutes 宣告允許哪些 namespace 綁進來,兩邊都同意才成立。
跨 namespace 引用後端服務時,同樣需要被引用方以 ReferenceGrant 明示同意。
對外開放的不會只有 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 都帶有 reason 與 message,前者是機器可讀的列舉值,後者是人類可讀的說明。
controllerName 媒合,使同叢集並存多個實作成為可能,也使「換實作不改路由資源」成為可能Gateway 能擁有專屬的資料平面以取得隔離性