昨天幫 todo-api 設定了滾動更新策略,推了 v2 版本上去,觀察了 Pod 逐批替換的過程,也試了 rollback。今天要學 Service——目前 todo-api 和 todo-frontend 各自跑著 2 個 Pod,但 Pod 之間互相連不到、外部也連不進來,Service 就是用來解決這兩件事的。
Pod 有兩個特性讓它不適合直接被連線:
Service 解決這兩個問題的方式是:提供一個固定的虛擬 IP,流量打進來之後自動分配給後面的 Pod。Pod IP 怎麼變都不影響,Service 用 Label Selector 動態找到目前健康的 Pod,把流量送過去。
只在 Cluster 內部可存取,外部連不進來。適合服務之間的內部溝通。
Cluster 內部 → Service → Pod
外部 ✗ 無法存取
例如:
frontend呼叫todo-api、todo-api連mysql,這種流量不需要對外開放,用 ClusterIP 就夠了。
在每個 Node 上開一個固定的 port(預設範圍 30000–32767),外部可以透過 Node IP:NodePort 連進來。
外部 → Node IP:30080 → Service → Pod
缺點:port 號不好記、需要知道 Node 的 IP,不適合正式對外服務。通常只在開發測試用。
在雲端平台(AWS、GCP、Azure)上建立一個真正的負載平衡器,提供一個對外的固定 IP。適合正式環境對外開放服務。
外部 → LoadBalancer IP → Service → Pod
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 對應。| Service | 類型 | 說明 |
|---|---|---|
mysql-service |
ClusterIP | 只給 todo-api 在 Cluster 內部連線 |
todo-api-service |
ClusterIP | 只給 frontend 在 Cluster 內部呼叫 |
todo-frontend-service |
ClusterIP | 統一入口,之後接 Ingress |
| 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 驗證前後端都能正常連線。
/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(後續章節會學到)。
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
kubectl get pods -l app=mysql

kubectl describe service mysql-service

有 Endpoints 就代表 Service 成功找到 Pod。之後 todo-api 可以用 mysql-service:3306 這個固定名稱連到資料庫。
/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
Endpoints 欄位,應該會看到 todo-api Pod 的 IP 列表:Endpoints: 10.244.0.5:8000,10.244.0.9:8000
在 /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

kubectl port-forward service/todo-api-service 8000:80
# 另一個 terminal
curl http://localhost:8000/todos
看到 API 回應就代表 Service 正常運作了。
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 不固定和流量分配的問題:
現在 MySQL、todo-api 和 todo-frontend 各有一個 Service,Cluster 內部已經可以互相溝通了,但外部還沒有統一的入口。
明天學 Ingress,把 /api 路由到後端、/ 路由到前端,讓整個 Todo App 的請求路徑完整跑通 !