Kubernetes Service - 穩定的網路存取入口
在 Kubernetes 中,Pod 的 IP 位址是 不穩定 的。 當 Pod 死掉重啟,新 Pod 會獲得一個新 IP。這使得後端服務 (Backend) 無法為前端 (Frontend) 提供穩定的存取點。
Service 資源就是為了解決這個問題。它提供了一個 固定的 IP (ClusterIP) 和 DNS 名稱,並負責將流量負載平衡 (Load Balancing) 到後端的一組 Pods。
定義 Service
建立 nginx-service.yaml:
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx-service
spec:
selector:
app: nginx # 關鍵:這裡要跟 Deployment 的 matchLabels 一樣
ports:
- protocol: TCP
port: 80 # Service 曝露的 Port
targetPort: 80 # 轉發到 Pod 的 Port (容器的 Port)
type: ClusterIP # (預設值)
這樣建立後,K8s 的 DNS 系統會自動解析這個名稱。叢集內的其他應用(例如後端程式)可以直接使用 http://my-nginx-service 來連線,就像你在瀏覽器輸入 google.com 一樣,完全不需要知道背後 Pod 的真實 IP 是多少。
測試範例:
我們可以起一個臨時的 Pod 來測試連線:
# 啟動一個帶有 curl 的臨時 Pod
kubectl run -it --rm test-connection --image=curlimages/curl -- sh
# 在 Pod 裡面執行 curl,直接使用 Service 名稱
curl http://my-nginx-service
# 你會看到 Nginx 的 Welcome 頁面 HTML
# ...
Service 的類型 (Service Types)
K8s 提供了幾種 Service 類型,決定了外界如何存取這個服務:
1. ClusterIP (預設)
- 僅限叢集內部存取。外部網路無法直接連線。
- 分配一個內部虛擬 IP (VIP),例如
10.96.0.1。 - 適用場景:資料庫、後端微服務 (不需對外公開)。
存取方式:
- 內部 Pod:直接連線
http://<Service-Name>。 - 外部 (測試用):雖然外部無法直接連,但我們可以透過
kubectl port-forward指令將流量轉發到本機:kubectl port-forward service/my-nginx-service 8080:80 # 現在能在本機瀏覽器打開 http://localhost:8080
2. NodePort
- 開放節點端口。
- 在每個 Node 上開放一個靜態 Port (預設範圍 30000-32767),讓外部可以透過
<NodeIP>:<NodePort>存取。 - 適用場景:開發測試、簡單對外服務。
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30007 # 可以指定 (需在範圍內),若不指定則隨機分配
存取方式:
假設你的 K8s 節點 (或 Minikube IP) 是 192.168.49.2,那你就可以在瀏覽器輸入:
http://192.168.49.2:30007 來存取服務。不管你連到哪一個節點,Kubernetes 都會幫你轉發到正確的 Pod。
3. LoadBalancer (外部負載平衡器)
這是最簡單直接讓 外部網路 (Internet) 存取你服務的方式。
當你將 Service 設為 LoadBalancer 時,K8s 會向你的雲端服務商(如 AWS, GCP, Azure)發出請求:「請給我一個真實的負載平衡器和一個公開 IP (Public IP)」。
- 運作方式:雲端廠商收到請求後,會佈署一個實體的 Load Balancer (如 AWS ELB),並將它的 IP 指向你的 K8s 節點。
- 缺點:
- 要錢:雲端廠商通常會對 每個 Load Balancer 收費 (例如 AWS ELB 一個約 $20/月)。
- 成本高:如果你有 10 個服務要對外開放,就要開 10 個 Load Balancer,費用會很驚人(這也是為什麼後來會有 Ingress 的原因)。
- 適用場景:正式環境的對外服務入口。
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
Pending (除非安裝了 MetalLB 等外掛)。4. ExternalName
這就像是給外部服務取一個 「別名 (Alias)」。
假設你的資料庫是在 AWS RDS 上,網址很長:mysql-prod.c5x8.us-east-1.rds.amazonaws.com。
你不希望在每個 App 的設定檔裡都寫這個長網址(萬一 AWS 網址換了,要改很多地方)。
你可以建立一個 ExternalName Service,取名叫 my-db,並指向那個長網址。
以後 App 只要連線到 my-db,K8s 就會自動幫你轉導到 AWS RDS。
kind: Service
metadata:
name: my-db # 內部 App 使用這個短名稱連線
spec:
type: ExternalName
externalName: mysql-prod.c5x8.us-east-1.rds.amazonaws.com # 實際的外部網址
服務發現與 DNS (Service Discovery)
Kubernetes 有一個內建的 DNS 伺服器 (CoreDNS)。每當你建立一個 Service,K8s 就會自動為它建立一筆 DNS 紀錄。
格式通常是:<service-name>.<namespace>.svc.cluster.cluster.local
假設你的 Service 名稱是 my-nginx,命名空間是 default:
- 同一個 namespace 的 Pod 可以直接用
my-nginx存取。 - 不同 namespace 的 Pod 必須用完整名稱
my-nginx.default存取。
這讓微服務之間的溝通變得非常簡單,不需要寫死 IP。
應用實例:前後端溝通
假設你開發了一個 購物網站,架構如下:
- Frontend: 前端網站 (Node.js)。
- Backend: 訂單系統 API (Python)。
你幫後端建立了一個 Service 名叫 backend-api。
在前端的程式碼中,你完全不用擔心後端 Pod 的 IP 是什麼,直接呼叫名稱即可:
// Node.js 範例
const response = await axios.get('http://backend-api/orders');
不管後端的 Pod 怎麼重啟、漂移或擴展,這個 http://backend-api 永遠有效。這就是 Service Discovery 的強大之處。
Headless Service (無頭服務)
一般的 Service 會有一個 Cluster IP (VIP),就像是一個 「總機小姐」。 當你打電話給總機 (連線到 VIP),你不知道也不在乎最後是分給哪位員工 (Pod) 接聽,反正有人接就好。
但有時候,你想要拿到 「員工通訊錄」,直接打給特定的員工 (Pod)。 例如:部署 Redis Cluster 或 MongoDB 時,Master 和 Slave 做的事情不一樣,你必須明確知道「誰是 Master、誰是 Slave」,並且直接連線到該 Pod IP,不能透過總機隨機轉發。
這時我們將 .spec.clusterIP 設為 None,這就是 Headless Service。
apiVersion: v1
kind: Service
metadata:
name: my-headless-service
spec:
clusterIP: None # 關鍵:設為 None,表示不需要「總機 (VIP)」
selector:
app: nginx
ports:
- port: 80
效果: 當你查詢 my-headless-service 的 DNS 時,K8s 不會給你一個 VIP,而是直接回給你 所有後端 Pod 的 IP 列表。讓用戶端自己決定要連哪一個。
如何查詢?
我們可以使用 nslookup 指令來驗證:
# 1. 啟動一個 debug pod
kubectl run -it --rm debug --image=busybox -- sh
# 2. 在裡面查詢 Headless Service 的 DNS
nslookup my-headless-service
# 3. 輸出結果 (會看到多個 IP,對應到後端的每個 Pod)
# Name: my-headless-service
# Address 1: 10.244.1.5
# Address 2: 10.244.2.8
# Address 3: 10.244.0.6
應用實例:資料庫叢集
假設你架設了一個 PostgreSQL Cluster (Master-Slave 架構):
postgres-0: Master (負責寫入)postgres-1: Slave (負責讀取)
應用程式需要進行「寫入」操作時,絕不能隨機連,必須精準連到 Master。
使用 Headless Service 後,應用程式可以直接解析 postgres-0.my-headless-service 拿到 Master 的 IP,確保資料寫入正確的地方。
Service 類型比較表
| 類型 | 存取範圍 | 特點 | 適用場景 |
|---|---|---|---|
| ClusterIP | 僅限叢集內部 | 預設類型,提供內部 VIP | 內部微服務、資料庫 |
| NodePort | 叢集外部 | 在每個節點開 Port (30000+) | 開發測試、非 HTTP 服務 |
| LoadBalancer | 叢集外部 | 整合雲端 LB,有獨立 Public IP | 正式環境對外服務 (HTTP/HTTPS) |
| ExternalName | 叢集內部 -> 外部 | 映射到外部 DNS CNAME | 連接外部既有服務 (如 AWS RDS) |
實戰:將服務曝露給外部
在 Minikube 環境中,我們可以使用 NodePort 或 minikube service 指令。
修改 type: ClusterIP 為 type: NodePort,然後 apply:
kubectl apply -f nginx-service.yaml
kubectl get svc
你會看到類似 80:31234/TCP,表示你可以透過 http://<minikube-ip>:31234 存取。
在 Minikube 中,可以直接用指令自動打開瀏覽器:
minikube service my-nginx-service
常用指令 (Common Commands)
除了手寫 YAML,你也可以使用 kubectl 指令快速建立與除錯 Service。
1. 快速建立 Service (kubectl expose)
如果你已經有一個 Deployment (例如 nginx-deploy),可以使用 expose 指令快速為它建立 Service:
# 為名為 nginx-deploy 的 Deployment 建立 Service
# --port: Service 要開的 Port
# --target-port: 容器的 Port (若不指定則預設與 port 相同)
# --type: Service 類型 (ClusterIP, NodePort, LoadBalancer)
kubectl expose deployment nginx-deploy --name=my-nginx --port=80 --target-port=80 --type=ClusterIP
這會自動產生一個 Service,並且 Selector 會自動對應到該 Deployment 的 Pod。
2. 檢查後端 Pod (kubectl get endpoints)
Service 建立後,最常見的問題是 「連不到」。這通常是因為 Label Selector 寫錯,導致 Service 找不到任何 Pod。
檢查的最佳方式是查看 Endpoints (簡寫 ep):
kubectl get endpoints my-nginx-service
- 正常情況:
ENDPOINTS欄位會顯示一個或多個 IP:Port (例如10.244.1.2:80,10.244.1.3:80)。 - 異常情況:
ENDPOINTS顯示<none>。這代表 Service 找不到任何符合 Label 的 Pod,請檢查 YAML 中的selector與 Pod 的labels是否完全一致。
3. 查看詳細資訊 (kubectl describe)
如果要查看完整的 Service 設定與事件:
kubectl describe service my-nginx-service
你可以在這裡看到 Selector, Type, IP, Port 以及最下面的 Endpoints 列表。
總結
Service 為動態的 Pod 提供了穩定的網路入口。但如果是 HTTP/HTTPS 服務,NodePort 只能開 Port,LoadBalancer 又很貴。有沒有辦法像 Nginx Virtual Host 那樣,用不同的 Domain 指向不同服務?
有的,這就是 Ingress 的工作。