# Kubernetes Gateway API * Gateway API 就是用來取代 Ingress 的。 * 在 Kubernetes 中,Gateway API 是一種用於管理和保護 API 流量的軟體。它充當單一入口點,可讓客戶端訪問後端服務。 * Kubernetes Gateway API 是 Kubernetes 1.18 版本引進的一種新的 API 規範,是 Kubernetes 官方正在開發的新的 API,Ingress 是 Kubernetes 已有的 API。Gateway API 會成為 Ingress 的下一代替代方案。Gateway API 提供更豐富的功能,支援 TCP、UDP、TLS 等,不只是 HTTP。Ingress 主要面向 HTTP 流量。Gateway API 具有更強的擴充性,透過 CRD 可以輕易新增特定的 Gateway 類型。 ## 架構圖 ![image](https://hackmd.io/_uploads/ByMs9xzM0.png) ## 運作原理 ![image](https://hackmd.io/_uploads/rJYs5efMR.png) * GatewayClass: 一組共用通用設定和行為的 Gateway 集合,就像 IngressClass、StorageClass 一樣。 * Gateway:就是 GatewayClass 的具體實現,聲明後由 GatewayClass 的基礎設備提供者提供一個具體存在的 Pod,充當了進入 Kubernetes 集群的流量的入口,負責流量接入以及往後轉發,同時還可以起到一個初步過濾的效果。 * HTTPRoute: 定義特定於 HTTP 的規則,用於將流量從 Gateway 導入到應到後端的服務。這些端點通常表示為 Service。 ## 安装 Gateway API CRD 和 Envoy Controller * 需先安裝好 metallb * 安裝標準版 Gateway API CRD,包括功能有 GatewayClass、Gateway、HTTPRoute 和 ReferenceGrant ``` $ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml ``` * 測試版 Gateway API CRD,包括功能有 TCPRoute、TLSRoute、UDPRoute 和 GRPCRoute(未來可能會刪除) ``` $ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/experimental-install.yaml ``` * 安裝 Envoy Controller ``` $ kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/v1.0.1/install.yaml ``` * 查看已安裝的 CRD 資源 ``` $ kubectl get crd |grep networking.k8s.io gatewayclasses.gateway.networking.k8s.io 2024-05-03T05:57:30Z gateways.gateway.networking.k8s.io 2024-05-03T05:57:30Z grpcroutes.gateway.networking.k8s.io 2024-05-03T05:57:30Z httproutes.gateway.networking.k8s.io 2024-05-03T05:57:30Z referencegrants.gateway.networking.k8s.io 2024-05-03T05:57:30Z tcproutes.gateway.networking.k8s.io 2024-05-03T05:57:30Z tlsroutes.gateway.networking.k8s.io 2024-05-03T05:57:31Z udproutes.gateway.networking.k8s.io 2024-05-03T05:57:31Z ``` * 查看安裝的 envoy controller ``` $ kubectl get pod -n envoy-gateway-system NAME READY STATUS RESTARTS AGE envoy-gateway-6dcd84c6c9-gl8kb 2/2 Running 0 108s ``` ## 部屬 gatewayclass * 部屬 gatewayclass ``` $ echo 'apiVersion: gateway.networking.k8s.io/v1beta1 kind: GatewayClass metadata: name: eg spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller' | kubectl apply -f - ``` ``` $ kubectl get gatewayclass NAME CONTROLLER ACCEPTED AGE eg gateway.envoyproxy.io/gatewayclass-controller True 7s ``` ## gateway api 測試 * 部屬測試用 backend deployment ``` $ echo $'apiVersion: v1 kind: Service metadata: name: svc-backend labels: app: backend service: backend spec: ports: - name: http port: 3000 targetPort: 3000 selector: app: backend --- apiVersion: v1 kind: Service metadata: name: svc-backend2 labels: app: backend2 service: backend2 spec: ports: - name: http port: 3000 targetPort: 3000 selector: app: backend2 --- 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: containers: - image: docker.io/taiwanese/echoserver imagePullPolicy: IfNotPresent name: backend ports: - containerPort: 3000 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace --- apiVersion: apps/v1 kind: Deployment metadata: name: backend2 spec: replicas: 1 selector: matchLabels: app: backend2 version: v1 template: metadata: labels: app: backend2 version: v1 spec: containers: - image: docker.io/taiwanese/echoserver imagePullPolicy: IfNotPresent name: backend2 ports: - containerPort: 3000 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace' | kubectl apply -f - ``` ``` $ kubectl get svc,pod NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 76d service/svc-backend ClusterIP 10.43.144.57 <none> 3000/TCP 4m10s service/svc-backend2 ClusterIP 10.43.198.74 <none> 3000/TCP 4m10s NAME READY STATUS RESTARTS AGE pod/backend-6c74b76b4-r6npw 1/1 Running 0 7h52m pod/backend2-67c74bfb48-6q78n 1/1 Running 0 5h54m ``` * 部屬 gateway resource * 設定 gateway 對外開的 port 是 80 ``` $ echo 'apiVersion: gateway.networking.k8s.io/v1beta1 kind: Gateway metadata: name: eg spec: gatewayClassName: eg listeners: - name: http protocol: HTTP port: 80' | kubectl apply -f - ``` ``` $ kubectl get gateway NAME CLASS ADDRESS PROGRAMMED AGE eg eg 192.168.11.160 True 28m ``` * 會在 `envoy-gateway-system` namespace 下建立一個對外的 service,並且導入到 `envoy-default-my-tcp-gateway-28bd5041` 這個 pod。 ``` $ kubectl -n envoy-gateway-system get po,svc NAME READY STATUS RESTARTS AGE pod/envoy-default-my-tcp-gateway-28bd5041-848dd48d74-wcjff 2/2 Running 0 2m20s pod/envoy-gateway-6bccd54479-lqmm5 1/1 Running 0 3m37s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/envoy-default-my-tcp-gateway-28bd5041 LoadBalancer 10.43.20.124 192.168.11.146 8080:32387/TCP,8090:31917/TCP 2m20s service/envoy-gateway ClusterIP 10.43.199.147 <none> 18000/TCP,18001/TCP 3m37s service/envoy-gateway-metrics-service ClusterIP 10.43.9.247 <none> 19001/TCP 3m37s ``` * 部屬 httproute resource * 來自 Gateway 的 HTTP 流量, 如果 Host 的 header 設定為 `www.example.com` 且請求路徑指定為 `/backend`, 將被路由至 svc-backend ,如果請求路徑指定為 `/backend2` 將被路由至 svc-backend2。 ``` $ echo 'apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: backend spec: parentRefs: - name: eg hostnames: - "www.example.com" rules: - backendRefs: - group: "" kind: Service name: svc-backend port: 3000 weight: 1 matches: - path: type: PathPrefix value: /backend - backendRefs: - group: "" kind: Service name: svc-backend2 port: 3000 weight: 1 matches: - path: type: PathPrefix value: /backend2' | kubectl apply -f - ``` ``` $ kubectl get httproute NAME HOSTNAMES AGE backend ["www.example.com"] 4m18s ``` * Controller 提供的流量入口 Pod。 ``` $ kubectl get pod -n envoy-gateway-system NAME READY STATUS RESTARTS AGE envoy-default-eg-64656661-8677c5c79c-c7zg4 1/1 Running 0 4m45s envoy-gateway-6dcd84c6c9-gl8kb 2/2 Running 0 10m ``` * 檢查 envoy 對外的 service,開了 80 port ``` $ kubectl get svc -n envoy-gateway-system|grep LoadBalancer envoy-default-my-tcp-gateway-28bd5041 LoadBalancer 10.43.20.124 192.168.11.146 80:30796/TCP 2m49s ``` ## request 流程圖 ![image](https://hackmd.io/_uploads/rJ9TffffC.png) 1. Client 開始準備 URL 為 `http://www.example.com` 的 HTTP 請求 2. Client 的 DNS 會先做名稱解析到對應的 IP。 3. Client 向 Gateway IP 位址發送 request;反向代理接收 HTTP request 並使用 header 匹配 Gateway 和 HTTPRoute 的設定。 4. 反向代理可以根據 HTTPRoute 的匹配規則針對 request 所帶的 path 需要符合規則。 5. 反向代理可以修改 request;例如,根據 HTTPRoute 的過濾規則新增或刪除 header。 6. 最後,反向代理將請求轉送到一個或多個後端。 * 再叢集外測試打一個 request,可以透過不同的 path 將流量導入到不同的服務。 ``` $ curl -H "host: www.example.com" http://192.168.11.160/backend { "path": "/backend", "host": "www.example.com", "method": "GET", "proto": "HTTP/1.1", "headers": { "Accept": [ "*/*" ], "User-Agent": [ "curl/8.0.1" ], "X-Envoy-Internal": [ "true" ], "X-Forwarded-For": [ "192.168.11.65" ], "X-Forwarded-Proto": [ "http" ], "X-Request-Id": [ "c218884d-3a8d-4ca1-b280-4f027c7acc3d" ] }, "namespace": "default", "ingress": "", "service": "", "pod": "backend-6c74b76b4-r6npw" } $ curl -H "host: www.example.com" http://192.168.11.160/backend2 { "path": "/backend2", "host": "www.example.com", "method": "GET", "proto": "HTTP/1.1", "headers": { "Accept": [ "*/*" ], "User-Agent": [ "curl/8.0.1" ], "X-Envoy-Internal": [ "true" ], "X-Forwarded-For": [ "192.168.11.65" ], "X-Forwarded-Proto": [ "http" ], "X-Request-Id": [ "f7ff826f-2c47-47a0-9940-abe4898b6517" ] }, "namespace": "default", "ingress": "", "service": "", "pod": "backend2-67c74bfb48-6q78n" } ``` * 環境清除 ``` $ kubectl delete httproute backend $ kubectl delete gateway eg ``` ## 部屬 tcproute resource(還在實驗階段) * 環境檢查 ``` $ kubectl get svc,pod NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 77d service/svc-backend ClusterIP 10.43.144.57 <none> 3000/TCP 6h12m service/svc-backend2 ClusterIP 10.43.198.74 <none> 3000/TCP 6h12m NAME READY STATUS RESTARTS AGE pod/backend-6f875cb97d-cv52t 1/1 Running 0 6h12m pod/backend2-74d9dd94f4-6sl2b 1/1 Running 0 6h12m ``` * 部屬 gateway resource * 設定 gateway 對外開的 port 是 8080 跟 8090 ``` $ echo 'apiVersion: gateway.networking.k8s.io/v1beta1 kind: Gateway metadata: name: my-tcp-gateway spec: gatewayClassName: eg listeners: - name: backend protocol: TCP port: 8080 allowedRoutes: kinds: - kind: TCPRoute - name: backend2 protocol: TCP port: 8090 allowedRoutes: kinds: - kind: TCPRoute' | kubectl apply -f - ``` ``` $ kubectl get gateway NAME CLASS ADDRESS PROGRAMMED AGE my-tcp-gateway eg 192.168.11.146 True 6s ``` * Gateway API 從 8080 port 來的流量都會導入到 svc-backend 這個 service 的 3000 port * Gateway API 從 8090 port 來的流量都會導入到 svc-backend2 這個 service 的 3000 port ``` # parentRefs 用來綁定是哪個 Gateway 資源 # sectionName 用來綁定是 Gateway 內的哪個 listeners 資源 $ echo 'apiVersion: gateway.networking.k8s.io/v1alpha2 kind: TCPRoute metadata: name: tcp-app-1 spec: parentRefs: - name: my-tcp-gateway sectionName: backend rules: - backendRefs: - name: svc-backend port: 3000 --- apiVersion: gateway.networking.k8s.io/v1alpha2 kind: TCPRoute metadata: name: tcp-app-2 spec: parentRefs: - name: my-tcp-gateway sectionName: backend2 rules: - backendRefs: - name: svc-backend2 port: 3000' | kubectl apply -f - ``` ``` $ kubectl get tcproute NAME AGE tcp-app-1 6s tcp-app-2 6s ``` * 檢查 envoy 對外的 service,開了 8080 跟 8090 兩個 port ``` $ kubectl get svc -n envoy-gateway-system|grep LoadBalancer envoy-default-my-tcp-gateway-28bd5041 LoadBalancer 10.43.141.32 192.168.11.146 8080:32495/TCP,8090:31354/TCP 3m57s ``` * 驗證 ``` $ curl http://192.168.11.146:8080 { "path": "/", "host": "192.168.11.146:8080", "method": "GET", "proto": "HTTP/1.1", "headers": { "Accept": [ "*/*" ], "User-Agent": [ "curl/8.0.1" ] }, "namespace": "default", "ingress": "", "service": "", "pod": "backend-6f875cb97d-cv52t" } $ curl http://192.168.11.146:8090 { "path": "/", "host": "192.168.11.146:8090", "method": "GET", "proto": "HTTP/1.1", "headers": { "Accept": [ "*/*" ], "User-Agent": [ "curl/8.0.1" ] }, "namespace": "default", "ingress": "", "service": "", "pod": "backend2-74d9dd94f4-6sl2b" } ``` ## https 測試 * 環境準備 ``` $ kubectl create deployment nginx --image=nginx $ kubectl expose deployment nginx --port=80 --target-port=80 $ kubectl get pod,svc NAME READY STATUS RESTARTS AGE pod/nginx-bf5d5cf98-q66g7 1/1 Running 0 16s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 399d service/nginx ClusterIP 10.43.229.227 <none> 80/TCP 60s ``` * 產生自簽憑證 ``` $ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout web.key \ -out web.crt \ -subj "/CN=*.web.k8s.local/O=nginx-org" $ kubectl create secret tls web \ --cert=web.crt \ --key=web.key # 讓 SLES OS 信任憑證,須注意自己使用的 OS $ sudo cp web.crt /usr/share/pki/trust/anchors/ $ sudo update-ca-certificates --fresh ``` * 實作 ``` $ nano gateway.yaml apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: web-gateway spec: gatewayClassName: eg listeners: - name: http port: 80 protocol: HTTP hostname: "gateway.web.k8s.local" - name: https port: 443 protocol: HTTPS hostname: "gateway.web.k8s.local" tls: mode: Terminate certificateRefs: - kind: Secret name: web $ kubectl apply -f gateway.yaml ``` ``` $ kubectl get gateway NAME CLASS ADDRESS PROGRAMMED AGE web-gateway eg 192.168.11.146 False 12s ``` ``` $ nano httproute.yaml apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: web-route spec: parentRefs: - kind: Gateway name: web-gateway sectionName: https hostnames: - gateway.web.k8s.local rules: - matches: - path: type: PathPrefix value: / backendRefs: - kind: Service name: nginx port: 80 $ kubectl apply -f httproute.yaml ``` ``` $ kubectl get httproutes NAME HOSTNAMES AGE web-route ["gateway.web.k8s.local"] 10s ``` ``` $ echo "192.168.11.146 gateway.web.k8s.local" | sudo tee -a /etc/hosts ``` * 在本機驗證可以直接透過 https 訪問 ``` $ curl https://gateway.web.k8s.local <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ``` ## 建立 Gateway 指定 IP,並允許直連 postgresql ### 部署服務 ### 部署 postgresql * 先確認自己環境的 CSI ``` $ kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE nfs-csi nfs.csi.k8s.io Retain Immediate false 70d ``` * 建立 postgresql pvc ``` $ echo 'apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-nfs-postgresql namespace: default spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi storageClassName: nfs-csi' | kubectl apply -f - ``` * 建立 postgresql ``` $ echo 'apiVersion: apps/v1 kind: StatefulSet metadata: name: postgresql-db namespace: default spec: serviceName: postgresql-db-service selector: matchLabels: app: postgresql-db replicas: 1 template: metadata: labels: app: postgresql-db spec: containers: - name: postgresql-db image: postgres:16.2 volumeMounts: - mountPath: /data name: pv-storage env: - name: POSTGRES_USER value: testuser - name: POSTGRES_PASSWORD value: testpassword - name: PGDATA value: /data/pgdata volumes: - name: pv-storage persistentVolumeClaim: claimName: pvc-nfs-postgresql --- apiVersion: v1 kind: Service metadata: name: postgres-db namespace: default spec: selector: app: postgresql-db type: ClusterIP ports: - port: 5432 targetPort: 5432' | kubectl apply -f - ``` * 檢查 postgresql 部署狀態 ``` $ kubectl get po,svc NAME READY STATUS RESTARTS AGE pod/backend-6d595bd7b-nqfzm 1/1 Running 0 36s pod/backend2-569588b4b4-rdf2t 1/1 Running 0 36s pod/my-gateway-istio-745d49b945-5lbsk 1/1 Running 0 36s pod/postgresql-db-0 1/1 Running 0 29s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 73d service/my-gateway-istio LoadBalancer 10.96.166.159 10.10.7.80 15021:31630/TCP,80:30515/TCP,5432:32576/TCP 5m57s service/postgres-db ClusterIP 10.96.96.42 <none> 5432/TCP 78s service/svc-backend ClusterIP 10.96.77.160 <none> 3000/TCP 23h service/svc-backend2 ClusterIP 10.96.179.189 <none> 3000/TCP 23h ``` ### 部署 Gateway * 這邊使用 istio 當作 controller * gateway 可以透過 `http 80` 或是 `tcp 5432` 連到服務 ``` $ echo 'apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: my-gateway spec: gatewayClassName: istio addresses: - type: IPAddress value: "10.10.7.80" listeners: - name: http port: 80 protocol: HTTP hostname: "web.example.com" - name: postgres port: 5432 protocol: TCP allowedRoutes: kinds: - kind: TCPRoute' | kubectl apply -f - ``` ``` $ kubectl get gateway NAME CLASS ADDRESS PROGRAMMED AGE my-gateway istio 10.10.7.80 True 80s ``` * 建立 httproute 用來連到 backend 服務 ``` $ echo 'apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: backend spec: parentRefs: - name: my-gateway hostnames: - "web.example.com" rules: - backendRefs: - group: "" kind: Service name: svc-backend port: 3000 weight: 1 matches: - path: type: PathPrefix value: /backend - backendRefs: - group: "" kind: Service name: svc-backend2 port: 3000 weight: 1 matches: - path: type: PathPrefix value: /backend2' | kubectl apply -f - ``` ``` $ kubectl get httproute NAME HOSTNAMES AGE backend ["web.example.com"] 6s ``` * 建立 tcproute 用來連接到 postgres 服務 ``` $ echo 'apiVersion: gateway.networking.k8s.io/v1alpha2 kind: TCPRoute metadata: name: tcp-postgres spec: parentRefs: - name: my-gateway # 對應 Gateway 的名稱 sectionName: postgres # 對應 Gateway 中 Listener 的名稱 rules: - backendRefs: - name: postgres-db port: 5432' | kubectl apply -f - ``` ``` $ kubectl get tcproute NAME AGE tcp-postgres 4s ``` #### 驗證 * 驗證 httproute 功能 ``` $ curl -H "host: web.example.com" -w "\n" http://10.10.7.80/backend ...... "namespace": "default", "ingress": "", "service": "", "pod": "backend-6d595bd7b-nqfzm" } $ curl -H "host: web.example.com" -w "\n" http://10.10.7.80/backend2 ...... "namespace": "default", "ingress": "", "service": "", "pod": "backend2-569588b4b4-rdf2t" } ``` * 驗證 tcproute 功能,登入 postgresql ``` $ psql -h 10.10.7.80 -U testuser -d postgres -p 5432 Password for user testuser: testpassword psql (16.11 (Ubuntu 16.11-0ubuntu0.24.04.1), server 16.2 (Debian 16.2-1.pgdg120+2)) Type "help" for help. postgres=# \l List of databases Name | Owner | Encoding | Locale Provider | Collate | Ctype | ICU Locale | ICU Rules | Access privileges -----------+----------+----------+-----------------+------------+------------+------------+-----------+----------------------- postgres | testuser | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | template0 | testuser | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | =c/testuser + | | | | | | | | testuser=CTc/testuser template1 | testuser | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | =c/testuser + | | | | | | | | testuser=CTc/testuser testuser | testuser | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | (4 rows) ``` ## 參考文件 https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api https://cloudnative.to/blog/kubernetes-gateway-api-explained/