Skip to content

antony@notes:~/kubernetes$ cat "認識與實作-Kubernetes-Gateway-API.md"

認識與實作 Kubernetes Gateway API

2024-12-25· kubernetes

認識與實作 Kubernetes Gateway API

Gateway API 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

背景

在 Kubernetes 最初的設計中,Ingress 與 Service 這兩種資源是基於一種使用模式:建立這些 Services 和 Ingresses 的 developers,擁有對定義和公開其應用程式給使用者的所有控制權。

然而,實際上叢集和基礎設施往往是共用的,這點在原本的 Ingress 模型中並沒有被很好地呈現。關鍵在於:當基礎設施被多個使用者共用時,每個使用者的關注點並不相同。要讓基礎設施專案成功,就必須同時滿足所有使用者的需求。

這就引出了核心挑戰:如何在滿足基礎設施使用者的彈性需求的同時,仍然保有基礎設施擁有者的控制權?

為了在可用性、彈性以及控制之間取得平衡,Gateway API 透過定義數個不同的角色和對應的人物,作為討論不同使用者需求的工具。Gateway API 的設計工作特別以這些人物為基礎。

需要注意的是,實際情況中(依照環境而異),同一個人可能同時扮演多個角色,下文將有詳細討論。

主要角色與人物

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 則是雲端提供商的員工(或是一個自動化流程)。
  • 在更大的組織裡,我們預期上述三個人物會分別由不同的人擔任(而且可能隸屬於不同部門,互相之間直接接觸並不多)。

Resource model

在資源模型中,有三種主要物件:

  • GatewayClass:定義了擁有共同設定與行為的一組 Gateways。
  • Gateway:提出一個「流量切入點」的需求,能將外部流量轉譯至叢集中的 Service。
  • Routes:描述從 Gateway 進來的流量如何對應(映射)到各個 Service。

GatewayClass

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 這個物件。

Gateway

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 集群的流量的入口,負責流量接入以及往後轉發,同時還可以起到一個初步過濾的效果。

Route Resources

Route resources define protocol-specific rules for mapping requests from a Gateway to Kubernetes Services.

HTTPRoute

定義了與 HTTP 有關的規則,用於將來自 Gateway listener 的流量,對應到後端網路端點的表示方式。這些端點通常以 Service 的形式呈現。

Route summary table

ObjectOSI LayerRouting DiscriminatorTLS SupportPurpose
HTTPRouteLayer 7Anything in the HTTP ProtocolTerminated onlyHTTP and HTTPS Routing
TLSRouteSomewhere between layer 4 and 7SNI or other TLS propertiesPassthrough or TerminatedRouting of TLS protocols including HTTPS where inspection of the HTTP stream is not required.
TCPRouteLayer 4destination portPassthrough or TerminatedAllows for forwarding of a TCP stream from the Listener to the Backends
UDPRouteLayer 4destination portNoneAllows for forwarding of a UDP stream from the Listener to the Backends.
GRPCRouteLayer 7Anything in the gRPC ProtocolTerminated onlygRPC Routing over HTTP/2 and HTTP/2 cleartext

Gateway API Quick Start

Step1: Install the Gateway API CRDs and Envoy Gateway

$ 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

$ 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 的定義請參考此連結

image

controllername 被定義在 envoy-gateway-system namespace 底下的 envoy-gateway-config configmap

kubectl -n envoy-gateway-system get cm envoy-gateway-config -o 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_NAMENAMESPACE 環境變數。

image

$ 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

$ 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 : 定義 listenerhostnamesportsprotocolterminationTLS 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"

執行結果 :

NAME                                         READY   STATUS    RESTARTS   AGE   IP             NODE            NOMINATED NODE   READINESS GATES
envoy-default-eg-e41e7b31-75c864f477-4drs5   2/2     Running   0          23s   10.244.33.16   tkdev-worker1              

檢查流量入口 Pod 的 Service

$ kubectl -n envoy-gateway-system get svc,endpointslices \
  -l "app.kubernetes.io/component=proxy"

執行結果 :

NAME                                TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
service/envoy-default-eg-e41e7b31   LoadBalancer   10.98.32.24        80:31375/TCP   65s

NAME                                                             ADDRESSTYPE   PORTS   ENDPOINTS      AGE
endpointslice.discovery.k8s.io/envoy-default-eg-e41e7b31-qswvj   IPv4          10080   10.244.33.16   65s

可以看到 Envoy 在使用者建立 Gateway 後,會自動建立接收外部流量的 pod 跟 Service

image

4. 設定與安裝 HttpRoute

HTTPRoute 提供一種路由 HTTP 請求的方式。 這包括根據 hostname、path、header 或 query param 來匹配請求的功能。Filters 可用於指定額外的處理步驟。Backends 指定匹配的請求應被路由到哪裡。

$ 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

查看安装的 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 的名稱設為變數

$ 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 出去:

$ 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

在 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

在此範例中,當 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

參考文件