這篇要解決的問題很實際:
如何讓使用者直接透過網址存取 Kubernetes 裡的應用程式,而不是每次都要靠
kubectl port-forward?
前面我們已經會建立 Pod、Deployment、Service,也知道可以用:
kubectl port-forward
暫時把 Kubernetes 裡的服務轉到自己的電腦上。
但 port-forward 比較適合開發與除錯,因為這條連線只存在於執行指令的那台電腦,而且終端機一關掉,連線通常也就中斷。正式環境不可能要求每一位使用者都安裝 kubectl,再自己執行 port-forward。
所以今天要開始處理真正的:
外部流量 → Kubernetes
先複習兩個最基本的概念。
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
以前 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/

這裡的 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。
今天繼續沿用:
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
接下來會遇到本機 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
測試。
安裝 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
讓設定真正產生效果
執行:
docker ps --filter name=cloud-provider-kind
你應該會看到:
registry.k8s.io/cloud-provider-kind/cloud-controller-manager:v0.11.1

這個 Container 裡執行的是:
Cloud Controller Manager
簡稱:
CCM
它的用途就是補上 kind 原本沒有 Cloud Provider 的問題,協助處理 LoadBalancer,以及今天要使用的 Gateway API。
接著確認 GatewayClass:
kubectl get gatewayclass

正常應該看到類似:
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
今天會把 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

假設看到:
ports:
- port: 80
targetPort: 8000
這兩個 Port 不要混在一起。
port: 80
代表:
Service 對外提供 80。
而:
targetPort: 8000
代表:
Service 最後把流量送到 Pod 的 8000。
所以資料流是:
Service :80
↓
Pod :8000
如果你沒有一路跟著前面的 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。
接著建立:
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 符合這個名稱時,我才處理。
我們的 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/
執行:
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

表示:
Gateway Controller 已經把這個 Gateway 的設定實際建立出來。
但先不要看到:
True
就以為整個網站一定正常。
因為 Gateway 正常只代表:
入口正常
後面還有:
HTTPRoute
Service
Pod
Network
全部都可能出問題。
現在有入口了,接著才要告訴 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。
這裡:
type: PathPrefix
value: /
代表:
只要 URL Path 是
/開頭就符合。
因此:
/
/users
/health
/api/test
/orders/123
都會 Match。
但這裡要特別注意:
HTTPRoute 只是在「比對 Path」。
它不會因為設定:
value: /api
就自動把 /api 從網址刪掉。
也就是:
/api/users
送給 Backend 時,原本仍然可能是:
/api/users
除非額外設定 URL Rewrite。
Backend 就是:
真正負責接收 Request 的後端服務。
這裡:
backendRefs:
- name: api
port: 80
就是:
api Service
Port 80
注意這裡填的是:
Service port
所以一定是:
80
不是 Pod 的:
8000
完整資料流會是:
Gateway
↓
HTTPRoute
↓
Service api:80
↓
Pod :8000
執行:
kubectl apply -f httproute.yaml
再看完整狀態:
kubectl -n cka-lab get httproute api-route -o yaml
往下面找到:
status
你會看到各種:
conditions
這裡的 Condition 可以理解成:
Controller 對這個 Resource 處理結果的報告。
例如:
Accepted=True

代表:
Gateway 接受這條 Route。
而:
ResolvedRefs=True
則代表:
HTTPRoute 引用的 Service 等 Resource 都找得到。
兩者意思不同。
例如:
Accepted=True
ResolvedRefs=False
完全有可能發生。
因為:
路由格式可以接受
但:
你指定的 Service 不存在
官方狀態說明:
https://gateway-api.sigs.k8s.io/geps/gep-1364/
現在 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}}'

以我自己為例,會看到:
0.0.0.0:63909->80/tcp
意思就是:
Mac
127.0.0.1:63909
↓
Gateway Container
Port 80
所以真正測試時,要連:
127.0.0.1:63909
注意你的 Port 不一定是 63909,請使用自己電腦實際顯示的數字。
前面我們已經找到 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。

我的第一次測試得到:
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 層排查。
遇到 Gateway 問題,不建議一開始就亂改 YAML。
先按照流量路徑一層一層確認:
Gateway
↓
HTTPRoute
↓
Service
↓
EndpointSlice
↓
Pod
↓
NetworkPolicy
先看 Gateway:
kubectl -n gateway-infra get gateway app-gateway
希望看到:
PROGRAMMED
True
Programmed=True 代表 Gateway Controller 已經把入口設定實際建立完成。
我們確實已建立完成
接著查看 HTTPRoute:
kubectl get httproute api-route -n cka-lab -o yaml

往下面找:
status:
parents:
...
conditions:
其中最重要的是:
Accepted=True
ResolvedRefs=True
Accepted=True 代表:
Gateway 接受這條 HTTPRoute。
而:
ResolvedRefs=True
代表:
HTTPRoute 裡引用的 Backend,例如
apiService,可以被成功找到。
所以如果現在是:
Gateway:
Programmed=True
HTTPRoute:
Accepted=True
ResolvedRefs=True
基本上可以先判斷:
Gateway 設定正常
HTTPRoute 設定正常
Backend Service 名稱也解析得到
接下來就應該往 Service 與 Pod 查。
查看:
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

因此填的是 Service Port 80。
但真正進入 Pod 時使用的是:
8000
後面寫 NetworkPolicy 時,這個差別非常重要。
前面我們一直畫:
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

正常情況應該可以看到類似:
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

再看 Pod Label:
kubectl get pods --show-labels -n cka-lab
如果 Pod 也是:
app=api

兩者才能 Match。
也就是:
Service selector
app=api
↓ Match
Pod label
app=api
官方文件:
https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
接著確認 API Pod:
kubectl get pods -n cka-lab -o wide
希望看到:
STATUS
Running
READY
1/1

如果你看到:
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
這時就要想到網路存取規則。
查看:
kubectl get networkpolicy -n cka-lab
我的環境實際看到:

再查看完整設定:
kubectl get networkpolicy -n cka-lab -o yaml
其中最重要的是:
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress

這裡的:
podSelector: {}
不是代表「沒有選任何 Pod」。
反而代表:
選擇這個 Namespace 裡的所有 Pod。
而:
policyTypes:
- Ingress
代表這是一條限制:
進入 Pod 的流量
的 NetworkPolicy。
更重要的是,它完全沒有:
ingress:
Allow 規則。
所以這份 Policy 的效果其實是:
cka-lab 裡所有 Pod
↓
Ingress 預設全部禁止
也就是:
default deny ingress
因為我們另外還有:
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/
如果只是想快速驗證,當然可以暫時刪掉:
kubectl -n cka-lab delete networkpolicy default-deny-ingress
然後重新 curl。
如果突然成功,就能證明確實是 NetworkPolicy。
但是這不應該是最後的解法。
因為前面建立 default-deny-ingress 的目的本來就是:
所有 Pod
預設禁止被任意存取
只有真的需要的流量
才額外開放
所以更合理的做法是:
保留 default-deny-ingress
+
新增 API 的 Allow Policy
先執行:
kubectl get pods --show-labels -n cka-lab
確認 API Pod 是否有:
app=api
例如:
api-xxxxxx 1/1 Running ... app=api
如果你的 Label 不是 app=api,下面的 NetworkPolicy 就要改成你真正的 Label。
建立:
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 都開放。
這裡非常容易搞混。
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
執行:
kubectl apply -f api-ingress.yaml
接著查看:
kubectl get networkpolicy -n cka-lab
現在應該會看到:
整體安全規則變成:
default-deny-ingress
│
├── 所有 Pod
│ 預設禁止 Ingress
│
├── redis-ingress
│ └── API 可以連 Redis:6379
│
└── api-ingress
└── API Pod 開放 TCP:8000
這正是:
Default Deny
+
Explicit Allow
的概念。
現在重新執行:
curl -i \
-H 'Host: api.ironman.test' \
http://127.0.0.1:63909/
如果使用自己的 FastAPI,只要 / 有 Endpoint,正常應該會得到類似:
HTTP/1.1 200 OK

如果你的 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
整條流量成功。
今天設定的是:
HTTP
使用:
Port 80
HTTPS 則可以理解成:
HTTP
+
TLS Encryption
TLS 是負責加密網路連線的機制。
正式環境通常還需要:
TLS Certificate
HTTPS Listener
Port 443
所以:
建立 Gateway 不代表 HTTPS 會自動存在。
今天先把 HTTP Routing、Backend 與 NetworkPolicy 關係搞懂即可。
剛才我們遇到的是:
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

代表:
HTTPRoute 引用的 Resource 無法成功解析。
而:
BackendNotFound
直接告訴我們:
指定的 Backend 根本不存在。
注意這時可能仍然看到:
Accepted=True
這並不矛盾。
因為:
Accepted=True
回答的是:
Gateway 接不接受這一條 Route?
而:
ResolvedRefs=False
回答的是:
Route 裡引用的 Backend 存不存在?
兩個 Condition 回答的是不同問題。
因此排錯時不要只看:
True / False
應該一起看:
type
status
reason
message
因為原本的:
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

再測一次:
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}}'

才是最可靠的方法。
經過這次實驗,可以建立一套固定排錯順序。
第一層先確認:
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 一個一個「穿過」。
而是:
這是一套很好用的除錯思考順序。
理解 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 處理。
先用一張圖來整理:
今天最容易犯的錯,就是把 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 實驗最重要的收穫。