iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0

昨天用 emptyDir 暫時掛載 MySQL 的儲存,但 emptyDir 是跟著 Pod 生滅的——Pod 重啟,資料就全部消失了。今天來學 PV 和 PVC,讓 MySQL 的資料真正持久化。


為什麼需要持久化儲存

K8s 的 Pod 是短暫的(ephemeral)。Pod 重啟、重建、搬到其他 Node,裡面的資料全部消失。

對 todo-api 和 todo-frontend 這類無狀態服務來說沒有問題,但對 MySQL 來說是致命的——資料庫重啟之後資料全部不見,這當然不能接受 !

K8s 用三個概念解決這個問題:PV、PVC、StorageClass。


PV、PVC、StorageClass 的關係

PersistentVolume(PV)

PV 是叢集層級的儲存資源,代表實際存在的一塊儲存空間,可以是:

  • 本機磁碟(hostPath,測試用)
  • NFS
  • 雲端儲存(AWS EBS、GCP Persistent Disk)

PV 是由叢集管理員建立或由 StorageClass 自動建立的,跟 Pod 的生命週期無關。

PersistentVolumeClaim(PVC)

PVC 是 Pod 對儲存空間的「申請單」。Pod 不直接使用 PV,而是透過 PVC 宣告「我需要多少空間、什麼存取模式」,K8s 再去找符合條件的 PV 來綁定。

Pod → PVC(我需要 5Gi 的空間)→ PV(這裡有 10Gi 的空間)→ 實際儲存

這個設計讓 Pod 的定義不需要知道底層儲存的細節,換個雲端平台只需要換 PV,Pod 和 PVC 的定義不用改。

StorageClass

StorageClass 定義儲存的「等級」和佈建方式。有了 StorageClass,PVC 提出申請之後,K8s 會自動建立對應的 PV,不需要管理員手動建立。

PVC 提出申請
    │
    ▼
StorageClass(動態佈建)
    │
    ▼
自動建立 PV 並綁定

minikube 有內建一個預設的 StorageClass(standard),會自動幫你建立 PV,開發環境直接用就好。


實際操作

今天目標:確認 minikube 的 StorageClass,更新 MySQL StatefulSet 加上 volumeClaimTemplates 申請 PVC,並透過刪除 Pod 重建後資料仍然存在,驗證持久化成功。

Step1: 確認 minikube 的 StorageClass

kubectl get storageclass

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

standard 就是 minikube 內建的 StorageClass,標示 (default) 代表 PVC 沒有指定 StorageClass 的時候會自動使用它。

Step2: 更新 MySQL StatefulSet,加上 PVC

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 完全終止後才結束,確保乾淨重建。

Step3: 確認 PVC 和 PV 的狀態

kubectl get pvc

https://ithelp.ithome.com.tw/upload/images/20260921/201838633Dl7AFavgg.png

STATUS 是 Bound 代表 PVC 已經成功綁定到一個 PV。

PVC 的名稱格式是 <volumeClaimTemplate名稱>-<pod名稱>,也就是 mysql-data-mysql-0。如果 StatefulSet 有多個副本,mysql-1 會有自己的 mysql-data-mysql-1,彼此獨立。

kubectl get pv

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

PV 是由 StorageClass 自動建立的,不需要手動建立。

Step4: 驗證資料持久化

建立一筆測試資料:

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;
"

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

強制刪除 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;"

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

Pod 重建之後資料還在,持久化成功。

Step5: Todo-App 測試資料持久化

在開啟前端畫面之前,因為剛才刪除重建了 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 正常後,再進行以下步驟:

  1. 瀏覽器開啟 Todo-app 前端畫面:

    • Linux/Mac:http://192.168.49.2
    • Windows:http://127.0.0.1:8888

    Windows 若開不起來,請先確認終端有執行:

    kubectl port-forward -n ingress-nginx service/ingress-nginx-controller 8888:80
    
  2. 在畫面上新增幾筆測試資料。
    https://ithelp.ithome.com.tw/upload/images/20260921/20183863xz2OnN8bPx.png

  3. 刪除 mysql Pod:

   kubectl delete pod mysql-0
  1. 等 mysql-0 重新回到 Running:
   kubectl get pods -w
  1. mysql-0 重啟後,todo-api 有時候會因為資料庫連線中斷而卡住,手動重啟讓它重新連線:
   kubectl rollout restart deployment/todo-api
   kubectl rollout status deployment/todo-api   # 看到 successfully rolled out 再繼續
  1. 重新整理瀏覽器,剛才新增的 Todo 應該都還在——代表資料持久化成功。
    https://ithelp.ithome.com.tw/upload/images/20260921/20183863uiUEvEZyMu.png

延伸筆記:StorageClass 和動態佈建

AccessMode 有三種:

  • ReadWriteOnce(RWO):同時只能被一個 Node 掛載,最常用,適合資料庫
  • ReadOnlyMany(ROX):可以被多個 Node 同時唯讀掛載
  • ReadWriteMany(RWX):可以被多個 Node 同時讀寫,需要支援此模式的儲存後端(例如 NFS、CephFS)

ReclaimPolicy(回收策略):

PVC 刪除之後,對應的 PV 怎麼處理:

  • Delete:PV 跟著刪除(minikube 預設)
  • Retain:PV 保留,需要手動清理,適合生產環境避免誤刪資料

生產環境的 StorageClass:

minikube 的 standard 只適合本地開發。生產環境通常用雲端平台提供的 StorageClass:

  • AWS:gp2(EBS)、gp3
  • GCP:standard(Persistent Disk)
  • Azure:managed-premium(Premium SSD)

換雲端平台的時候,只需要改 StorageClass 名稱,PVC 和 StatefulSet 的定義不需要動,這就是 K8s 抽象層的好處。


小結

今天學了 PV 和 PVC,讓 MySQL 的資料真正持久化:

  • emptyDir:跟 Pod 生滅,Pod 重啟資料消失,只適合暫時用途
  • PV:叢集層級的實際儲存資源
  • PVC:Pod 對儲存空間的申請單,跟 PV 解耦
  • StorageClass:自動動態佈建 PV,不需要手動建立
  • volumeClaimTemplates:StatefulSet 的每個 Pod 自動建立各自獨立的 PVC

到目前為止,Todo App 的三個服務都跑在 K8s 上了:todo-api(Deployment)、todo-frontend(Deployment)、mysql(StatefulSet + PVC)。

明天會學 Probe,確保 K8s 知道 Pod 什麼時候真的健康、什麼時候該重啟 !


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

尚未有邦友留言

立即登入留言