昨天用 emptyDir 暫時掛載 MySQL 的儲存,但 emptyDir 是跟著 Pod 生滅的——Pod 重啟,資料就全部消失了。今天來學 PV 和 PVC,讓 MySQL 的資料真正持久化。
K8s 的 Pod 是短暫的(ephemeral)。Pod 重啟、重建、搬到其他 Node,裡面的資料全部消失。
對 todo-api 和 todo-frontend 這類無狀態服務來說沒有問題,但對 MySQL 來說是致命的——資料庫重啟之後資料全部不見,這當然不能接受 !
K8s 用三個概念解決這個問題:PV、PVC、StorageClass。
PV 是叢集層級的儲存資源,代表實際存在的一塊儲存空間,可以是:
PV 是由叢集管理員建立或由 StorageClass 自動建立的,跟 Pod 的生命週期無關。
PVC 是 Pod 對儲存空間的「申請單」。Pod 不直接使用 PV,而是透過 PVC 宣告「我需要多少空間、什麼存取模式」,K8s 再去找符合條件的 PV 來綁定。
Pod → PVC(我需要 5Gi 的空間)→ PV(這裡有 10Gi 的空間)→ 實際儲存
這個設計讓 Pod 的定義不需要知道底層儲存的細節,換個雲端平台只需要換 PV,Pod 和 PVC 的定義不用改。
StorageClass 定義儲存的「等級」和佈建方式。有了 StorageClass,PVC 提出申請之後,K8s 會自動建立對應的 PV,不需要管理員手動建立。
PVC 提出申請
│
▼
StorageClass(動態佈建)
│
▼
自動建立 PV 並綁定
minikube 有內建一個預設的 StorageClass(standard),會自動幫你建立 PV,開發環境直接用就好。
今天目標:確認 minikube 的 StorageClass,更新 MySQL StatefulSet 加上 volumeClaimTemplates 申請 PVC,並透過刪除 Pod 重建後資料仍然存在,驗證持久化成功。
kubectl get storageclass

standard 就是 minikube 內建的 StorageClass,標示 (default) 代表 PVC 沒有指定 StorageClass 的時候會自動使用它。
StatefulSet 有一個特別的欄位 volumeClaimTemplates,讓每個 Pod 自動建立各自獨立的 PVC:
調整 /Todo-App/k8s/mysql-statefulset.yaml:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
tier: database
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_ROOT_PASSWORD
- name: MYSQL_DATABASE
value: "tododb"
- name: MYSQL_USER
value: "user"
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_PASSWORD
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql # MySQL 資料存放的路徑
volumeClaimTemplates: # 每個 Pod 自動建立獨立的 PVC
- metadata:
name: mysql-data
spec:
accessModes:
- ReadWriteOnce # 同時只能被一個 Node 掛載
resources:
requests:
storage: 5Gi # 申請 5GB 的儲存空間
kubectl apply -f mysql-statefulset.yaml
踩坑小紀錄:
volumeClaimTemplates不可變更如果你是從 Day 12 的 StatefulSet(沒有
volumeClaimTemplates)升級過來,套用時會遇到這個錯誤:The StatefulSet "mysql" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'revisionHistoryLimit', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden
volumeClaimTemplates是建立後就不能修改的欄位。解法是刪掉舊的 StatefulSet 再重建:kubectl delete statefulset mysql --cascade=foreground kubectl apply -f mysql-statefulset.yaml
--cascade=foreground會等 Pod 完全終止後才結束,確保乾淨重建。
kubectl get pvc

STATUS 是 Bound 代表 PVC 已經成功綁定到一個 PV。
PVC 的名稱格式是 <volumeClaimTemplate名稱>-<pod名稱>,也就是 mysql-data-mysql-0。如果 StatefulSet 有多個副本,mysql-1 會有自己的 mysql-data-mysql-1,彼此獨立。
kubectl get pv

PV 是由 StorageClass 自動建立的,不需要手動建立。
建立一筆測試資料:
kubectl exec -it mysql-0 -- mysql -u root -prootpassword tododb -e "
CREATE TABLE IF NOT EXISTS test (id INT, name VARCHAR(50));
INSERT INTO test VALUES (1, 'hello');
SELECT * FROM test;
"

強制刪除 Pod,讓 StatefulSet 重建:
kubectl delete pod mysql-0
等 Pod 重建完成後,確認資料還在:
kubectl get pods # 等到 mysql-0 再次 Running
kubectl exec -it mysql-0 -- mysql -u root -prootpassword tododb -e "SELECT * FROM test;"

Pod 重建之後資料還在,持久化成功。
在開啟前端畫面之前,因為剛才刪除重建了 MySQL StatefulSet,todo-api 的資料庫連線已經中斷,需要先讓它重新連線:
#確認 mysql-0 已經 Running
kubectl get pods -w
#重啟 todo-api 讓它重新連上 MySQL
kubectl rollout restart deployment/todo-api
kubectl rollout status deployment/todo-api # 看到 successfully rolled out 再繼續
確認 todo-api 正常後,再進行以下步驟:
瀏覽器開啟 Todo-app 前端畫面:
Windows 若開不起來,請先確認終端有執行:
kubectl port-forward -n ingress-nginx service/ingress-nginx-controller 8888:80
在畫面上新增幾筆測試資料。
刪除 mysql Pod:
kubectl delete pod mysql-0
kubectl get pods -w
kubectl rollout restart deployment/todo-api
kubectl rollout status deployment/todo-api # 看到 successfully rolled out 再繼續
AccessMode 有三種:
ReclaimPolicy(回收策略):
PVC 刪除之後,對應的 PV 怎麼處理:
生產環境的 StorageClass:
minikube 的 standard 只適合本地開發。生產環境通常用雲端平台提供的 StorageClass:
gp2(EBS)、gp3
standard(Persistent Disk)managed-premium(Premium SSD)換雲端平台的時候,只需要改 StorageClass 名稱,PVC 和 StatefulSet 的定義不需要動,這就是 K8s 抽象層的好處。
今天學了 PV 和 PVC,讓 MySQL 的資料真正持久化:
到目前為止,Todo App 的三個服務都跑在 K8s 上了:todo-api(Deployment)、todo-frontend(Deployment)、mysql(StatefulSet + PVC)。
明天會學 Probe,確保 K8s 知道 Pod 什麼時候真的健康、什麼時候該重啟 !