在 Traefik 中使用 Kubernetes Gateway API
Gateway API(之前叫 Service API)是由 SIG-NETWORK 社區管理的開源項目,項目地址:https://gateway-api.sigs.k8s.io/。主要原因是 Ingress 資源對象不能很好的滿足網絡需求,很多場景下 Ingress 控制器都需要通過定義 annotations 或者 crd 來進行功能擴展,這對于使用標準和支持是非常不利的,新推出的 Gateway API 旨在通過可擴展的面向角色的接口來增強服務網絡。
Gateway API 是 Kubernetes 中的一個 API 資源集合,包括 GatewayClass、Gateway、HTTPRoute、TCPRoute、Service 等,這些資源共同為各種網絡用例構建模型。
Gateway API 的改進比當前的 Ingress 資源對象有很多更好的設計:
- 面向角色 - Gateway 由各種 API 資源組成,這些資源根據使用和配置 Kubernetes 服務網絡的角色進行建模。
- 通用性 - 和 Ingress 一樣是一個具有眾多實現的通用規范,Gateway API 是一個被設計成由許多實現支持的規范標準。
- 更具表現力 - Gateway API 資源支持基于 Header 頭的匹配、流量權重等核心功能,這些功能在 Ingress 中只能通過自定義注解才能實現。
- 可擴展性 - Gateway API 允許自定義資源鏈接到 API 的各個層,這就允許在 API 結構的適當位置進行更精細的定制。
還有一些其他值得關注的功能:
- GatewayClasses - GatewayClasses 將負載均衡實現的類型形式化,這些類使用戶可以很容易了解到通過 Kubernetes 資源可以獲得什么樣的能力。
- 共享網關和跨命名空間支持 - 它們允許共享負載均衡器和 VIP,允許獨立的路由資源綁定到同一個網關,這使得團隊可以安全地共享(包括跨命名空間)基礎設施,而不需要直接協調。
- 規范化路由和后端 - Gateway API 支持類型化的路由資源和不同類型的后端,這使得 API 可以靈活地支持各種協議(如 HTTP 和 gRPC)和各種后端服務(如 Kubernetes Service、存儲桶或函數)。
面向角色設計
無論是道路、電力、數據中心還是 Kubernetes 集群,基礎設施都是為了共享而建的,然而共享基礎設施提供了一個共同的挑戰,那就是如何為基礎設施用戶提供靈活性的同時還能被所有者控制。
Gateway API 通過對 Kubernetes 服務網絡進行面向角色的設計來實現這一目標,平衡了靈活性和集中控制。它允許共享的網絡基礎設施(硬件負載均衡器、云網絡、集群托管的代理等)被許多不同的團隊使用,所有這些都受到集群運維設置的各種策略和約束。下面的例子顯示了是如何在實踐中運行的。
一個集群運維人員創建了一個基于 GatewayClass 的 Gateway 資源,這個 Gateway 部署或配置了它所代表的基礎網絡資源,集群運維和特定的團隊必須溝通什么可以附加到這個 Gateway 上來暴露他們的應用。集中的策略,如 TLS,可以由集群運維在 Gateway 上強制執行,同時,Store 和 Site 應用在他們自己的命名空間中運行,但將他們的路由附加到相同的共享網關上,允許他們獨立控制他們的路由邏輯。
這種關注點分離的設計可以使不同的團隊能夠管理他們自己的流量,同時將集中的策略和控制留給集群運維。
概念
在整個 Gateway API 中涉及到3個角色:基礎設施提供商、集群管理員、應用開發人員,在某些場景下可能還會涉及到應用管理員等角色。Gateway API 中定義了3種主要的資源模型:GatewayClass、Gateway、Route。
GatewayClass
GatewayClass 定義了一組共享相同配置和動作的網關。每個GatewayClass 由一個控制器處理,是一個集群范圍的資源,必須至少有一個 GatewayClass 被定義。
這與 Ingress 的 IngressClass 類似,在 Ingress v1beta1 版本中,與 GatewayClass 類似的是 ingress-class 注解,而在Ingress V1 版本中,最接近的就是 IngressClass 資源對象。
Gateway
Gateway 網關描述了如何將流量轉化為集群內的服務,也就是說,它定義了一個請求,要求將流量從不了解 Kubernetes 的地方轉換到集群內的服務。例如,由云端負載均衡器、集群內代理或外部硬件負載均衡器發送到 Kubernetes 服務的流量。
它定義了對特定負載均衡器配置的請求,該配置實現了 GatewayClass 的配置和行為規范,該資源可以由管理員直接創建,也可以由處理 GatewayClass 的控制器創建。
Gateway 可以附加到一個或多個路由引用上,這些路由引用的作用是將流量的一個子集導向特定的服務。
Route 資源
路由資源定義了特定的規則,用于將請求從網關映射到 Kubernetes 服務。
從 v1alpha2 版本開始,API 中包含四種 Route 路由資源類型,對于其他未定義的協議,鼓勵采用特定實現的自定義路由類型,當然未來也可能會添加新的路由類型。
HTTPRoute
HTTPRoute 適用于 HTTP 或 HTTPS 連接,適用于我們想要檢查 HTTP 請求并使用 HTTP 請求進行路由或修改的場景,比如使用 HTTP Headers 頭進行路由,或在請求過程中對它們進行修改。
TLSRoute
TLSRoute 用于 TLS 連接,通過 SNI 進行區分,它適用于希望使用 SNI 作為主要路由方法的地方,并且對 HTTP 等更高級別協議的屬性不感興趣,連接的字節流不經任何檢查就被代理到后端。
TCPRoute 和 UDPRoute
TCPRoute(和UDPRoute)旨在用于將一個或多個端口映射到單個后端。在這種情況下,沒有可以用來選擇同一端口的不同后端的判別器,所以每個 TCPRoute 在監聽器上需要一個不同的端口。你可以使用 TLS,在這種情況下,未加密的字節流會被傳遞到后端,當然也可以不使用 TLS,這樣加密的字節流將傳遞到后端。
組合
GatewayClass、Gateway、xRoute 和 Service 的組合定義了一個可實施的負載均衡器。下圖說明了不同資源之間的關系:
使用反向代理實現的網關的典型客戶端/網關 API 請求流程如下所示:
1. 客戶端向 http://foo.example.com 發出請求
2. DNS 將域名解析為 Gateway 網關地址
3. 反向代理在監聽器上接收請求,并使用 Host Header 來匹配HTTPRoute
4. (可選)反向代理可以根據 HTTPRoute 的匹配規則進行路由
5. (可選)反向代理可以根據 HTTPRoute 的過濾規則修改請求,即添加或刪除 headers
6. 最后,反向代理根據 HTTPRoute 的 forwardTo 規則,將請求轉發給集群中的一個或多個對象,即服務。
實現
目前已經有很多 Gateway API 的控制器實現方案了,比如 Contour、Google Kubernetes Engine、Istio、Traefik 等等。接下來我們以 Traefik 為例來進行測試。不過需要注意的是 Traefik 目前是基于 v1alpha1 規范實現的,可能和上面提到的一些概念略有不同。
要在 Traefik 中使用 Gateway API,首先我們需要先手動安裝 Gateway API 的 CRDs,使用如下命令即可安裝,這將安裝包括 GatewayClass、Gateway、HTTPRoute、TCPRoute 等 CRDs:
- ➜ kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.3.0" \
- | kubectl apply -f -
然后我們需要在 Traefik 中開啟 kubernetesgateway 這個 Provider,同樣基于前面 Traefik 章節中的 Helm Chart 包進行定義,設置 experimental.kubernetesGateway.enabled=true,完整的 Values 文件如下所示:
- # ci/deployment-prod.yaml
- # Enable experimental features
- experimental:
- kubernetesGateway: # 開啟 gateway api 支持
- enabled: true
- providers:
- kubernetesCRD:
- enabled: true
- allowCrossNamespace: true # 是否允許跨命名空間
- allowExternalNameServices: true # 是否允許使用 ExternalName 的服務
- kubernetesIngress:
- enabled: true
- allowExternalNameServices: true
- # ......
- # 其他忽略
然后使用下面的命令更新 Traefik 即可:
- ➜ helm upgrade --install traefik ./traefik -f ./traefik/ci/deployment-prod.yaml --namespace kube-system
更新完成后可以前往 Traefik 的 Dashboard 查看是否已經啟用 KubernetesGateway 這個 Provider:
正常情況下啟用成功后 Traefik 也會創建一個默認的 GatewayClass 資源對象和 Gateway 實例:
- ➜ kubectl get gatewayclass
- NAME CONTROLLER AGE
- traefik traefik.io/gateway-controller 4m13s
- ➜ kubectl get gatewayclass traefik -o yaml
- apiVersion: networking.x-k8s.io/v1alpha1
- kind: GatewayClass
- metadata:
- name: traefik
- spec:
- controller: traefik.io/gateway-controller
- ......
- ➜ kubectl get gateway -n kube-system
- NAME CLASS AGE
- traefik-gateway traefik 5m55s
- ➜ kubectl get gateway -n kube-system traefik-gateway -o yaml
- apiVersion: networking.x-k8s.io/v1alpha1
- kind: Gateway
- metadata:
- name: traefik-gateway
- namespace: kube-system
- spec:
- gatewayClassName: traefik
- listeners:
- - port: 8000
- protocol: HTTP
- routes:
- group: networking.x-k8s.io
- kind: HTTPRoute
- namespaces:
- from: Same
- selector:
- matchLabels:
- app: traefik
- ......
可以看到默認創建的 Gateway 實例引用了 traefik 這個 GatewayClass,其中 listeners 部分定義了該網關關聯的監聽器入口,監聽器定義邏輯端點綁定在該網關地址上,至少需要指定一個監聽器,下面的 HTTPRoute 定義了路由規則,namespaces 表示應該在哪些命名空間中為該網關選擇路由,默認情況下,這被限制在該網關的命名空間中,Selector 則指定一組路由標簽,如果定義了這個 Selector,則只路由匹配選擇器與網關相關聯的對象,一個空的選擇器匹配所有對象,這里會去匹配具有 app: traefik 標簽的對象。
為了能夠處理其他命名空間中的路由規則,我們可以將這里的 namespaces.from 修改為 All,但是經測試未生效?
下面我們安裝一個簡單的 whoami 服務來進行測試,直接使用下面的資源清單部署對應的服務即可:
- # 01-whoami.yaml
- ---
- kind: Deployment
- apiVersion: apps/v1
- metadata:
- name: whoami
- namespace: kube-system
- spec:
- replicas: 2
- selector:
- matchLabels:
- app: whoami
- template:
- metadata:
- labels:
- app: whoami
- spec:
- containers:
- - name: whoami
- image: containous/whoami
- ports:
- - containerPort: 80
- name: http
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: whoami
- namespace: kube-system
- spec:
- ports:
- - protocol: TCP
- port: 80
- targetPort: http
- selector:
- app: whoami
測試服務部署完成后,我們就可以使用 Gateway API 的方式來進行流量配置了。
部署一個簡單的 Host 主機
在以前的方式中我們會創建一個 Ingress 或 IngressRoute 資源對象,這里我們將部署一個簡單的 HTTPRoute 對象。
- # 02-whoami-httproute.yaml
- apiVersion: networking.x-k8s.io/v1alpha1
- kind: HTTPRoute
- metadata:
- name: http-app-1
- namespace: kube-system
- labels:
- app: traefik
- spec:
- hostnames:
- - "whoami"
- rules:
- - matches:
- - path:
- type: Exact
- value: /
- forwardTo:
- - serviceName: whoami
- port: 80
- weight: 1
上面的 HTTPRoute 資源會捕捉到向 whoami 主機名發出的請求,并將其轉發到上面部署的 whoami 服務,如果你現在對這個主機名進行請求,你會看到典型的 whoami 輸出:
- ➜ kubectl apply -f 02-whoami-httproute.yaml
- ➜ kubectl get httproute -n kube-system
- NAME HOSTNAMES AGE
- http-app-1 ["whoami"] 25s
- # 使用 whoami 這個主機名進行訪問測試
- ➜ curl -H "Host: whoami" http://192.168.31.108
- Hostname: whoami-6b465b89d6-lcg4k
- IP: 127.0.0.1
- IP: ::1
- IP: 10.244.1.87
- IP: fe80::cccc:6aff:fef8:eca9
- RemoteAddr: 10.244.1.85:60384
- GET / HTTP/1.1
- Host: whoami
- User-Agent: curl/7.64.1
- Accept: */*
- Accept-Encoding: gzip
- X-Forwarded-For: 192.168.31.9
- X-Forwarded-Host: whoami
- X-Forwarded-Port: 80
- X-Forwarded-Proto: http
- X-Forwarded-Server: traefik-84d4cccf9c-2pl5r
- X-Real-Ip: 192.168.31.9
另外需要注意上面 HTTPRoute 對象中需要定義 app:traefik 標簽,否則創建的 Gateway 實例不能關聯上。
帶路徑的 Host 主機
上面的例子可以很容易地限制流量只在一個給定的子路徑上進行路由。
- # 03-whoami-httproute-paths.yaml
- ---
- apiVersion: networking.x-k8s.io/v1alpha1
- kind: HTTPRoute
- metadata:
- name: http-app-1
- namespace: kube-system
- labels:
- app: traefik
- spec:
- hostnames:
- - whoami
- rules:
- - forwardTo:
- - port: 80
- serviceName: whoami
- weight: 1
- matches:
- - path:
- type: Exact # 匹配 /foo 的路徑
- value: /foo
創建上面修改后的 HTTPRoute,你會發現之前的請求現在返回404錯誤,而請求 /foo 路徑后綴則返回成功。
- ➜ curl -H "Host: whoami" http://192.168.31.108
- 404 page not found
- ➜ curl -H "Host: whoami" http://192.168.31.108/foo
- Hostname: whoami-6b465b89d6-p5vwz
- IP: 127.0.0.1
- IP: ::1
- IP: 10.244.2.154
- IP: fe80::7045:53ff:fef9:fadc
- RemoteAddr: 10.244.1.85:51686
- GET /foo HTTP/1.1
- Host: whoami
- User-Agent: curl/7.64.1
- Accept: */*
- Accept-Encoding: gzip
- X-Forwarded-For: 192.168.31.9
- X-Forwarded-Host: whoami
- X-Forwarded-Port: 80
- X-Forwarded-Proto: http
- X-Forwarded-Server: traefik-84d4cccf9c-2pl5r
- X-Real-Ip: 192.168.31.9
關于請求的哪些部分可以被匹配的更多信息可以在官方 Gateway APIs 文檔(https://gateway-api.sigs.k8s.io/v1alpha1/api-types/httproute/#rules)中找到。
金絲雀發布
Gateway APIs 規范可以支持的另一個功能是金絲雀發布,假設你想在一個端點上運行兩個不同的服務(或同一服務的兩個版本),并將一部分請求路由到每個端點,則可以通過修改你的 HTTPRoute 來實現。
首先,我們需要運行第二個服務,這里我們快速生成一個 Nginx 的實例來進行測試。
- # 03-nginx.yaml
- kind: Deployment
- apiVersion: apps/v1
- metadata:
- name: nginx
- namespace: kube-system
- spec:
- replicas: 2
- selector:
- matchLabels:
- app: nginx
- template:
- metadata:
- labels:
- app: nginx
- spec:
- containers:
- - name: nginx
- image: nginx
- ports:
- - containerPort: 80
- name: http
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: nginx
- namespace: kube-system
- spec:
- ports:
- - protocol: TCP
- port: 80
- targetPort: http
- selector:
- app: nginx
接著我們修改前面的 HTTPRoute 資源對象,其中有一個 weight 選項,可以為兩個服務分別分配不同的權重,如下所示:
- # 04-whoami-nginx-canary.yaml
- ---
- apiVersion: networking.x-k8s.io/v1alpha1
- kind: HTTPRoute
- metadata:
- labels:
- app: traefik
- name: http-app-1
- namespace: kube-system
- spec:
- hostnames:
- - whoami
- rules:
- - forwardTo:
- - port: 80
- serviceName: whoami
- weight: 3 # 3/4 的請求到whoami
- - port: 80
- serviceName: nginx
- weight: 1 # 1/4 的請求到whoami
創建上面的 HTTPRoute 后,現在我們可以再次訪問 whoami 服務,正常我們可以看到有大約 25% 的請求會看到 Nginx 的響應,而不是 whoami 的響應。
到這里我們就使用 Traefik 來測試了 Kubernetes Gateway APIs 的使用。目前,Traefik 對 Gateway APIs 的實現是基于 v1alpha1 版本的規范,目前最新的規范是 v1alpha2,所以和最新的規范可能有一些出入的地方。