來到第9天啦!
昨天我們不斷更新 Deployment。
過程中:
舊 Pod 消失
新 Pod 出現
也代表:
Pod IP 可能一直改變。
所以其他 Application 絕對不能寫:
10.244.1.17
直接找某個 Pod。
這就是為什麼需要:
Service
存在的原因。
先:
kubectl get pods -n cka-lab -o wide
記住某個 Pod IP。
刪掉:
kubectl delete pod POD_NAME -n cka-lab
等 Deployment 補新的。
再:
kubectl get pods -n cka-lab -o wide
新 Pod IP 已經不同。
所以 Application 不應依賴 Pod IP。

kubectl expose:kubectl expose deployment api \
--name=api \
--port=80 \
--target-port=80 \
-n cka-lab
kubectl expose 的核心功能就是把一個已經存在的資源(在這裡是名為 api 的 Deployment)公開出來,並為它建立一個全新的 Service 元件
kubectl expose deployment api:告訴 K8s「我要幫一個叫做 api 的 Deployment 建立 Service」。--name=api:新建立的 Service 名字 叫做 api。--port=80:這個 Service 監聽的埠口 是 80(群集內部拜訪此 Service 時使用的 Port)。--target-port=80:流量轉發到後端 Pod 的埠口 是 80。-n cka-lab:在名為 cka-lab 的 命名空間(Namespace) 執行此操作。查看:
kubectl get svc -n cka-lab

port 和 targetPort非常容易搞混。
假設:
Service
Port 80
送進:
Container
Port 8000
就是:
Client
↓
Service :80
↓
Pod :8000
因此:
ports:
- port: 80 (Service 提供給別人進來的)
targetPort: 8000 (Service 要送去的port)
port 是:
Service 對外提供的 Port。
targetPort:
後端 Pod Application 真正 Listen 的 Port。
目前 nginx 剛好兩個都是 80。
後面 FastAPI 就會變成:
Service 80
→
Container 8000
查看:
kubectl get service api \
-n cka-lab \
-o yaml
你會看到:
selector:
app: api

還記得 Day 6?
Service 根本不知道:
Deployment 是誰。
它只知道:
我要標籤是 app=api 的 Pod。
因此:
Service
│ selector app=api
▼
Pod A app=api
Pod B app=api
Pod C app=api

是 Kubernetes 用來取代傳統 Endpoints 資源的解決方案,專門解決大規模叢集下的網路擴展性(Scalability)瓶頸。
在原生設計中,一個 Service 只對應一個 Endpoints 物件:
kube-proxy。Kubernetes 將 Endpoints 拆分成多個「切片(Slices)」:
EndpointSlice 最多只容納 100 個 Endpoint。kubectl get endpointslices -n cka-lab

此圖可看到 名為 api 的 Service 目前找到了 2 個健康的後端 Pod,且流量已經準備好可以打給它們了。你會看到 Service 對應的 Endpoint。
各欄位拆解如下:
api-r4w2m):api 開頭,後綴隨機字串。IPv4):80):10.244.1.9,10.244.2.8):這是最重要的核心資訊。 代表目前有兩個實際運行的 Pod IP 位址。
kubectl get pods -n cka-lab -o wide,會發現這兩個 IP 正好就是你那兩個 api Pod 的 IP。17m):10.96.x.x)。10.244.1.9:80 與 10.244.2.8:80。如果這時候你將 Deployment 擴展到 3 個 Pod,這裡的 ENDPOINTS 就會自動變成 3 個 IP;反之若 Pod 掛掉或未就緒,對應的 IP 就會立刻從這裡被移除。
可以理解:
Service 規則
↓
EndpointSlice
↓
目前真正可送流量的 Pod IP
因此 Service 打不到時:
EndpointSlice
是超級重要的排錯線索。
在 Kubernetes 中,Service 的 ClusterIP 與內部域名只能在叢集內部的網路環境被訪問。一般我們會建立一個「一次性(Ephemeral)的測試 Pod」來驗證網路與服務連線。
kubectl run test-curl -n cka-lab --image=busybox:1.28 --restart=Never -- wget -O- http://api

kubectl run test-curl
作用:在叢集中建立並執行一個名為 test-curl 的 Pod。
-n cka-lab(或 --namespace=cka-lab)
作用:指定將這個測試 Pod 建立在 cka-lab 命名空間下。同命名空間才能直接使用「Service 短名稱」發送請求。
--image=busybox:1.28
作用:指定 Pod 運行的容器映像檔。
**為何推薦 busybox:1.28**:
體積極小(數 MB)、拉取極快。
內建標準 shell、wget 與 nslookup 工具。
CKA 考試環境中最穩定,不會像 curlimages/curl 有預設 entrypoint 覆蓋或引數解析問題。
--restart=Never
作用:告訴 Kubernetes 不要建立 Deployment 或 ReplicaSet,而是建立一個純粹的獨立 Pod。當裡面的指令執行完畢(不管成功或失敗)就不會自動重啟。
wget -O- http://api
作用:在容器內執行的網路請求指令。
-O-(大寫 O 接減號 -):將取得的網頁內容導向標準輸出(stdout),直接印在終端畫面上,而不是存成檔案。
http://api:目標 Service 的名稱與 Port 80(HTTP 預設)。
驗證: 查看該 Pod 的日誌輸出:
kubectl logs test-curl -n cka-lab
執行成功之現象
若連線成功,終端機會直接印出 Nginx 的 HTML 首頁原始碼:

http://api 就會通?(CoreDNS 底層原理)我們在指令中完全沒有輸入任何 IP 位址(如 10.96.x.x 或 Pod IP),但它依然能找到目標服務。背後是靠 Kubernetes 的內建 DNS(CoreDNS)與 Linux 的解析機制協同完成。

Kubernetes Service 的標準完整格式為:
<SERVICE_NAME>.<NAMESPACE>.svc.cluster.local
因此,在 cka-lab 命名空間下的 api Service,其完整 FQDN 為:
api.cka-lab.svc.cluster.local
api:Service 的名稱。cka-lab:所屬的 Namespace。svc:代表這是一條 Service 類型的 DNS 紀錄(區別於 Pod IP 紀錄)。cluster.local:叢集的預設網域後綴(Domain Suffix)。/etc/resolv.conf)當 Pod 建立時,Kubelet 會自動在容器內的 /etc/resolv.conf 寫入 DNS 設定:
nameserver 10.96.0.10
search cka-lab.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
nameserver 10.96.0.10:指向叢集內部 CoreDNS 的 ClusterIP。search ...:搜尋後綴列表。當你在 Pod 裡發出 api 請求時,系統會依序拿後綴拼接:api + cka-lab.svc.cluster.local ➔ 成功命中 CoreDNS 紀錄!REDIS_HOST=redis
DATABASE_HOST=postgres
default 命名空間,想呼叫 cka-lab 的服務,不能只寫 api(會被補成 api.default... 導致解析失敗),必須至少明確帶上 Namespace:http://api.cka-lab
# 或使用完整 FQDN
http://api.cka-lab.svc.cluster.local
在 Kubernetes 中,最常見的架構失誤之一是 「Service 的 Selector 與 Pod 的 Label 對不上」。
執行 patch 指令,把 Service 監聽的標籤改為不存在的 app: wrong:
kubectl patch service api \
-n cka-lab \
-p '{"spec":{"selector":{"app":"wrong"}}}'

patch service api:對名為 api 的 Service 進行局部設定修改(不需要重寫整個 YAML)。-p(--patch):傳入要覆蓋的 JSON / YAML 格式字串。'{"spec":{"selector":{"app":"wrong"}}}':將 spec.selector 欄位改為只尋找帶有標籤 app=wrong 的 Pod。檢查 Pod 狀態:
kubectl get pods -n cka-lab

Running。檢查 Service 的後端轉發清單(Endpoints / EndpointSlices):
kubectl get endpoints api -n cka-lab
# 或新版建議:
kubectl get endpointslices -n cka-lab
ENDPOINTS 欄位會變成 <none>,原本掛在後面的 Pod IP(如 10.244.1.9:80)全部消失。
kubectl exec -n cka-lab nginx -- curl -sI http://api

curl 的 Exit Code 7 在官方定義中是:
CURLE_COULDNT_CONNECT(7): Failed to connect() to host or proxy.
(即底層作業系統回傳了Connection refused)
DNS 解析成功:nginx Pod 成功將 api 解析為 Service 的 ClusterIP(10.96.204.166)。
連線被拒絕:封包送到該 IP 後,因為你之前執行了:Bash
kubectl patch service api -n cka-lab -p '{"spec":{"selector":{"app":"wrong"}}}'
Service 的後端轉發清單(Endpoints)是空的,核心網路找不到任何實體 Pod 來接收流量,直接回絕 TCP 連線,因此 curl 回報 Exit Code 7。
將 Service 的 Selector 改回正確匹配 Nginx Pod 的標籤(app: api):
kubectl patch service api \
-n cka-lab \
-p '{"spec":{"selector":{"app":"api"}}}'

kubectl get endpoints api -n cka-lab

ENDPOINTS 欄位應重新出現 Pod 的 IP 與 Port(例如 10.244.1.9:80,10.244.2.8:80)。kubectl exec -n cka-lab nginx -- curl -sI http://api

kubectl exec:在 Kubernetes 叢集中「已經正在運行的 Pod」內部執行指令(類似遠端連進容器操作)。
-n cka-lab:指定目標 Pod 所在的命名空間(Namespace)為 cka-lab。
nginx:指定要在哪一個 Pod 裡面執行(借用叢集裡原本就活著、名為 nginx 的 Pod)。
--(參數分隔符號):
-- 前面是給 kubectl 自己的參數。
-- 後面則是直接丟進 nginx 容器內部執行的指令。
curl -sI http://api:在容器內執行的網路請求指令:
-s(silent):安靜模式,不印出下載進度條與統計資訊。
-I(head only):只抓取 HTTP 標頭(Headers),不下載整份 HTML 網頁內容,適合用來快速確認服務狀態碼與伺服器資訊。
http://api:目標服務名稱。同命名空間下會由 CoreDNS 自動解析成 Service 的 ClusterIP。
HTTP/1.1 200 OK
Server: nginx/1.27.5
Date: Mon, 07 Sep 2026 11:14:34 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Wed, 16 Apr 2025 12:55:34 GMT
Connection: keep-alive
ETag: "67ffa8c6-267"
Accept-Ranges: bytes
HTTP/1.1 200 OK:最關鍵的一行。狀態碼 200 代表請求成功送達、後端正常處理並成功回應。Server: nginx/1.27.5:代表處理這個請求的後端伺服器是版本為 1.27.5 的 Nginx。Content-Type: text/html 與 Content-Length: 615:代表如果下載網頁主體,會拿到一份長度為 615 Bytes 的 HTML 內容(即標準的 "Welcome to nginx!" 預設頁面)。這份回應證明了四件事:
nginx Pod 順利將短域名 api 解析為 Service 的 ClusterIP。app: wrong 的 Selector 已經修正回 app: api。<none>),kube-proxy / CNI 正確將流量轉發給後端容器。今天要建立這張圖:
Client
↓
Service
↓
Selector
↓
EndpointSlice
↓
Pod
Service 的價值不是:
讓 Pod 可以跑。
而是:
替一群不穩定、隨時可能被替換的 Pod 提供穩定入口。
明天開始,不再使用 nginx 當主角。
我們要建立真正屬於這系列的 Application!