主線

KAI K8s之路 03|Service 是什麼?Pod 會換,入口不能飄

Pod 可以被 Deployment 不斷替換,但呼叫方不能每天追新的 Pod IP。Service 把一組可替換 Pod 包成穩定入口,讓流量能透過 DNS、ClusterIP、selector 和 EndpointSlice 找到真正 ready 的後端。

中文 主線 第 3 篇
先抓住

Pod 會換,IP 會飄;Service 是把一組 Pod 變成穩定入口的網路抽象。

讀完帶走

Debug Service 不要只盯 ClusterIP;順著 selector、EndpointSlice、Pod readiness 和 targetPort 一路查。

上一篇講 Deployment 時,我們先接受一件事:Pod 不該被手動照顧。

Pod 掛了可以重建,版本換了可以 rollout,node 出事也可能被重新排程。這些都很正常。

但這裡馬上出現下一個問題:

如果 Pod 一直會換,別的服務要怎麼連到它?

我不會讓 frontend 去記某個 backend Pod 的 IP。那樣等於叫客人每天追著不同員工跑。

這一章要記的句子很短:

Pod 會換,IP 會飄;Service 是把一組 Pod 變成穩定入口的網路抽象。

先不要追 Pod IP

Pod 有自己的 IP,這點很重要。

但 Pod IP 不是一個穩定承諾。

一個 Deployment 今天有三個 Pod,明天 rollout 之後可能是另外三個 Pod。名字會變,IP 會變,所在 node 也可能變。

如果另一個服務直接連 Pod IP,它其實是在跟「某一次落地的結果」綁死。

Kubernetes 不希望你這樣想。

它希望你把一組可以替換的 Pod 放在一個穩定入口後面,讓呼叫方只記得:

我要找 web 這個服務

而不是:

我要找 10.244.2.17 這個 Pod

這就是 Service 出現的位置。

KAI notebook style diagram showing a stable Kubernetes Service counter in front of changing backend Pods selected by labels.
Service 的重點不是讓某個 Pod 變穩,而是讓一組可替換的 Pod 有一個穩定入口。Pod 可以被換掉,但 client 不應該追著 Pod IP 跑。

用櫃台想 Service

延續上一章的排班例子。

Deployment 像排班表加主管:今天櫃台要有三個人,有人離開就補人,換流程時慢慢替換。

那 Service 是什麼?

我會把它想成「固定櫃台號碼」。

客人不需要知道今天是哪三個員工在後面做事,也不需要知道某個員工換到哪個座位。

客人只需要記得:

我要去 3 號櫃台。

Service 做的就是這件事。

它不等於員工,也不等於排班表。它是一個穩定的入口,後面接的是一組符合條件、當下可以服務的 backend Pods。

這個比喻也順手提醒一件事:

如果櫃台後面沒有人,櫃台號碼還在,但服務不會 magically 成功。

Service 有入口,不代表一定有健康後端。

Service 實際上描述了什麼

最常見的 Service 會長得像這樣:

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - name: http
      port: 80
      targetPort: 8080

我會這樣讀:

  • metadata.name: web:這個穩定入口叫 web
  • type: ClusterIP:預設只在 cluster 內部提供一個穩定 IP
  • selector: app=web:哪些 Pod 算是這個 Service 的後端
  • port: 80:Service 對外呈現的 port
  • targetPort: 8080:真正轉去 Pod 裡 app 監聽的 port

這裡最容易被忽略的是 selector。

Service 不是靠 Deployment 名字找 Pod,也不是靠 Pod 名字找 Pod。

它主要靠 label selector 找 backend。

所以如果 Deployment 的 Pod template label 是:

labels:
  app: api

但 Service selector 寫:

selector:
  app: web

那這個 Service 很可能就沒有後端。

入口還在,但門後面是空的。

Service、DNS、EndpointSlice 怎麼接起來

正常的 ClusterIP Service 會得到一個 cluster 內部 IP,也會有 DNS 名字。

在同一個 namespace 裡,你通常可以用短名:

web

跨 namespace 時,會寫得更清楚:

web.default.svc.cluster.local

但 DNS 只是讓你找到 Service 入口。

真正的 backend list 不是藏在 DNS 裡,而是由 Kubernetes 根據 Service selector 維護成 EndpointSlice。

可以先用這條線記:

client -> Service DNS / ClusterIP -> EndpointSlice -> ready Pod IP:targetPort

更完整一點:

  1. 你建立 Service,寫好 selector
  2. Kubernetes 找到符合 label 的 Pods
  3. EndpointSlice 記錄這些 backend endpoints
  4. 每個 node 上的 service proxy / dataplane 依照這份 endpoint 資訊導流
  5. client 只需要連穩定的 Service 名字或 ClusterIP
KAI notebook style diagram showing a client calling Service DNS and ClusterIP, selector mapping to EndpointSlices, and traffic routed to ready backend Pods.
Service 給 client 穩定地址;EndpointSlice 記錄當下有哪些 backend endpoint。正常場景下,不 ready 或正在終止的 Pod 不應該被當成一般可用後端。

這裡有一個重要細節:Service 不是健康檢查本身。

Pod 能不能進入可用 backend,通常跟 Pod readiness 有關。readiness 沒過,你會看到 Pod 還在,但流量不應該被正常送過去。

所以排查 Service 時,不要只問:

Service 有沒有 IP?

還要問:

EndpointSlice 裡有沒有 ready endpoint?

porttargetPort 不要看混

這個坑非常常見。

Service 的 port 是 client 看到的 port。

Pod 裡 app 真的監聽的,是 targetPort

例如:

ports:
  - name: http
    port: 80
    targetPort: 8080

意思是:

client -> web:80 -> Pod:8080

如果 app 其實聽在 3000,但 Service 寫 targetPort: 8080,Service 入口可能存在,Endpoint 也可能存在,但請求就是打不到正確地方。

我自己的偏好是,production manifest 盡量用 named port 讓語意穩一點:

# Service
ports:
  - name: http
    port: 80
    targetPort: http

# Pod template
ports:
  - name: http
    containerPort: 8080

這樣以後 Pod 內部 port 從 8080 改成 3000 時,只要維持 name: http 的語意,Service 不一定要跟著改數字。

Service type 先不用背太多,但要知道邊界

新手很容易把 Service 想成「對外暴露服務」。

這只對了一部分。

Service 預設的 ClusterIP 是 cluster 內部入口,不是 public internet 入口。

幾個常見 type 可以先這樣放:

  • ClusterIP:預設,給 cluster 內部用
  • NodePort:在每個 node 上開一個固定範圍的 port,通常不是我會直接當正式入口的第一選擇
  • LoadBalancer:請 cloud provider 或外部實作給你一個外部 load balancer
  • ExternalName:用 DNS CNAME 把 Service 名字指到外部 hostname

還有一個常見例外是 headless Service:

clusterIP: None

它不給你一般 ClusterIP,而是讓 DNS 回到後端 endpoint。這常出現在 StatefulSet 或需要 client 自己知道每個 backend 身份的場景。

這篇先不展開,因為主線要先抓住一般 Service 的直覺:

Service 不是「把東西公開到網路上」的同義詞;它先是一個穩定的服務入口抽象。

新手最容易誤會的幾件事

1. 以為 Service 會建立 Pod

Service 不會建立 Pod。

建立和維持 Pod 數量是 Deployment / ReplicaSet 那一層的事。

Service 只是看 label,找到符合條件的 backend,然後提供入口。

所以如果沒有 Pod,或者 selector 對不到 Pod,Service 不會幫你生出後端。

2. 以為 Service IP 通就代表 app 通

Service 有 ClusterIP,只代表入口物件存在。

要真的通,還要看:

  • selector 有沒有選到 Pod
  • EndpointSlice 裡有沒有 endpoint
  • Pod readiness 有沒有過
  • targetPort 有沒有對到 app 監聽的 port
  • app 自己有沒有正常回應

Service 是入口,不是保證成功的魔法門。

3. 把 selector 寫得太隨便

Selector 太窄,會選不到 Pod。

Selector 太寬,可能選到不該接流量的 Pod。

這種錯很麻煩,因為 YAML 看起來都合法,但流量行為會很怪。

我會把 Service selector 當成一條很重要的合約:

哪些 Pod 被允許站在這個入口後面。

4. 把 Service、Ingress、LoadBalancer 混在一起

Service 先解的是:cluster 裡面怎麼給一組 Pod 穩定入口。

Ingress / Gateway 更常是在解:外部 HTTP / HTTPS 流量怎麼進 cluster、怎麼做 host/path routing、TLS、流量策略。

LoadBalancer Service 可以拿到外部入口,但它不是 Ingress 的完整替代品。

先把層次分開,後面會輕鬆很多。

我會怎麼 inspect 一個 Service

如果有人說「這個 svc 不通」,我不會只看 kubectl get svc 就停。

我會順著這條線查:

KAI notebook style debugging map for Kubernetes Service: check Service, selector, EndpointSlice, Pod readiness, and port to targetPort mapping.
Service debug 的第一原則:沒有 endpoints 時,不要先怪 DNS。先確認 selector、Pod label、readiness 和 targetPort,入口背後到底有沒有人。
kubectl get svc -n <ns>
kubectl describe svc <svc-name> -n <ns>

kubectl get endpointslice -n <ns> \
  -l kubernetes.io/service-name=<svc-name>

kubectl get pods -n <ns> --show-labels
kubectl get pods -n <ns> -l app=web -o wide
kubectl describe pod <pod-name> -n <ns>

我會特別看:

  • Service selector 是什麼
  • Pod labels 是否真的 match
  • EndpointSlice 裡有沒有 addresses
  • endpoint condition 是否 ready / serving
  • Service port 和 backend targetPort 是否對得上
  • Pod 的 readiness probe 是否一直失敗

如果要從 cluster 裡驗證 DNS 和 HTTP,可以起一個暫時 client:

kubectl run tmp-curl -n <ns> --rm -it \
  --image=curlimages/curl -- sh

nslookup web
curl -v http://web:80

如果 DNS 找得到,但 EndpointSlice 是空的,問題通常不在 DNS。

如果 EndpointSlice 有 ready endpoint,但 request timeout,才繼續往 targetPort、app listen address、NetworkPolicy、CNI / dataplane 那些方向查。

我對 Service 的記法

我不會把 Service 記成「Kubernetes 的 load balancer」。

這句太粗,容易把 ClusterIP、NodePort、LoadBalancer、Ingress 全部混在一起。

我比較喜歡這樣記:

Service 是一個穩定入口,背後是一份會隨 Pod 變動而更新的 backend 名單。

這樣你排查時就會自然問兩件事:

  1. 入口本身是否存在?
  2. 入口後面的 backend 名單是否正確且可用?

只要這兩件事分開,Service 就不難了。

這篇先記住三件事

  1. Pod IP 不是穩定介面;Service 才是一組 Pod 對外承諾的穩定入口
  2. Service 靠 selector 找 Pod,EndpointSlice 記錄當下可用的 backend endpoints
  3. Debug Service 要順著 Service -> selector -> EndpointSlice -> Pod readiness -> targetPort 查,不要只盯 ClusterIP

下一篇會接著問:Service 給了 cluster 內部穩定入口,那外部 HTTP / HTTPS 流量要怎麼進來?