iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0

昨天幫 todo-api 設定了滾動更新策略,推了 v2 版本上去,觀察了 Pod 逐批替換的過程,也試了 rollback。今天要學 Service——目前 todo-api 和 todo-frontend 各自跑著 2 個 Pod,但 Pod 之間互相連不到、外部也連不進來,Service 就是用來解決這兩件事的。


為什麼需要 Service

Pod 有兩個特性讓它不適合直接被連線:

  • Pod IP 不固定:Pod 每次重建 IP 都會改變,其他服務無法靠 IP 穩定連線。
  • 多個副本難以分配流量:Pod 跑了多個副本就有多個 IP,呼叫方不知道該連哪個,也沒辦法自己分流。

Service 解決這兩個問題的方式是:提供一個固定的虛擬 IP,流量打進來之後自動分配給後面的 Pod。Pod IP 怎麼變都不影響,Service 用 Label Selector 動態找到目前健康的 Pod,把流量送過去。


Service 的三種類型

1. ClusterIP(預設)

只在 Cluster 內部可存取,外部連不進來。適合服務之間的內部溝通。

Cluster 內部  →  Service  →  Pod
外部          ✗  無法存取

例如:frontend 呼叫 todo-api、todo-api 連 mysql,這種流量不需要對外開放,用 ClusterIP 就夠了。

2. NodePort

在每個 Node 上開一個固定的 port(預設範圍 30000–32767),外部可以透過 Node IP:NodePort 連進來。

外部  →  Node IP:30080  →  Service  →  Pod

缺點:port 號不好記、需要知道 Node 的 IP,不適合正式對外服務。通常只在開發測試用。

3. LoadBalancer

在雲端平台(AWS、GCP、Azure)上建立一個真正的負載平衡器,提供一個對外的固定 IP。適合正式環境對外開放服務。

外部  →  LoadBalancer IP  →  Service  →  Pod

Service YAML 拆解

apiVersion: v1
kind: Service
metadata:
  name: todo-api-service
spec:
  type: ClusterIP          # Service 類型,預設就是 ClusterIP
  selector:
    app: todo-api          # 用 Label 找到對應的 Pod
  ports:
    - port: 80             # Service 對外暴露的 port
      targetPort: 8000     # 轉發到 Pod 的哪個 port
  • selector:用 Label Selector 找到對應的 Pod,這裡會找所有帶 app: todo-api 的 Pod。
  • port:其他服務連這個 Service 時用的 port。
  • targetPort:Service 把流量轉發到 Pod 的哪個 port,要跟 Pod 的 containerPort 對應。

環境說明

  • 正式運作時,三個服務都走 ClusterIP,frontend 和 todo-api 在 Cluster 內部互相溝通,再透過 Ingress 統一對外。
Service 類型 說明
mysql-service ClusterIP 只給 todo-api 在 Cluster 內部連線
todo-api-service ClusterIP 只給 frontend 在 Cluster 內部呼叫
todo-frontend-service ClusterIP 統一入口,之後接 Ingress
  • 測試時,frontend 和 todo-api 通常會改用 NodePort,讓外部可以直接透過 Node IP + port 在瀏覽器操作。
Service 類型 說明
todo-api-service NodePort 透過 Node IP + port 直接測試 API
todo-frontend-service NodePort 透過 Node IP + port 直接開瀏覽器

⚠️ 本系列使用 minikube + Docker driver,minikube 跑在 Docker container 裡,Mac / Windows 的本機路由不到 Docker 內部網路,所以 NodePort 也連不進來。

你的電腦(Mac/Windows)
    │
    │  ✗ 路由不到 Docker 內部網路
    │
    ▼
Docker container(= minikube 的「Node」)
    │  內部網路 172.17.x.x 或 192.168.x.x
    ▼
    Kubernetes Cluster
        ├── Pod
        └── Service(NodePort 30080)

因此改用 port-forward 在本機和 Cluster 之間開一條臨時隧道來測試:

你的電腦
    ├── localhost:8000  →  kubectl port-forward  →  todo-api-service  →  todo-api Pod
    └── localhost:8080  →  kubectl port-forward  →  todo-frontend-service  → frontend Pod

實際操作

今天目標:幫 MySQL、todo-api、todo-frontend 各建一個 Service,讓 Cluster 內部可以互相溝通,並用 port-forward 驗證前後端都能正常連線。

Step 1:建立 MySQL 的 Deployment 與 Service

  1. todo-api 需要連接資料庫,所以先把 MySQL 跑起來。在 /Todo-App/k8s 目錄下建立 mysql-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: "password"
            - name: MYSQL_DATABASE
              value: "tododb"
            - name: MYSQL_USER
              value: "user"
            - name: MYSQL_PASSWORD
              value: "password"
          ports:
            - containerPort: 3306

⚠️ 密碼直接寫在 YAML 是學習用的簡化做法,正式環境應使用 Kubernetes Secret(後續章節會學到)。

  1. 再建立 mysql-service.yaml:
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  selector:
    app: mysql
  ports:
    - port: 3306
      targetPort: 3306
kubectl apply -f mysql-deployment.yaml
kubectl apply -f mysql-service.yaml
  1. 確認 Pod 跑起來(MySQL 啟動比較慢,可能要等 20–30 秒):
kubectl get pods -l app=mysql

https://ithelp.ithome.com.tw/upload/images/20260918/20183863gvFIJIdDdr.png

  1. 確認 Service 有接到 Pod:
kubectl describe service mysql-service

https://ithelp.ithome.com.tw/upload/images/20260921/20183863KDi3maFwd9.png

有 Endpoints 就代表 Service 成功找到 Pod。之後 todo-api 可以用 mysql-service:3306 這個固定名稱連到資料庫。

Step 2:建立 todo-api 的 Service

  1. 在 /Todo-App/k8s 目錄下建立 todo-api-service.yaml:
apiVersion: v1
kind: Service
metadata:
  name: todo-api-service
spec:
  type: ClusterIP
  selector:
    app: todo-api
  ports:
    - port: 80
      targetPort: 8000
kubectl apply -f todo-api-service.yaml
kubectl describe service todo-api-service
  1. 看 Endpoints 欄位,應該會看到 todo-api Pod 的 IP 列表:
Endpoints: 10.244.0.5:8000,10.244.0.9:8000

Step 3:建立 todo-frontend 的 Service

在 /Todo-App/k8s 目錄下建立 todo-frontend-service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: todo-frontend-service
spec:
  type: ClusterIP
  selector:
    app: todo-frontend
  ports:
    - port: 80
      targetPort: 80
kubectl apply -f todo-frontend-service.yaml
kubectl get services

https://ithelp.ithome.com.tw/upload/images/20260918/201838635WSOYPByiu.png

Step 4:測試連線

  1. 測試 todo-api
kubectl port-forward service/todo-api-service 8000:80
# 另一個 terminal
curl http://localhost:8000/todos

看到 API 回應就代表 Service 正常運作了。

  1. 測試 todo-frontend
kubectl port-forward service/todo-frontend-service 8080:80

開瀏覽器打 http://localhost:8080,能看到前端畫面就代表整條路通了。

踩坑提醒:如果前端畫面出現 fail to fetch todos,很可能是 todo-api 在 MySQL 還沒準備好時就先啟動,導致連線失敗。確認 MySQL Pod 是 Running 狀態後,重啟 API 即可:

kubectl get pods -l app=mysql   # 確認 STATUS 是 Running
kubectl rollout restart deployment/todo-api

小結

今天學了 Service,解決了 Pod IP 不固定和流量分配的問題:

  • ClusterIP:Cluster 內部使用,適合服務間的內部溝通
  • NodePort:透過 Node IP + port 對外,適合開發測試
  • LoadBalancer:雲端平台建立負載平衡器,正式環境對外使用

現在 MySQL、todo-api 和 todo-frontend 各有一個 Service,Cluster 內部已經可以互相溝通了,但外部還沒有統一的入口。

明天學 Ingress,把 /api 路由到後端、/ 路由到前端,讓整個 Todo App 的請求路徑完整跑通 !


上一篇
Day 07|Rolling Update
下一篇
Day 09|Ingress
系列文
從零學 K8s|30 天核心概念 × 實作,新手也能真正掌握 Kubernetes 共 15 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言