# 認識與實作 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.
## 關鍵角色與人物

### 背景
<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)

`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` 環境變數。

```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

### 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`,在多個後端之間進行流量分配時使用。

#### 查看安装的 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:

在 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. 檢視請求流程

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