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 節點。
  • 缺點
    1. 要錢:雲端廠商通常會對 每個 Load Balancer 收費 (例如 AWS ELB 一個約 $20/月)。
    2. 成本高:如果你有 10 個服務要對外開放,就要開 10 個 Load Balancer,費用會很驚人(這也是為什麼後來會有 Ingress 的原因)。
  • 適用場景:正式環境的對外服務入口。
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 80
如果你是在 Minikube 或地端 (Bare Metal) 環境,因為沒有雲端廠商支援,IP 通常會一直顯示 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。

應用實例:前後端溝通

假設你開發了一個 購物網站,架構如下:

  1. Frontend: 前端網站 (Node.js)。
  2. 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 ClusterMongoDB 時,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: ClusterIPtype: 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 的工作。