# 認識與實作 Kubernetes Gateway API <style> .indent-title-1{ margin-left: 1em; } .indent-title-2{ margin-left: 2em; } .indent-title-3{ margin-left: 3em; } </style> [Gateway API](https://gateway-api.sigs.k8s.io/) is an official Kubernetes project focused on **L4 and L7 routing in Kubernetes**. This project represents the next generation of Kubernetes Ingress, Load Balancing, and Service Mesh APIs. From the outset, it has been designed to be generic, expressive, and role-oriented. ## 關鍵角色與人物 ![image](https://hackmd.io/_uploads/SkOATwrrkl.png) ### 背景 <div class="indent-title-1"> 在 Kubernetes 最初的設計中,Ingress 與 Service 這兩種資源是基於一種使用模式:建立這些 Services 和 Ingresses 的 developers,擁有對定義和公開其應用程式給使用者的所有控制權。 然而,實際上叢集和基礎設施往往是共用的,這點在原本的 Ingress 模型中並沒有被很好地呈現。關鍵在於:當基礎設施被多個使用者共用時,每個使用者的關注點並不相同。要讓基礎設施專案成功,就必須同時滿足所有使用者的需求。 這就引出了核心挑戰:**如何在滿足基礎設施使用者的彈性需求的同時,仍然保有基礎設施擁有者的控制權?** 為了在可用性、彈性以及控制之間取得平衡,Gateway API 透過定義數個不同的角色和對應的人物,作為討論不同使用者需求的工具。Gateway API 的設計工作特別以這些人物為基礎。 需要注意的是,實際情況中(依照環境而異),同一個人可能同時扮演多個角色,下文將有詳細討論。 </div> ### 主要角色與人物 <div class="indent-title-1"> Gateway API 定義了三種角色及其對應的人物: 1. **Ian (he/him)** - 是基礎設施提供者 ( Infrastructure Provider ),負責維護能讓多個隔離的叢集服務多個租戶的基礎設施。 - 他不受制於任何單一租戶,而是關心所有租戶的整體需求。 - Ian 通常在雲端提供商(AWS、Azure、GCP ...)或平台即服務(PaaS)提供商工作。 2. **Chihiro (they/them)** - 是叢集營運者(cluster operator),負責管理叢集並確保能滿足各類使用者的需求。 - 通常會關注政策(policy)、網路存取、應用程式權限等問題。 - 同樣地,Chihiro 不替叢集的任何單一用戶服務,而是確保叢集能符合所有用戶的需要。 3. **Ana (she/her)** - 是應用程式開發者 ( Application developer ) ,負責在叢集內建立並管理應用程式。 - 就 Gateway API 而言,Ana 需要設定諸如逾時(timeout)、請求匹配/過濾,以及如何將服務組合(例如設定路徑將流量導向後端)等。 - Ana 與其他人物不同,她主要關注的是應用程式本身要達成的業務目標,而非 Kubernetes 或 Gateway API。事實上,她可能覺得 Gateway API 和 Kubernetes 只是阻礙她完成工作的「摩擦」。 根據實際環境,不同角色可能由同一個人擔任: - 若將所有角色都賦予同一位使用者,這就複刻了所謂「自行服務(self-service)」的模式,小型新創公司在裸機(bare metal)上跑 Kubernetes 時,可能就會這麼做。 - 比較常見的狀況是小型新創公司使用雲端提供商的叢集。此時,Ana 與 Chihiro 的角色可能由同一個人擔任,而 Ian 則是雲端提供商的員工(或是一個自動化流程)。 - 在更大的組織裡,我們預期上述三個人物會分別由不同的人擔任(而且可能隸屬於不同部門,互相之間直接接觸並不多)。 </div> ## Resource model 在資源模型中,有三種主要物件: * GatewayClass:定義了擁有共同設定與行為的一組 Gateways。 * Gateway:提出一個「流量切入點」的需求,能將外部流量轉譯至叢集中的 Service。 * Routes:描述從 Gateway 進來的流量如何對應(映射)到各個 Service。 ### GatewayClass <div class="indent-title-1"> GatewayClass 定義了一組具備共同設定與行為的 Gateway。每個 GatewayClass 都會由單一 controller 來處理,儘管一個 controller 也可以同時管理多個 GatewayClass。 GatewayClass 是一種叢集層級(cluster-scoped)的資源。要讓 Gateway 正常運作,系統中至少必須定義一個 GatewayClass。任何實作了 Gateway API 的 controller,都是透過提供對應的 GatewayClass 資源,讓使用者在其 Gateway 中引用。 這與 Ingress 的 IngressClass、以及 PersistentVolume 的 StorageClass 類似。在 Ingress v1beta1 中,與 GatewayClass 最相近的是 `ingress-class` annotation;而在 Ingress v1 中,最接近的則是 IngressClass 這個物件。 </div> ### Gateway <div class="indent-title-1"> Gateway 用來描述叢集中流量如何被轉譯到 Service。換句話說,它定義了一種要求,能把「外面不知道 Kubernetes 的地方」發過來的流量,轉譯到「能理解 Kubernetes 的地方」。例如,從雲端負載平衡器(cloud load balancer)、叢集內的代理(in-cluster proxy),或是外部硬體負載平衡器(external hardware load balancer)傳送到 Kubernetes Service 的流量。雖然很多情境中,客戶端的流量可能來自叢集「外部」,但這並不是必要條件。 Gateway 定義了一個對「特定負載平衡器設定」的請求,該設定實作了 GatewayClass 的設定與行為約定。這個資源可以由操作人員(operator)直接建立,或者由管理 GatewayClass 的 controller 來建立。 由於 Gateway 規範(spec)是為了捕捉使用者的意圖,因此它可能不包含所有屬性的完整規範。例如,使用者可能略過像是位址(addresses)、TLS 設定這類的欄位。如此一來,負責管理 GatewayClass 的控制器就能為使用者填入這些設定,讓整個規範更具可攜性。這項行為會透過 GatewayClass 的狀態(Status)物件來說明。 一個 Gateway 可以附加到一個或多個 `Route references` ,這些 Route 用於將部分流量導向至特定服務。 可以說成 GatewayClass 的具體實現,聲明後由 GatewayClass 的 Infrastructure Provider 提供一個具體存在的 Pod,充當了進入 Kubernetes 集群的流量的入口,負責流量接入以及往後轉發,同時還可以起到一個初步過濾的效果。 </div> ### Route Resources <div class="indent-title-1"> Route resources define protocol-specific rules for mapping requests from a Gateway to Kubernetes Services. #### HTTPRoute <div class="indent-title-1"> 定義了與 HTTP 有關的規則,用於將來自 Gateway listener 的流量,對應到後端網路端點的表示方式。這些端點通常以 Service 的形式呈現。 </div> #### Route summary table | Object | OSI Layer | Routing Discriminator | TLS Support | Purpose | |-----------|------------------------------|---------------------------------------|--------------------------|-----------------------------------------------------------------------------------------| | HTTPRoute | Layer 7 | Anything in the HTTP Protocol | Terminated only | HTTP and HTTPS Routing | | TLSRoute | Somewhere between layer 4 and 7 | SNI or other TLS properties | Passthrough or Terminated | Routing of TLS protocols including HTTPS where inspection of the HTTP stream is not required. | | TCPRoute | Layer 4 | destination port | Passthrough or Terminated | Allows for forwarding of a TCP stream from the Listener to the Backends | | UDPRoute | Layer 4 | destination port | None | Allows for forwarding of a UDP stream from the Listener to the Backends. | | GRPCRoute | Layer 7 | Anything in the gRPC Protocol | Terminated only | gRPC Routing over HTTP/2 and HTTP/2 cleartext | </div> # Gateway API Quick Start ## Step1: Install the Gateway API CRDs and Envoy Gateway ```bash! $ kubectl apply --server-side -f https://github.com/envoyproxy/gateway/releases/download/v1.2.4/install.yaml ``` 查看安装的 CRDs 資源 ``` $ kubectl get crd | grep networking.k8s.io ``` 執行結果 : ``` backendlbpolicies.gateway.networking.k8s.io 2024-12-22T11:11:31Z backendtlspolicies.gateway.networking.k8s.io 2024-12-22T11:11:31Z gatewayclasses.gateway.networking.k8s.io 2024-12-22T11:11:31Z gateways.gateway.networking.k8s.io 2024-12-22T11:11:31Z grpcroutes.gateway.networking.k8s.io 2024-12-22T11:11:31Z httproutes.gateway.networking.k8s.io 2024-12-22T11:11:31Z referencegrants.gateway.networking.k8s.io 2024-12-22T11:11:31Z tcproutes.gateway.networking.k8s.io 2024-12-22T11:11:31Z tlsroutes.gateway.networking.k8s.io 2024-12-22T11:11:31Z udproutes.gateway.networking.k8s.io 2024-12-22T11:11:31Z ``` 查看安装的 envoy controller 運作狀態是否正常 ``` $ kubectl get pod -n envoy-gateway-system ``` 執行結果 : ``` NAME READY STATUS RESTARTS AGE envoy-gateway-67469b6f59-n4js6 1/1 Running 0 73s ``` ## Step2: Install the GatewayClass, Gateway, HTTPRoute and example app ### 1. 安裝 GatewayClass ```yaml! $ cat <<EOF | kubectl apply -f - apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: eg spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller EOF ``` `GatewayClass` 是 Cluster level 的資源, `GatewayClass` 規格定義如下: - `spec` : Spec 定義了 `GatewayClass` 期望的狀態 - `controllerName` : ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. 此欄位不可變更,且不能為空。 - 更多有關 `GatewayClass` 的定義請參考此[連結](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GatewayClass) ![image](https://hackmd.io/_uploads/rJvGdxtrkg.png) `controllername` 被定義在 `envoy-gateway-system` namespace 底下的 `envoy-gateway-config` configmap ``` kubectl -n envoy-gateway-system get cm envoy-gateway-config -o yaml ``` 執行結果 : ```yaml apiVersion: v1 data: envoy-gateway.yaml: | apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyGateway gateway: controllerName: gateway.envoyproxy.io/gatewayclass-controller logging: level: default: info provider: kubernetes: rateLimitDeployment: container: image: docker.io/envoyproxy/ratelimit:49af5cca patch: type: StrategicMerge value: spec: template: spec: containers: - imagePullPolicy: IfNotPresent name: envoy-ratelimit shutdownManager: image: envoyproxy/gateway:v1.2.4 ... 以下省略 ``` #### 檢視 GatewayClass ``` $ kubectl get gatewayclass ``` 執行結果 : ``` NAME CONTROLLER ACCEPTED AGE eg gateway.envoyproxy.io/gatewayclass-controller True 13m ``` 在本範例中,已實作 Gateway API 的 controller 被設定為管理 GatewayClasses,controller 名稱為 `gateway.envoyproxy.io/gatewayclass-controller`。 此類別的 Gateway 將由實作的 controller 管理。 ### 2. 安裝範例 Application 依序建立: 1. **backend ServiceAccount**:供權限管理使用。 2. **backend Service**:監聽 `3000` tcp/port 並依據標籤 `app: backend` 對應到後端 Pod。 3. **backend Deployment**:啟動一個網站,並自動設定 `POD_NAME`、`NAMESPACE` 環境變數。 ![image](https://hackmd.io/_uploads/rJOTcztrJl.png) ```yaml! $ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ServiceAccount metadata: name: backend --- apiVersion: v1 kind: Service metadata: name: backend labels: app: backend service: backend spec: ports: - name: http port: 3000 targetPort: 3000 selector: app: backend --- apiVersion: apps/v1 kind: Deployment metadata: name: backend spec: replicas: 1 selector: matchLabels: app: backend version: v1 template: metadata: labels: app: backend version: v1 spec: serviceAccountName: backend containers: - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e imagePullPolicy: IfNotPresent name: backend ports: - containerPort: 3000 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace EOF ``` #### 檢視 Application 部署狀態 ``` $ kubectl get pods,svc -l app=backend ``` 執行結果 : ``` NAME READY STATUS RESTARTS AGE pod/backend-765694d47f-4rcr6 1/1 Running 0 28s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/backend ClusterIP 10.98.32.77 <none> 3000/TCP 28s ``` ### 3. 安裝 Gateway ```yaml! $ cat <<EOF | kubectl apply -f - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: eg spec: gatewayClassName: eg listeners: - name: http protocol: HTTP port: 80 EOF ``` `Gateway` 規格定義如下: * **Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses.** * `GatewayClassName` : 定義此 `Gateway` 使用的 `GatewayClass` 物件的名稱。 * `Listeners` : 定義 `listener` 的 `hostnames`、`ports`、`protocol`、`termination`、`TLS settings`,以及哪些 `routes` 可以附加到 `listener`。 * Listener 體現了 `Gateway` 接受網路連線的邏輯端點概念。 * `Addresses` : 定義此 `Gateway` 所要求的 IP 位址,If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. #### 檢視 Gateway ``` $ kubectl get gateway ``` 執行結果 : ``` NAME CLASS ADDRESS PROGRAMMED AGE eg eg False 16m ``` #### 檢查流量入口的 Pod ```! $ kubectl -n envoy-gateway-system get pods \ -o wide \ -l "app.kubernetes.io/component=proxy" ``` 執行結果 : <pre> NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES envoy-default-eg-e41e7b31-75c864f477-4drs5 2/2 Running 0 23s <font color=red>10.244.33.16</font> tkdev-worker1 <none> <none> </pre> #### 檢查流量入口 Pod 的 Service ```! $ kubectl -n envoy-gateway-system get svc,endpointslices \ -l "app.kubernetes.io/component=proxy" ``` 執行結果 : <pre> NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/envoy-default-eg-e41e7b31 LoadBalancer 10.98.32.24 <pending> 80:31375/TCP 65s NAME ADDRESSTYPE PORTS ENDPOINTS AGE endpointslice.discovery.k8s.io/envoy-default-eg-e41e7b31-qswvj IPv4 10080 <font color=red>10.244.33.16</font> 65s </pre> </pre> </pre> </pre> 可以看到 Envoy 在使用者建立 `Gateway` 後,會自動建立接收外部流量的 pod 跟 Service ![image](https://hackmd.io/_uploads/Sk9fTMKBke.png) ### 4. 設定與安裝 HttpRoute HTTPRoute 提供一種路由 HTTP 請求的方式。 這包括根據 hostname、path、header 或 query param 來匹配請求的功能。Filters 可用於指定額外的處理步驟。Backends 指定匹配的請求應被路由到哪裡。 ```yaml! $ cat <<EOF | kubectl apply -f - apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: backend spec: parentRefs: - name: eg hostnames: - "www.example.com" rules: - backendRefs: - group: "" kind: Service name: backend port: 3000 weight: 1 matches: - path: type: PathPrefix value: / EOF ``` - `spec` : 定義 `HTTPRoute` 的期望狀態。 - `parentRefs` : 用來指定一個路由(Route)想要附加的資源(通常是 Gateways)。 - `hostnames` : Hostnames 是用來定義一組主機名稱,用來匹配 HTTP 請求中的 Host Header,以選擇適合的 HTTPRoute 處理該請求。 - 在進行匹配時,實作(例如 Gateway 或控制器)必須忽略 HTTP Host 標頭中包含的埠號(Port Value)。 - 如果沒有進行任何與標頭修改相關的配置,實作必須原樣將 Host 標頭轉發給後端(Backend),不進行任何更改。 - 如果 Listener 和 HTTPRoute 都指定了主機名稱,則 HTTPRoute 必須至少有一個相交的主機名稱才能附加到 Listener。 - `rules` : HTTPRouteRule 是用來定義如何處理 HTTP 請求的規則,主要分為以下三個部分: 1. 匹配條件 (`matches`): 根據請求的特定條件(例如 URL 路徑、方法、標頭等)來判斷是否符合規則。 - `path`:根據 URL 路徑做為的匹配條件。 - `type`:匹配類型,這裡是 `PathPrefix`,表示路徑前綴匹配。 - `value`:路徑值,這裡是 `/`,表示所有以 `/` 開頭的路徑都會匹配此規則。 2. 處理方式(`filters`): 指定如何處理這些匹配的請求,比如添加、修改或刪除標頭,執行重新導向等操作。 3. 轉發目標(`backendRefs`): 將處理後的請求轉發給指定的 API 物件,例如後端服務或其他資源。 - `group`:API 群組,這裡是空字串,表示 Core API 群組。 - `kind`:資源類型,這裡是 `Service`,表示 Kubernetes Service。 - `name`:Service 的名稱,這裡是 `backend`。 - `port`:服務的 port number,這裡是 `3000`。 - `weight`:權重,這裡是 `1`,在多個後端之間進行流量分配時使用。 ![image](https://hackmd.io/_uploads/H1cbRMKrke.png) #### 查看安装的 httproute 資源 ``` $ kubectl get httproute ``` 執行結果 : ``` NAME HOSTNAMES AGE backend ["www.example.com"] 9s ``` ### 5. 測試連線並檢視請求流程 如果 K8s 有安裝 MetalLB,則可跳過 5.1 和 5.2 步驟,直接執行以下命令 :::spoiler With External LoadBalancer Support :::info 取得 Envoy Service 的 external IP,請執行: ``` $ export GATEWAY_HOST=$(kubectl get gateway/eg \ -o jsonpath='{.status.addresses[0].value}') ``` 透過 Envoy proxy 連線範例應用 Application ```! $ curl --verbose --header "Host: www.example.com" http://$GATEWAY_HOST/get ``` ::: #### 5.1. 取得範例 Gateway 所建立的 Envoy Service 的名稱: 就是 Envoy 在使用者建立 Gateway 後,會自動建立接收外部流量的 pod 跟 Service,先把這個 Service 的名稱設為變數 ```bash! $ export ENVOY_SERVICE=$(kubectl get svc -n envoy-gateway-system --selector=gateway.envoyproxy.io/owning-gateway-namespace=default,gateway.envoyproxy.io/owning-gateway-name=eg -o jsonpath='{.items[0].metadata.name}') ``` #### 5.2. 將 Envoy Service Port forward 出去: ```bash! $ kubectl -n envoy-gateway-system port-forward \ --address 192.168.61.128 \ service/${ENVOY_SERVICE} 8888:80 & ``` > `--address`, Addresses to listen on (comma separated),`192.168.61.128` 為 tkcdc 主機的 IP 執行結果 : ``` [1] 32937 Forwarding from 192.168.61.128:8888 -> 10080 Handling connection for 8888 ``` #### 5.3. 透過 Envoy proxy 連線範例應用 App: ![image](https://hackmd.io/_uploads/BkDyJQtHke.png) 在 Taroko K8s 叢集外部執行以下命令 : ``` $ curl --verbose --header "Host: www.example.com" http://192.168.61.128:8888/get ``` 執行結果 : ``` * Trying 192.168.61.128:8888... * connect to 192.168.61.128 port 8888 from 0.0.0.0 port 9362 failed: Connection refused * Failed to connect to 192.168.61.128 port 8888 after 2037 ms: Could not connect to server * closing connection #0 curl: (7) Failed to connect to 192.168.61.128 port 8888 after 2037 ms: Could not connect to server C:\Users\antony>curl --verbose --header "Host: www.example.com" http://192.168.61.128:8888/get * Trying 192.168.61.128:8888... * Connected to 192.168.61.128 (192.168.61.128) port 8888 * using HTTP/1.x > GET /get HTTP/1.1 > Host: www.example.com > User-Agent: curl/8.10.1 > Accept: */* > < HTTP/1.1 200 OK < content-type: application/json < x-content-type-options: nosniff < date: Wed, 25 Dec 2024 02:17:01 GMT < content-length: 456 < { "path": "/get", "host": "www.example.com", "method": "GET", "proto": "HTTP/1.1", "headers": { "Accept": [ "*/*" ], "User-Agent": [ "curl/8.10.1" ], "X-Envoy-Internal": [ "true" ], "X-Forwarded-For": [ "10.244.33.9" ], "X-Forwarded-Proto": [ "http" ], "X-Request-Id": [ "1788f941-4043-42b7-b7ea-893bf3f4a449" ] }, "namespace": "default", "ingress": "", "service": "", "pod": "backend-765694d47f-tv9ft" }* Connection #0 to host 192.168.61.128 left intact ``` #### 5.4. 檢視請求流程 ![image](https://hackmd.io/_uploads/By452lOB1x.png) 在此範例中,當 Gateway 作為反向代理時,請求流程如下: 1. 客戶端開始準備一個指向 `http://www.example.com` 的 HTTP 請求。 2. 客戶端的 DNS 解析器查詢目標名稱,並獲取與 Gateway 關聯的一個或多個 IP 位址。 3. 客戶端向 Gateway 的 IP 位址發送請求;反向代理接收該 HTTP 請求,並使用 `Host:` 標頭來匹配從 Gateway 和附加的 HTTPRoute 衍生的配置。 4. (可選)反向代理可以根據 HTTPRoute 的匹配規則,對請求的標頭和/或路徑進行匹配。 5. (可選)反向代理可以修改該請求;例如,根據 HTTPRoute 的過濾規則添加或刪除標頭。 6. 最後,反向代理將該請求轉發給一個或多個後端服務。 ### 6. 清除環境 移除 Qucik Start 中的所有內容 : ```! $ kubectl delete -f \ https://github.com/envoyproxy/gateway/releases/download/v1.2.4/quickstart.yaml \ --ignore-not-found=true ``` 刪除 Gateway API CRD 和 Envoy Gateway: ```! $ kubectl delete -f \ https://github.com/envoyproxy/gateway/releases/download/v1.2.4/install.yaml \ --ignore-not-found=true ``` ## 參考文件 - [Introduction - Gateway API Docs](https://gateway-api.sigs.k8s.io/) - [Gateway API - K8s Docs](https://kubernetes.io/docs/concepts/services-networking/gateway/) - [Kubernetes Gateway API 深入解讀與落地指南 - 雲原生社區](https://cloudnative.to/blog/kubernetes-gateway-api-explained/) - [Quickstart - Envoy Gateway Docs](https://gateway.envoyproxy.io/docs/tasks/quickstart/)