iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
Kubernetes

不是背 YAML!30 天從零打造 Kubernetes 微服務:從本機實戰一路到 CKA系列 第 26 篇

Day 26|2026 年重新學「Ingress」:Gateway API、HTTPRoute 與傳統 Ingress 到底差在哪?

  • 分享至 

  • xImage
  •  

這篇要解決的問題很實際:

如何讓使用者直接透過網址存取 Kubernetes 裡的應用程式,而不是每次都要靠 kubectl port-forward?

前面我們已經會建立 Pod、Deployment、Service,也知道可以用:

kubectl port-forward

暫時把 Kubernetes 裡的服務轉到自己的電腦上。

但 port-forward 比較適合開發與除錯,因為這條連線只存在於執行指令的那台電腦,而且終端機一關掉,連線通常也就中斷。正式環境不可能要求每一位使用者都安裝 kubectl,再自己執行 port-forward。

所以今天要開始處理真正的:

外部流量 → Kubernetes

先搞懂:Service 為什麼還不夠?

先複習兩個最基本的概念。

Pod 是 Kubernetes 裡真正執行應用程式的單位,例如 FastAPI、Nginx、Redis 都會實際跑在 Pod 裡。

但 Pod 隨時可能因為重建而換 IP,所以我們通常不會直接連 Pod,而是透過 Service。

Service 提供一個穩定的名稱與連接埠,例如:

api:80

再由 Service 把流量導到後面的 Pod。

問題是:

Service 解決的是 Kubernetes 裡面
「怎麼找到某一組 Pod」

但網站真正需要的是:

https://api.example.com
https://grafana.example.com
https://shop.example.com

也就是需要有一個「入口」,先接住外部的 HTTP Request,再判斷這個 Request 應該送去哪一個 Service。

例如:

api.example.com
        ↓
API Service

grafana.example.com
        ↓
Grafana Service

這個根據網址決定流量去向的行為,就叫做 Routing(路由)。

其中:

Hostname 是網址中的主機名稱,例如:

api.example.com

而 Path 是網址後面的路徑,例如:

/users
/orders
/health

因此你甚至可以設定:

example.com/api
        ↓
API Service

example.com/dashboard
        ↓
Dashboard Service

以前常用 Ingress,現在開始認識 Gateway API

以前 Kubernetes 最常使用的 HTTP 入口是:

Ingress

Ingress 可以定義:

什麼 Host
什麼 Path
要送去哪個 Service

但這裡有一個非常重要的觀念:

Ingress 本身只是一份設定,不是真的幫你轉送封包的程式。

真正負責讀取 Ingress 設定,並實際建立代理規則的是:

Ingress Controller

這裡第一次看到 Controller(控制器) 的話,可以把它理解成:

一個持續觀察 Kubernetes 狀態,並試著讓「實際狀態」符合「你設定的狀態」的程式。

所以只有:

kind: Ingress

卻沒有安裝任何 Ingress Controller,通常不會突然就多出一個可以使用的網站入口。

Kubernetes 官方目前仍保留 Ingress,而且 Ingress 是 Stable API,不會突然消失。

但 Ingress API 已經進入:

Frozen

Frozen 可以理解成:

API 本身不會再持續增加新的大型功能。

官方目前建議新的功能逐漸往 Gateway API 發展。

官方 Ingress 說明:

https://kubernetes.io/docs/concepts/services-networking/ingress/

目前 CKA 也仍然會碰到 Gateway API 與 Ingress,所以兩套都必須理解:

https://training.linuxfoundation.org/certification/certified-kubernetes-administrator-cka/


Gateway API 到底是什麼?

https://ithelp.ithome.com.tw/upload/images/20260925/20168537f33FVrrXgc.png

這裡的 API 不要把它理解成我們平常寫 FastAPI 時的 REST API。

在 Kubernetes 裡,可以先把 API 理解成:

Kubernetes 認得哪些 Resource,以及每種 Resource 可以有哪些欄位。

例如 Kubernetes 本來就認得:

kind: Pod
kind: Deployment
kind: Service

Gateway API 則額外提供:

GatewayClass
Gateway
HTTPRoute

最重要的三個 Resource 分工如下。

GatewayClass 用來決定:

這個 Gateway 要由哪一套 Controller 負責?

今天會使用:

cloud-provider-kind

接著是 Gateway。

Gateway 可以理解成真正的「入口設定」,例如:

我要開 HTTP
使用 Port 80
接受 api.ironman.test

最後是 HTTPRoute。

HTTPRoute 負責描述:

什麼 Host
什麼 Path
要交給哪一個 Service

所以三者關係可以畫成:

GatewayClass
選擇哪個 Controller
        ↓
Gateway
建立/設定入口
        ↑
HTTPRoute
掛到 Gateway 上並定義路由
        ↓
Service
        ↓
Pod

但要特別注意:

這不是四台設備一台接一台。

GatewayClass、Gateway、HTTPRoute 都是 Kubernetes 裡面的「設定資源」。

真正處理 HTTP 流量的,是 Gateway Controller 最後建立或設定出來的 Proxy / Load Balancer。


Step 1:確認 kind Cluster

今天繼續沿用:

Mac
Docker Desktop
kind
kubectl
cka-lab

如果前面已經一路做到這裡,基本上不用重新安裝。

這裡第一次看到 kind 的話,它的完整名稱是:

Kubernetes IN Docker

它會利用 Docker Container,在你的 Mac 上模擬 Kubernetes Node,非常適合本機練習 Kubernetes。

先打開 Docker Desktop,接著確認環境:

docker ps
kind get clusters
kubectl config current-context
kubectl get nodes

其中:

kubectl config current-context

會告訴你:

現在這個 kubectl 到底正在操作哪一個 Kubernetes Cluster。

Node 應該要看到:

Ready

如果你目前完全沒有 kind Cluster,才需要建立:

kind create cluster --name cka-lab

Step 2:安裝 cloud-provider-kind

接下來會遇到本機 kind 的一個問題。

在 AWS、GCP、Azure 這些真正的 Cloud 環境裡,如果建立:

type: LoadBalancer

Cloud Provider 通常可以幫你建立 Load Balancer,並提供 External IP。

但 kind 跑在自己的 Docker 裡,並沒有真正的 Cloud Provider。

所以如果完全沒有額外處理,建立:

type: LoadBalancer

常常會看到:

EXTERNAL-IP
<pending>

因此今天要使用:

cloud-provider-kind

它是 Kubernetes SIGs 提供給 kind 使用的 Cloud Provider 實作,可以在本機模擬:

LoadBalancer
Gateway API

官方 Lab:

https://kubernetes.io/blog/2026/01/28/experimenting-gateway-api-with-kind/

專案:

https://github.com/kubernetes-sigs/cloud-provider-kind

這裡使用 v0.11.1。

執行:

docker run -d \
  --name cloud-provider-kind \
  --rm \
  --network kind \
  -v /var/run/docker.sock:/var/run/docker.sock \
  registry.k8s.io/cloud-provider-kind/cloud-controller-manager:v0.11.1 \
  --gateway-channel=standard \
  --enable-lb-port-mapping

這段不要只是複製,我們拆開理解。

-d

代表讓 Container 在 Background 執行。

--network kind

則是讓 cloud-provider-kind 加入 kind 使用的 Docker Network,這樣它才能與 kind 裡面的 Node 溝通。

接著:

-v /var/run/docker.sock:/var/run/docker.sock

這是在把 Mac 上 Docker 的控制介面提供給這個 Container。

因為 cloud-provider-kind 必須能操作 Docker,才能建立 Load Balancer、Gateway 對應的 Container 與網路設定。

最後:

--enable-lb-port-mapping

會幫我們把 Load Balancer / Gateway 的 Port 映射到 Mac,後面才能直接從:

127.0.0.1

測試。


CRD 是什麼?

安裝 cloud-provider-kind 後,會使用到 Gateway API 的:

CRD

CRD 全名叫:

CustomResourceDefinition

中文可以理解成:

自訂資源定義

Kubernetes 原本只認得它內建的 Resource,例如:

Pod
Service
Deployment

如果今天想讓 Kubernetes 認得新的:

Gateway
HTTPRoute

就可以透過 CRD 擴充 Kubernetes API。

可以把它想像成:

CRD
=
告訴 Kubernetes:
「現在多了一種新的 YAML Resource」

但 CRD 只負責:

讓 Kubernetes 看得懂這個 Resource

真正讓它運作的仍然是:

Controller

也就是:

CRD
讓 Kubernetes 認得設定

Controller
讓設定真正產生效果

Step 3:確認 cloud-provider-kind 有成功執行

執行:

docker ps --filter name=cloud-provider-kind

你應該會看到:

registry.k8s.io/cloud-provider-kind/cloud-controller-manager:v0.11.1

https://ithelp.ithome.com.tw/upload/images/20260925/20168537tPXqHXYWGK.png

這個 Container 裡執行的是:

Cloud Controller Manager

簡稱:

CCM

它的用途就是補上 kind 原本沒有 Cloud Provider 的問題,協助處理 LoadBalancer,以及今天要使用的 Gateway API。

接著確認 GatewayClass:

kubectl get gatewayclass

https://ithelp.ithome.com.tw/upload/images/20260925/201685370IL87MJQIF.png

正常應該看到類似:

cloud-provider-kind

Controller 則會看到:

kind.sigs.k8s.io/gateway-controller

而:

ACCEPTED=True

代表這個 GatewayClass 已經被 Controller 接受,可以用它建立 Gateway。

如果一直沒有出現,可以看 cloud-provider-kind Log:

docker logs --tail=100 cloud-provider-kind

Step 4:確認我們的 API Service

今天會把 Gateway 流量送到:

cka-lab Namespace

裡的:

api Service

這裡先補一個名詞。

Namespace 是 Kubernetes 用來把 Resource 分組的邏輯空間。

例如:

cka-lab
production
monitoring
gateway-infra

可以把不同用途的 Resource 分開管理。

今天我們會把:

Application

放在:

cka-lab

Gateway 則放在:

gateway-infra

先查看 API Service:

kubectl -n cka-lab get service api -o yaml

https://ithelp.ithome.com.tw/upload/images/20260925/20168537ZNSwTJRtmB.png

假設看到:

ports:
  - port: 80
    targetPort: 8000

這兩個 Port 不要混在一起。

port: 80

代表:

Service 對外提供 80。

而:

targetPort: 8000

代表:

Service 最後把流量送到 Pod 的 8000。

所以資料流是:

Service :80
     ↓
Pod :8000

如果你沒有前面的 API

如果你沒有一路跟著前面的 Lab 做,也可以臨時建立一個測試 API。

先建立 Namespace:

kubectl create namespace cka-lab \
  --dry-run=client -o yaml | \
  kubectl apply -f -

接著建立測試 Pod:

kubectl -n cka-lab run api \
  --image=registry.k8s.io/e2e-test-images/agnhost:2.40 \
  --restart=Never \
  --port=8000 \
  -- netexec --http-port=8000

再建立 Service:

kubectl -n cka-lab expose pod api \
  --port=80 \
  --target-port=8000

最後等待 Pod Ready:

kubectl -n cka-lab wait \
  --for=condition=Ready pod/api \
  --timeout=120s

這個測試程式會在:

8000

提供 HTTP Server。

Service 則提供:

80

所以仍然是:

Service :80
      ↓
Pod :8000

如果你本來就已經有自己的 API,就不用建立這組測試 Resource。


Step 5:建立 Gateway

接著建立:

gateway.yaml

內容:

apiVersion: v1
kind: Namespace
metadata:
  name: gateway-infra
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: app-gateway
  namespace: gateway-infra
spec:
  gatewayClassName: cloud-provider-kind
  listeners:
    - name: http
      hostname: api.ironman.test
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: All

這裡最重要的新名詞是:

Listener

Listener 可以理解成:

Gateway 的「監聽入口設定」。

它會定義:

我要聽哪個 Port?
使用什麼 Protocol?
接受哪個 Hostname?

例如:

hostname: api.ironman.test
port: 80
protocol: HTTP

就是:

HTTP
↓
Port 80
↓
api.ironman.test

但這裡一定要注意:

hostname: api.ironman.test

不代表 Kubernetes 幫你買了一個網域,也不代表它自動建立 DNS。

DNS 是:

Domain Name System

作用是把:

api.ironman.test

轉換成:

IP Address

Gateway 裡面的 hostname 只是:

當 HTTP Request 的 Host 符合這個名稱時,我才處理。


allowedRoutes 又是什麼?

我們的 Gateway 在:

gateway-infra

但等等 HTTPRoute 會建立在:

cka-lab

也就是:

跨 Namespace

因此這裡設定:

allowedRoutes:
  namespaces:
    from: All

意思就是:

允許其他 Namespace 裡面的 HTTPRoute 掛到這個 Gateway。

Lab 為了簡單使用:

All

正式環境通常不會讓所有 Namespace 都可以任意掛路由,而會再限制。

官方說明:

https://gateway-api.sigs.k8s.io/guides/user-guides/multiple-ns/


Step 6:套用 Gateway

執行:

kubectl apply -f gateway.yaml

等待 Gateway 完成:

kubectl -n gateway-infra wait \
  --for=condition=Programmed gateway/app-gateway \
  --timeout=180s

查看:

kubectl -n gateway-infra get gateway app-gateway

正常應該看到:

PROGRAMMED=True

並看到 ADDRESS。

這裡的:

Programmed=True

https://ithelp.ithome.com.tw/upload/images/20260925/20168537Ks3xhMPy0P.png
表示:

Gateway Controller 已經把這個 Gateway 的設定實際建立出來。

但先不要看到:

True

就以為整個網站一定正常。

因為 Gateway 正常只代表:

入口正常

後面還有:

HTTPRoute
Service
Pod
Network

全部都可能出問題。


Step 7:建立 HTTPRoute

現在有入口了,接著才要告訴 Gateway:

Request 到底要送去哪裡?

建立:

httproute.yaml

內容:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
  namespace: cka-lab
spec:
  parentRefs:
    - name: app-gateway
      namespace: gateway-infra
      sectionName: http
  hostnames:
    - api.ironman.test
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: api
          port: 80

先看:

parentRefs:

它的意思就是:

這個 HTTPRoute 要掛到哪一個 Gateway?

所以:

name: app-gateway
namespace: gateway-infra

表示掛到:

gateway-infra/app-gateway

而:

sectionName: http

指定的是剛才 Gateway 裡:

listeners:
  - name: http

那一個 Listener。


PathPrefix 是什麼?

這裡:

type: PathPrefix
value: /

代表:

只要 URL Path 是 / 開頭就符合。

因此:

/
/users
/health
/api/test
/orders/123

都會 Match。

但這裡要特別注意:

HTTPRoute 只是在「比對 Path」。

它不會因為設定:

value: /api

就自動把 /api 從網址刪掉。

也就是:

/api/users

送給 Backend 時,原本仍然可能是:

/api/users

除非額外設定 URL Rewrite。


BackendRefs 指的是誰?

Backend 就是:

真正負責接收 Request 的後端服務。

這裡:

backendRefs:
  - name: api
    port: 80

就是:

api Service
Port 80

注意這裡填的是:

Service port

所以一定是:

80

不是 Pod 的:

8000

完整資料流會是:

Gateway
   ↓
HTTPRoute
   ↓
Service api:80
   ↓
Pod :8000

Step 8:套用 HTTPRoute

執行:

kubectl apply -f httproute.yaml

再看完整狀態:

kubectl -n cka-lab get httproute api-route -o yaml

往下面找到:

status

你會看到各種:

conditions

這裡的 Condition 可以理解成:

Controller 對這個 Resource 處理結果的報告。

例如:

Accepted=True

https://ithelp.ithome.com.tw/upload/images/20260925/20168537YlEcfa5jmP.png

代表:

Gateway 接受這條 Route。

而:

ResolvedRefs=True

則代表:

HTTPRoute 引用的 Service 等 Resource 都找得到。

兩者意思不同。

例如:

Accepted=True
ResolvedRefs=False

完全有可能發生。

因為:

路由格式可以接受

但:

你指定的 Service 不存在

官方狀態說明:

https://gateway-api.sigs.k8s.io/geps/gep-1364/


Step 9:真的從 Mac 連進 Gateway

現在 Kubernetes 裡的設定完成了。

但還有一個本機 kind 特有的問題。

Docker Desktop 實際上會讓 Container 跑在 Linux VM 裡,所以:

Gateway ADDRESS

顯示的 IP,不一定能直接從 macOS 使用。

因此我們前面才加:

--enable-lb-port-mapping

現在查看 Gateway Container 的 Port Mapping:

docker ps --filter name=kindccm-gw \
  --format 'table {{.Names}}\t{{.Ports}}'

https://ithelp.ithome.com.tw/upload/images/20260925/201685370NYZQP9JNN.png

以我自己為例,會看到:

0.0.0.0:63909->80/tcp

意思就是:

Mac
127.0.0.1:63909

        ↓

Gateway Container
Port 80

所以真正測試時,要連:

127.0.0.1:63909

注意你的 Port 不一定是 63909,請使用自己電腦實際顯示的數字。


Step 10:使用 curl,真正從 Mac 測試 Gateway

前面我們已經找到 Gateway Container 的 Port Mapping:

docker ps --filter name=kindccm-gw \
  --format 'table {{.Names}}\t{{.Ports}}'

我的環境實際顯示:

0.0.0.0:63909->80/tcp

這代表:

Mac 127.0.0.1:63909
        ↓
Gateway Container :80

這裡的 63909 是 Docker 動態分配的本機 Port,不是固定值。因此文章裡如果看到 63909 等數字,都只能當範例,每次實驗要以自己執行 docker ps 看到的結果為準。

接著用 curl 發送真正的 HTTP Request:

curl -i \
  -H 'Host: api.ironman.test' \
  http://127.0.0.1:63909/

curl 是一個 Command Line HTTP Client,也就是可以直接從 Terminal 發送 HTTP Request 的工具。

其中:

-i

代表連 HTTP Response Header 一起顯示,所以除了 Response Body,還能看到:

HTTP/1.1 200 OK
HTTP/1.1 404 Not Found
HTTP/1.1 503 Service Unavailable

這些資訊對排錯非常重要。

而:

-H 'Host: api.ironman.test'

則是在 HTTP Request 裡加入:

Host: api.ironman.test

這個 Header。

我們的 Gateway Listener 設定了:

hostname: api.ironman.test

HTTPRoute 也設定:

hostnames:
  - api.ironman.test

但是實際上 curl 連線的位址是:

127.0.0.1:63909

所以我們需要透過 HTTP 的 Host Header 告訴 Gateway:

雖然我實際連的是 127.0.0.1,
但我要存取的網站是 api.ironman.test。

Gateway 才能拿這個值去比對 Listener 與 HTTPRoute。

因此目前不必真的設定 DNS。


第一次測試:出現 503 Service Unavailable

https://ithelp.ithome.com.tw/upload/images/20260925/201685372J5R8p9FnR.png
我的第一次測試得到:

HTTP/1.1 503 Service Unavailable
server: envoy

upstream connect error or disconnect/reset before headers.
reset reason: connection timeout

這時不要看到 503 就以為整套 Gateway 都失敗了。

這個 Response 裡其實已經提供非常重要的線索:

server: envoy

Envoy 是一套 Proxy,也就是負責接收 Request 並轉送到後端的程式。cloud-provider-kind 建立的 Gateway Data Plane 會使用 Envoy 處理這些 HTTP 流量。

既然 Response 是 Envoy 回傳的,就代表:

Mac
 ↓
127.0.0.1:63909
 ↓
Gateway / Envoy

這一段其實已經成功了。

現在的問題比較像是:

Gateway / Envoy
       ↓
HTTPRoute
       ↓
Service
       ↓
Pod
       ✕

也就是 Gateway 收到了 Request,但是它往 Backend 送時失敗。

而:

connection timeout

尤其值得注意。

如果只是 Hostname 寫錯,通常會比較像路由不匹配;但現在 Envoy 已經嘗試連後端,最後卻等到 Timeout,因此要繼續往 Backend 與 Network 層排查。


Step 11:先確認 Gateway 與 HTTPRoute 本身正常

遇到 Gateway 問題,不建議一開始就亂改 YAML。

先按照流量路徑一層一層確認:

Gateway
↓
HTTPRoute
↓
Service
↓
EndpointSlice
↓
Pod
↓
NetworkPolicy

先看 Gateway:

kubectl -n gateway-infra get gateway app-gateway

希望看到:

PROGRAMMED
True

Programmed=True 代表 Gateway Controller 已經把入口設定實際建立完成。
https://ithelp.ithome.com.tw/upload/images/20260925/20168537wuvDdEhxLE.png

我們確實已建立完成

接著查看 HTTPRoute:

kubectl get httproute api-route -n cka-lab -o yaml

https://ithelp.ithome.com.tw/upload/images/20260925/20168537YDum3kVkm3.png

往下面找:

status:
  parents:
    ...
    conditions:

其中最重要的是:

Accepted=True
ResolvedRefs=True

Accepted=True 代表:

Gateway 接受這條 HTTPRoute。

而:

ResolvedRefs=True

代表:

HTTPRoute 裡引用的 Backend,例如 api Service,可以被成功找到。

所以如果現在是:

Gateway:
Programmed=True

HTTPRoute:
Accepted=True
ResolvedRefs=True

基本上可以先判斷:

Gateway 設定正常
HTTPRoute 設定正常
Backend Service 名稱也解析得到

接下來就應該往 Service 與 Pod 查。


Step 12:確認 Service 到底把流量送去哪裡

查看:

kubectl get service api -n cka-lab -o yaml 

我的架構是:

ports:
  - port: 80
    targetPort: 8000

這兩個 Port 一定要分清楚。

port: 80 是:

Service 對外提供的 Port

而:

targetPort: 8000

是:

Pod 裡 Application 真正監聽的 Port

所以目前流量應該是:

Gateway
   ↓
HTTPRoute
   ↓
Service api:80
   ↓
Pod :8000

HTTPRoute 裡:

backendRefs:
  - name: api
    port: 80

https://ithelp.ithome.com.tw/upload/images/20260925/20168537Xt8Zk41vnO.png

因此填的是 Service Port 80。

但真正進入 Pod 時使用的是:

8000

後面寫 NetworkPolicy 時,這個差別非常重要。


Step 13:使用 EndpointSlice 確認 Service 真的有找到 Pod

前面我們一直畫:

Service
↓
Pod

但是 Service 本身到底怎麼知道有哪些 Pod 可以使用?

這裡就會遇到另一個 Kubernetes Resource:

EndpointSlice

EndpointSlice 用來記錄某個 Service 目前有哪些實際 Backend Endpoint,包括:

Pod IP
Port
Ready 狀態

查看 API Service 的 EndpointSlice:

kubectl get endpointslices -n cka-lab \
  -l kubernetes.io/service-name=api -o wide

也可以看更完整的內容:

kubectl -n cka-lab get endpointslices \
  -l kubernetes.io/service-name=api -o yaml

https://ithelp.ithome.com.tw/upload/images/20260925/20168537El6De0Zaxf.png

正常情況應該可以看到類似:

192.x.x.x

的 Pod IP。

如果 Endpoint 是:

<none>

或完全找不到預期的 Pod,就表示:

Service
✕
Pod

Service 沒有找到 Backend。

這時通常要檢查 Service Selector:

kubectl get service api -n cka-lab -o yaml

例如:

selector:
  app: api

https://ithelp.ithome.com.tw/upload/images/20260925/201685370BeumQSRS5.png

再看 Pod Label:

kubectl get pods --show-labels -n cka-lab 

如果 Pod 也是:

app=api

https://ithelp.ithome.com.tw/upload/images/20260925/20168537JL2KrD4ExG.png

兩者才能 Match。

也就是:

Service selector
app=api

        ↓ Match

Pod label
app=api

官方文件:

https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/


Step 14:確認 Pod 本身正常

接著確認 API Pod:

kubectl get pods -n cka-lab -o wide 

希望看到:

STATUS
Running

READY
1/1

https://ithelp.ithome.com.tw/upload/images/20260925/20168537EuGmJ2ftbM.png

如果你看到:

CrashLoopBackOff
Pending
Error
0/1

那 Gateway 當然也不可能成功。

必要時還可以查看 Log:

kubectl logs <API-POD-NAME> -n cka-lab 

到這裡如果,我們已經追蹤了全部路徑:

Gateway 正常
HTTPRoute 正常
Service 正常
EndpointSlice 有 Pod IP
Pod 也是 Running

可是 curl 還是:

503 Service Unavailable
connection timeout

這時就要想到網路存取規則。


Step 15:檢查 NetworkPolicy

查看:

kubectl get networkpolicy -n cka-lab 

我的環境實際看到:

https://ithelp.ithome.com.tw/upload/images/20260925/20168537vSSbAJQ59y.png

再查看完整設定:

kubectl get networkpolicy -n cka-lab -o yaml

其中最重要的是:

kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
    - Ingress

https://ithelp.ithome.com.tw/upload/images/20260925/20168537o8YMxhUxuA.png

這裡的:

podSelector: {}

不是代表「沒有選任何 Pod」。

反而代表:

選擇這個 Namespace 裡的所有 Pod。

而:

policyTypes:
  - Ingress

代表這是一條限制:

進入 Pod 的流量

的 NetworkPolicy。

更重要的是,它完全沒有:

ingress:

Allow 規則。
https://ithelp.ithome.com.tw/upload/images/20260925/20168537y76oabqkIq.png

所以這份 Policy 的效果其實是:

cka-lab 裡所有 Pod
        ↓
Ingress 預設全部禁止

也就是:

default deny ingress

為什麼 Redis 還可以被 API 連?

因為我們另外還有:

redis-ingress

它設定:

podSelector:
  matchLabels:
    app: redis

並且允許:

from:
  - podSelector:
      matchLabels:
        app: api
ports:
  - port: 6379

所以:

API Pod
app=api
   ↓
Redis :6379

被額外允許。

複習一下
這裡要建立一個非常重要的 NetworkPolicy 觀念:

NetworkPolicy 是疊加式的,不是後面的 Policy 把前面的 Policy 覆蓋掉。

也就是現在:

default-deny-ingress
↓
所有 Pod 預設禁止

+

redis-ingress
↓
另外允許 API → Redis:6379

所以 Redis 有一個額外的洞可以進去。

但是目前:

API Pod

沒有自己的 Allow Policy。

因此 Gateway 嘗試連 API 時:

Gateway / Envoy
      ↓
API Pod :8000
      ✕
NetworkPolicy

封包被擋住。

最後 Envoy 等不到 Backend 回應,才會出現:

HTTP/1.1 503 Service Unavailable

upstream connect error
reset reason: connection timeout

這正好就是我們現在遇到的情況。

官方 NetworkPolicy 文件:

https://kubernetes.io/docs/concepts/services-networking/network-policies/


Step 16:不要刪掉 Default Deny,而是正確放行 API

如果只是想快速驗證,當然可以暫時刪掉:

kubectl -n cka-lab delete networkpolicy default-deny-ingress

然後重新 curl。

如果突然成功,就能證明確實是 NetworkPolicy。

但是這不應該是最後的解法。

因為前面建立 default-deny-ingress 的目的本來就是:

所有 Pod
預設禁止被任意存取

只有真的需要的流量
才額外開放

所以更合理的做法是:

保留 default-deny-ingress

+

新增 API 的 Allow Policy

Step 17:先確認 API Pod 的 Label

先執行:

kubectl get pods --show-labels -n cka-lab 

確認 API Pod 是否有:

app=api

例如:

api-xxxxxx   1/1   Running   ...   app=api

如果你的 Label 不是 app=api,下面的 NetworkPolicy 就要改成你真正的 Label。


Step 18:建立 api-ingress NetworkPolicy

建立:

api-ingress.yaml

內容:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-ingress
  namespace: cka-lab
spec:
  podSelector:
    matchLabels:
      app: api

  policyTypes:
    - Ingress

  ingress:
    - ports:
        - protocol: TCP
          port: 8000

這份設定可以拆成兩部分理解。

首先:

podSelector:
  matchLabels:
    app: api

代表:

這條 Policy 是套用到 app=api 的 Pod。

接著:

ingress:
  - ports:
      - protocol: TCP
        port: 8000

代表:

允許進入這些 API Pod 的 TCP 8000 流量。

目前沒有寫:

from:

所以在這個 Lab 裡可以先理解成:

任何來源
↓
API Pod :8000

都可以進來。

但是注意:

只有 API Pod 的 8000

被開放。

不是整個 Namespace 所有 Port 都開放。


為什麼 NetworkPolicy 要寫 8000,不是 80?

這裡非常容易搞混。

HTTPRoute 寫:

backendRefs:
  - name: api
    port: 80

因為 HTTPRoute 連的是:

Service

所以填:

Service Port 80

但 NetworkPolicy 保護的是:

Pod

最後真正進到 Pod 的 Port 是:

8000

所以:

HTTPRoute
port: 80

和:

NetworkPolicy
port: 8000

其實沒有衝突。

完整流程:

Gateway
   ↓
HTTPRoute
   ↓
Service api:80
   ↓
targetPort
   ↓
API Pod :8000

因此 NetworkPolicy 要放行:

TCP 8000

Step 19:套用 NetworkPolicy

執行:

kubectl apply -f api-ingress.yaml

接著查看:

kubectl get networkpolicy -n cka-lab

現在應該會看到:
https://ithelp.ithome.com.tw/upload/images/20260925/20168537G9BnxK0Mi9.png

整體安全規則變成:

default-deny-ingress
│
├── 所有 Pod
│   預設禁止 Ingress
│
├── redis-ingress
│   └── API 可以連 Redis:6379
│
└── api-ingress
    └── API Pod 開放 TCP:8000

這正是:

Default Deny
+
Explicit Allow

的概念。


Step 20:重新測試 Gateway

現在重新執行:

curl -i \
  -H 'Host: api.ironman.test' \
  http://127.0.0.1:63909/

如果使用自己的 FastAPI,只要 / 有 Endpoint,正常應該會得到類似:

HTTP/1.1 200 OK

https://ithelp.ithome.com.tw/upload/images/20260925/20168537nt5qNgZ9zq.png

如果你的 API 沒有 / Route,但 Gateway 已經成功連到 Application,也可能得到:

404 Not Found

注意:

404

和剛才的:

503 connection timeout

意義完全不同。

404 通常代表:

Gateway
↓
Backend
↓
Application

這條連線已經通了,
只是 Application 沒有這個 URL。

而:

503 + connection timeout

表示:

Gateway
↓
Backend

連線本身就沒有建立成功。

因此排錯不能只看:

成功 / 失敗

而是要看:

HTTP Status Code
Response Header
Response Body

才能判斷問題在哪一層。


那到這裡才算真正完成:

Mac
↓
Docker Host Port :63909
↓
Envoy Gateway :80
↓
HTTPRoute
↓
Service api:80
↓
NetworkPolicy
↓
Pod :8000

整條流量成功。


那 HTTPS 呢?

今天設定的是:

HTTP

使用:

Port 80

HTTPS 則可以理解成:

HTTP
+
TLS Encryption

TLS 是負責加密網路連線的機制。

正式環境通常還需要:

TLS Certificate
HTTPS Listener
Port 443

所以:

建立 Gateway 不代表 HTTPS 會自動存在。

今天先把 HTTP Routing、Backend 與 NetworkPolicy 關係搞懂即可。


Step 21:故意弄壞 HTTPRoute,再練一次排錯

剛才我們遇到的是:

NetworkPolicy 問題

接下來故意製造另一種完全不同的錯誤:

HTTPRoute 引用不存在的 Service

把 Backend 改成:

api-does-not-exist

執行:

kubectl -n cka-lab patch httproute api-route \
  --type=json \
  -p='[{"op":"replace","path":"/spec/rules/0/backendRefs/0/name","value":"api-does-not-exist"}]'

查看:

kubectl get httproute api-route -n cka-lab -o yaml

等 Controller 更新後,應該會看到類似:

type: ResolvedRefs
status: "False"
reason: BackendNotFound

這次問題就和剛才不同。

ResolvedRefs=False

https://ithelp.ithome.com.tw/upload/images/20260925/20168537G0ndcIgmXY.png

代表:

HTTPRoute 引用的 Resource 無法成功解析。

而:

BackendNotFound

直接告訴我們:

指定的 Backend 根本不存在。

注意這時可能仍然看到:

Accepted=True

這並不矛盾。

因為:

Accepted=True

回答的是:

Gateway 接不接受這一條 Route?

而:

ResolvedRefs=False

回答的是:

Route 裡引用的 Backend 存不存在?

兩個 Condition 回答的是不同問題。

因此排錯時不要只看:

True / False

應該一起看:

type
status
reason
message

Step 22:恢復 HTTPRoute

因為原本的:

httproute.yaml

裡仍然寫著:

backendRefs:
  - name: api
    port: 80

所以直接重新 Apply:

kubectl apply -f httproute.yaml

然後確認:

kubectl get httproute api-route -n cka-lab -o yaml

應該重新回到:

Accepted=True
ResolvedRefs=True

https://ithelp.ithome.com.tw/upload/images/20260925/20168537m5OcoytQxb.png
再測一次:

curl -i \
  -H 'Host: api.ironman.test' \
  http://127.0.0.1:63909/

如果未來重建 Gateway,Port 可能再次改變,因此重新查:

docker ps --filter name=kindccm-gw \
  --format 'table {{.Names}}\t{{.Ports}}'

https://ithelp.ithome.com.tw/upload/images/20260925/20168537yuD6aHHKON.png
才是最可靠的方法。


Gateway 發生問題時,到底應該怎麼排?

經過這次實驗,可以建立一套固定排錯順序。

第一層先確認:

Gateway
kubectl get gateway app-gateway -n gateway-infra 

重點:

Programmed=True

第二層:

HTTPRoute
kubectl get httproute api-route -n cka-lab -o yaml

重點:

Accepted=True
ResolvedRefs=True

第三層:

Service
kubectl get service api -n cka-lab -o yaml

確認:

port
targetPort
selector

第四層:

EndpointSlice
kubectl get endpointslices -n cka-lab \
  -l kubernetes.io/service-name=api -o yaml

確認 Service 有真正找到 Backend Pod。

第五層:

Pod
kubectl get pods -n cka-lab -o wide

確認:

Running
Ready

最後才看:

NetworkPolicy
kubectl get networkpolicy -n cka-lab 
kubectl get networkpolicy -n cka-lab -o yaml

確認流量有沒有被網路政策擋掉。

所以可以把 Troubleshooting 流程記成:

Gateway
   ↓
HTTPRoute
   ↓
Service
   ↓
EndpointSlice
   ↓
Pod
   ↓
NetworkPolicy

這不是代表封包一定照這些 Kubernetes Resource 一個一個「穿過」。

而是:

這是一套很好用的除錯思考順序。


最後再回頭看 Ingress

理解 Gateway API 後,再看傳統 Ingress 就容易很多。

例如:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  namespace: cka-lab
spec:
  rules:
    - host: api.ironman.test
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80

意思就是:

如果 Host
=
api.ironman.test

而且 Path 符合
/

就送到

api Service :80

和剛才 Gateway API 解決的是同一類需求。

只是 Gateway API 把不同職責拆成:

GatewayClass
Gateway
HTTPRoute

Ingress 則比較集中在:

Ingress

這個 Resource 裡。

但一樣要記住:

Ingress YAML 本身不會真的轉送 HTTP Request。

仍然需要:

Ingress Controller

真正讀取設定並建立 Proxy 規則。

很多環境還會使用:

ingressClassName:

指定:

IngressClass

也就是告訴 Kubernetes:

這份 Ingress 應該交給哪一類 Ingress Controller 處理。


最後真正要建立的 Gateway 心智模型

先用一張圖來整理:
https://ithelp.ithome.com.tw/upload/images/20260925/201685374oxYlJRWU6.png

今天最容易犯的錯,就是把 Kubernetes Resource 想成一台一台實體網路設備。

例如:

Gateway
↓
HTTPRoute
↓
Service
↓
EndpointSlice
↓
Pod

這張圖適合用來理解設定關係與排錯順序。

但不要把它理解成:

Packet 一定真的依序經過五台設備。

更精確的理解應該是:

外部 HTTP Request
        ↓
Gateway Controller 所建立/管理的入口 Proxy
        ↓
根據 HTTPRoute 決定 Backend
        ↓
後端 Pod

其中:

Gateway

描述入口。

HTTPRoute

描述:

Host / Path
↓
Backend

的 Routing Rule。

Service

代表一組穩定的 Backend。

EndpointSlice

則記錄目前真正有哪些 Backend Endpoint。

NetworkPolicy

決定某些網路流量:

可以進來
還是不可以進來

這些大多屬於 Kubernetes Control Plane 裡的設定資料。

真正處理封包的是:

Envoy / Proxy
Load Balancer
CNI Network Plugin
Linux Network
Container Network

等底層元件。

某些 Gateway 實作甚至可以根據 EndpointSlice 直接取得 Pod Endpoint,因此不一定是每一個 Packet 都真的先經過 Service ClusterIP。


今天完整跑通的架構

最後把這次真正完成的實驗整理成:

Mac
│
│ curl
│ Host: api.ironman.test
│
▼
127.0.0.1:63909
│
│ Docker Port Mapping
▼
Envoy Gateway :80
│
│ HTTPRoute
│ 判斷 Host / Path
▼
api Service :80
│
│ targetPort
▼
NetworkPolicy
│
│ api-ingress
│ allow TCP :8000
▼
API Pod :8000

而 Kubernetes 裡負責描述整套架構的 Resource 則是:

GatewayClass
   ↓
指定哪個 Controller 實作 Gateway

Gateway
   ↓
描述入口、Protocol、Port、Hostname

HTTPRoute
   ↓
描述 Host / Path 要去哪些 Backend

Service
   ↓
代表一組穩定 Backend

EndpointSlice
   ↓
記錄實際 Backend Endpoint

NetworkPolicy
   ↓
限制哪些網路流量可以進出 Pod

這次還重新碰到一個非常重要的 Kubernetes 擴充機制:

CRD

Gateway、HTTPRoute 這些 Resource 可以透過 CRD 加入 Kubernetes API。

所以最後可以記住:

CRD
讓 Kubernetes 認得新的 Resource

Controller
讀取這些 Resource
並讓設定真正產生效果

而這次遇到的 503 Service Unavailable 也讓我們實際看到:

Gateway Programmed=True
HTTPRoute Accepted=True
ResolvedRefs=True

不代表整條 HTTP Request 一定成功。

因為後面還有:

Service
EndpointSlice
Pod
NetworkPolicy

任何一層出問題,都可能導致請求失敗。

也因此真正重要的不是背指令,而是能看到錯誤後知道:

這個錯誤到底屬於哪一層?
下一個應該檢查哪個 Resource?

這才是這次 Gateway API 實驗最重要的收穫。


上一篇
Day 25|Kustomize vs Helm:YAML 開始變多之後,怎麼避免複製貼上地獄?
下一篇
Day 27|從 CRD、Operator 到 Argo CD:看懂 Kubernetes 的擴充機制
系列文
不是背 YAML!30 天從零打造 Kubernetes 微服務:從本機實戰一路到 CKA 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言