再見 Kubernetes Ingress,是時候擁抱更強大的繼任者 Gateway API 了
Gateway API(之前叫 Service API)是由 SIG-NETWORK 社區(qū)管理的開源項目,項目地址:https://gateway-api.sigs.k8s.io/。
主要原因是 Ingress 資源對象不能很好的滿足網(wǎng)絡需求,很多場景下 Ingress 控制器都需要通過定義 annotations 或者 crd 來進行功能擴展,這對于使用標準和支持是非常不利的,新推出的 Gateway API 旨在通過可擴展的面向角色的接口來增強服務網(wǎng)絡。
Gateway API 是 Kubernetes 中的一個 API 資源集合,包括 GatewayClass、Gateway、HTTPRoute、TCPRoute、Service 等,這些資源共同為各種網(wǎng)絡用例構建模型。
Gateway API的價值與定位
- 提供跨Namespace的一種Ingress機制:讓多個跨Namespace的服務,可以分享一個L7 LoadBalancer
- 可實現(xiàn)多租戶的一種Ingress機制:提供HttpRoute與Gateway分離的抽象組件,讓設計路由(HttpRoute)的人,可以共享一個L7 LoadBalancer,
- 藍綠部署金絲雀部署時為使用同一個命名空間下的部署,可以簡單的定義在HttpRoute中
- 基于Hostname、Header、或Subpath的服務,使用AB測試等等。
Gateway API 介紹
網(wǎng)關API主要分為以下幾個角色:
- GatewayClass(由Cloud或K8s提供者提供),
- Gateway(由網(wǎng)管平臺公司自建GatewayClass),
- HTTPRoute(由開發(fā)者團隊服務部署者選擇對應的網(wǎng)關對接)
目前 GKE 提供了四種網(wǎng)關類,分別是:
- gke-l7-rilb建立在內部 HTTP(S) 負載均衡上的區(qū)域內部 HTTP(S) 負載均衡器(單叢集Internal LoadBalancer)
- gke-l7-gxlb建立在外部 HTTP(S) 負載均衡之上的全球外部 HTTP(S) 負載均衡器(單叢集External LoadBalancer)
- gke-l7-rilb-mc基于Internal HTTP(S) Load Balancing的多集群區(qū)域負載均衡器(多叢集Internal LoadBalancer)
- gke-l7-gxlb-mc基于External HTTP(S) Load Balancing的多集群全局負載均衡器(多叢集External LoadBalancer)
網(wǎng)關部署
目前GKE的Gateway APIK8S 1.20以上的版本,如果使用GKE發(fā)布頻道需要捧油,現(xiàn)在需要使用RAPID區(qū)域,同時僅在以下提供公開Beta測試:
- us-west1
- us-east1
- us-central1
- europe-west4
- europe-west3
- europe-west2
- europe-west1
- asia-southeast1
下面的測試,我們選擇先測試Interal-LoadBalancer并嘗試下面的拓撲圖:
部署私有 GKE
我們先開發(fā)一個私人GKE集群,可以參照我提供的Terraform腳本,請直接將shawn.tfvars改為符合你集群的參數(shù),另外,由于我使用遠端GCS也請設置保存 tfstate,修改config.tf里面的bucket名稱。其他經(jīng)常設置和私有GKE類似,只需要使用內部LB而增加了一個proxy-only的子網(wǎng)給Envoy使用。
部署網(wǎng)關
根據(jù)上述的第三個階段,我們先部署Gateway API的CRD檔案:
- kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.3.0" \
- | kubectl apply -f -
- # 部署成功後,可使用下方命令檢視單叢集internal/external GatewayClass
- private-gke-tf % kubectl get gatewayclass
- NAME CONTROLLER AGE
- gke-l7-gxlb networking.gke.io/gateway 23h
- gke-l7-rilb networking.gke.io/gateway 23h
下一步我們準備部署網(wǎng)關,使用 kubectl apply -f 部署底下YAML:
- kind: Gateway
- apiVersion: networking.x-k8s.io/v1alpha1
- metadata:
- name: internal-http
- spec:
- gatewayClassName: gke-l7-rilb
- listeners:
- - protocol: HTTP
- port: 80
- routes:
- kind: HTTPRoute
- selector:
- matchLabels:
- gateway: internal-http
- namespaces:
- from: "All"
因為Gateway可以接受三個關聯(lián)模式,基于 kind,selector,以及namespace。
- kind:以下可選任一種Route: HTTPRoute, TCPRoute, or customRoute
- 標簽:通過選擇或選擇對應的標簽
- namespaces:貫穿 namespaces.from 選擇Route所在的namespace,預設是跟Gateway在同一個namespace的Route
因為我們想創(chuàng)建一個跨命名空間的共享網(wǎng)關,所以我們將 namespaces.from 改成 All。
部署成功后,可以通過以下命令來查看:
- private-gke-tf % kubectl describe gateway internal-http
- Events:
- Type Reason Age From Message
- ---- ------ ---- ---- -------
- Normal SYNC 4m59s (x396 over 23h) sc-gateway-controller SYNC on default/internal-http was a success
部署服務+HTTPRoute
接下來我們快速部署測試的兩個服務,其中 store 服務部署在默認命名空間,site 服務部署在 site 命名空間。部署對應的 HTTPRoute,其中 http-route.yaml 適用于 store 服務的:
- kind: HTTPRoute
- apiVersion: networking.x-k8s.io/v1alpha1
- metadata:
- name: store
- labels:
- gateway: internal-http
- spec:
- hostnames:
- - "store.example.com"
- rules:
- - forwardTo:
- - serviceName: store-v1
- port: 8080
- weight: 50
- - serviceName: store-v2
- port: 8080
- weight: 50
- - matches:
- - headers:
- type: Exact
- values:
- env: canary
- forwardTo:
- - serviceName: store-v2
- port: 8080
- - matches:
- - path:
- type: Prefix
- value: /de
- forwardTo:
- - serviceName: store-german
- port: 8080
我們針對 store 服務,故意開發(fā)了三個不同的后端服務,一次來測試HTTPRoute的不同能力
- (1)主機名匹配:通過不同的主機名(如store.example.com or site.example.com)將用戶連接路由到指定的后臺服務
- (2.1) header match: 如果請求 header 中包含 env: canary 這樣的信息,就會被路由到 store-v2 服務
- (2.2)前綴匹配:若請求路徑前綴是 /de,則會被路由到 store-german 服務
- (3)默認路由:根據(jù)權重,自動均衡到指定的 store-v1 與 store-v2 的兩個后臺服務
而 http-service.yaml 是用于配置服務的,由于 HTTPRoute 與 Gateway 所在的命名空間不同,因此必須引入一個 gatewayRefs 參數(shù):
- kind: HTTPRoute
- apiVersion: networking.x-k8s.io/v1alpha1
- metadata:
- name: site
- namespace: site
- labels:
- gateway: internal-http
- spec:
- gateways:
- allow: FromList
- gatewayRefs:
- - name: internal-http
- namespace: default
- hostnames:
- - "site.example.com"
- rules:
- - forwardTo:
- - serviceName: site-v1
- port: 8080
由于我們使用的是 Internal LoadBalancer,所以我們需要開一臺 GCE VM 來進行測試,由于我們使用的是 private cluster,所以要記得 GCE VM 和 private GKE cluster 在同一個 VPC 中。
在 K8S client上,獲取 Internal LoadBalancer 的IP地址:
- kubectl get gateway internal-http -o jsonpath='{.status.addresses[0].value}'
- 10.81.68.140
在 GCE VM 上,我們來測試上面我們定義的 HttpRoute:
(1) 按照 Hostname 來路由服務:通過定義 hostname 為 site.example.com 來路由到另一個 namespace 的K8S服務中:
- shawnho@jumper2:~$ curl -H "host: site.example.com" 10.81.68.140
- {
- "cluster_name": "pgke-1",
- "host_header": "site.example.com",
- "metadata": "site-v1",
- "node_name": "gke-pgke-1-cluster-runtime-425beb23-vx9j.c.shawn-demo-2021.internal",
- "pod_name": "site-v1-86dc4b4fbc-4g6jr",
- "pod_name_emoji": "🌾",
- "project_id": "shawn-demo-2021",
- "timestamp": "2021-07-18T15:26:15",
- "zone": "asia-southeast1-a"
- }
(2) 基于Hostname: “store.example.com”,再分別測試 (2.1) Header 與 (2.2) Prefix 前綴機制:
- # 測試header match
- shawnho@jumper2:~$ curl -H "host: store.example.com" -H "env: canary" 10.81.68.140
- {
- "cluster_name": "pgke-1",
- "host_header": "store.example.com",
- "metadata": "store-v2",
- "node_name": "gke-pgke-1-cluster-runtime-425beb23-vx9j.c.shawn-demo-2021.internal",
- "pod_name": "store-v2-6856f59f7f-49fv8",
- "pod_name_emoji": "🎹",
- "project_id": "shawn-demo-2021",
- "timestamp": "2021-07-18T15:33:04",
- "zone": "asia-southeast1-a"
- }
- # 測試prefix match
- shawnho@jumper2:~$ curl -H "host: store.example.com" 10.81.68.140/de
- {
- "cluster_name": "pgke-1",
- "host_header": "store.example.com",
- "metadata": "Gutentag!",
- "node_name": "gke-pgke-1-cluster-runtime-425beb23-vx9j.c.shawn-demo-2021.internal",
- "pod_name": "store-german-66dcb75977-ttngz",
- "pod_name_emoji": "💇🏼♀",
- "project_id": "shawn-demo-2021",
- "timestamp": "2021-07-18T15:32:15",
- "zone": "asia-southeast1-a"
- }
(3) 通過基于 Host: store.example.com 下的 Default Route 按權重(1:1)路由服務:
- # 測試default route
- shawnho@jumper2:~$ curl -H "host: store.example.com" 10.81.68.140
- {
- "cluster_name": "pgke-1",
- "host_header": "store.example.com",
- "metadata ": "store-v1",
- "node_name": "gke-pgke-1-cluster-runtime-425beb23-vx9j.c.shawn-demo-2021.internal",
- "pod_name": "store-v1-65b47557df-rv65j ",
- "pod_name_emoji": "🧘🏿♂️",
- "project_id": "shawn-demo-2021",
- "timestamp": "2021-07-18T15:32:09",
- "zone": "asia-southeast1-a”
- }
- shawnho@jumper2:~$ curl -H "host: store.example.com" 10.81.68.140
- {
- "cluster_name": "pgke-2",
- "host_header": "store.example.com",
- "metadata": "store -v2",
- "node_name": "gke-pgke-2-cluster-runtime-e7f1b4b7-wg5a.c.shawn-demo-2021.internal",
- "pod_name": "store-v2-6856f59f7f-gkkct",
- "pod_name_emoji ": "👈🏼",
- "project_id": "shawn-demo-2021",
- "timestamp": "2021-07-18T15:32:29",
- "zone": "asia-southeast1-a"
- }
到這里可以看到測試成功了!