Kubernetes 持久化儲存 PV 與 PVC

前言

在 Kubernetes 環境中,容器的生命週期是短暫的,當 Pod 被刪除或重新調度時,容器內的資料也會隨之消失。為了解決資料持久化的問題,Kubernetes 提供了持久化儲存機制,包括 Persistent Volume(PV)和 Persistent Volume Claim(PVC)。本文將深入介紹這些儲存概念、建立方式以及動態供應機制。

儲存概念

為什麼需要持久化儲存

容器設計的理念是無狀態的,但實際應用中經常需要儲存資料:

  • 資料庫資料:MySQL、PostgreSQL 等資料庫需要持久化儲存
  • 應用程式日誌:日誌檔案需要保留以供分析
  • 使用者上傳檔案:圖片、文件等使用者資料
  • 配置檔案:某些應用需要持久化的配置

Kubernetes 儲存架構

Kubernetes 儲存架構包含以下核心元件:

元件說明
Volume最基本的儲存抽象,與 Pod 生命週期綁定
Persistent Volume (PV)叢集層級的儲存資源,獨立於 Pod 生命週期
Persistent Volume Claim (PVC)使用者對儲存資源的請求
StorageClass定義動態供應儲存的規範

Volume 與 Persistent Volume 的差異

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
┌─────────────────────────────────────────────────────────────┐
│                        Volume                                │
│  - 與 Pod 生命週期綁定                                        │
│  - Pod 刪除時,資料可能遺失                                    │
│  - 適合暫時性資料儲存                                         │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                   Persistent Volume                          │
│  - 獨立於 Pod 生命週期                                        │
│  - Pod 刪除後,資料仍然保留                                    │
│  - 適合需要持久化的資料                                       │
│  - 可在不同 Pod 間共享                                        │
└─────────────────────────────────────────────────────────────┘

PV 建立

PV 基本概念

Persistent Volume(PV)是叢集中的一塊儲存空間,由管理員預先配置或透過 StorageClass 動態供應。PV 是叢集資源,不屬於任何 Namespace。

PV 存取模式

PV 支援以下存取模式:

存取模式縮寫說明
ReadWriteOnceRWO單一節點讀寫掛載
ReadOnlyManyROX多節點唯讀掛載
ReadWriteManyRWX多節點讀寫掛載
ReadWriteOncePodRWOP單一 Pod 讀寫掛載(Kubernetes 1.22+)

PV 回收策略

策略說明
Retain保留資料,需手動處理
Delete刪除 PV 時同時刪除後端儲存
Recycle清除資料後重新使用(已棄用)

建立 hostPath PV

最簡單的 PV 類型,使用節點本地目錄:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /data/pv-data
    type: DirectoryOrCreate
1
2
3
4
5
6
7
8
# 套用 PV 定義
kubectl apply -f pv-hostpath.yaml

# 查看 PV 狀態
kubectl get pv

# 查看 PV 詳細資訊
kubectl describe pv local-pv

建立 NFS PV

使用 NFS 共享儲存:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    server: 192.168.1.100
    path: /exports/data
  mountOptions:
    - hard
    - nfsvers=4.1

建立雲端儲存 PV

AWS EBS PV

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: PersistentVolume
metadata:
  name: aws-ebs-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  awsElasticBlockStore:
    volumeID: vol-0123456789abcdef0
    fsType: ext4

Azure Disk PV

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
apiVersion: v1
kind: PersistentVolume
metadata:
  name: azure-disk-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  azureDisk:
    diskName: myDisk
    diskURI: /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Compute/disks/myDisk
    kind: Managed
    fsType: ext4

GCE Persistent Disk PV

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: PersistentVolume
metadata:
  name: gce-pd-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  gcePersistentDisk:
    pdName: my-data-disk
    fsType: ext4

PVC 使用

PVC 基本概念

Persistent Volume Claim(PVC)是使用者對儲存資源的請求。PVC 會與符合條件的 PV 進行綁定,綁定後 Pod 就可以使用該儲存。

建立 PVC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  # 可選:指定 StorageClass
  # storageClassName: standard
  # 可選:使用標籤選擇特定 PV
  selector:
    matchLabels:
      type: local
1
2
3
4
5
6
7
8
# 套用 PVC 定義
kubectl apply -f pvc.yaml

# 查看 PVC 狀態
kubectl get pvc

# 查看 PVC 詳細資訊
kubectl describe pvc my-pvc

PV 與 PVC 綁定機制

PVC 與 PV 的綁定遵循以下規則:

  1. 容量匹配:PV 容量必須大於或等於 PVC 請求
  2. 存取模式匹配:PV 必須支援 PVC 請求的存取模式
  3. StorageClass 匹配:如果指定 StorageClass,必須相符
  4. 標籤選擇器:如果 PVC 使用 selector,PV 必須符合條件

在 Pod 中使用 PVC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: nginx:1.24
    volumeMounts:
    - name: data-volume
      mountPath: /usr/share/nginx/html
  volumes:
  - name: data-volume
    persistentVolumeClaim:
      claimName: my-pvc

在 Deployment 中使用 PVC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: web-app
        image: nginx:1.24
        ports:
        - containerPort: 80
        volumeMounts:
        - name: web-data
          mountPath: /usr/share/nginx/html
        - name: logs
          mountPath: /var/log/nginx
      volumes:
      - name: web-data
        persistentVolumeClaim:
          claimName: web-data-pvc
      - name: logs
        persistentVolumeClaim:
          claimName: logs-pvc

StatefulSet 與 PVC

StatefulSet 可以為每個 Pod 副本建立獨立的 PVC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: root-password
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: standard
      resources:
        requests:
          storage: 10Gi

這會為每個 Pod 建立獨立的 PVC:data-mysql-0data-mysql-1data-mysql-2

StorageClass

StorageClass 概念

StorageClass 提供了一種描述儲存「類別」的方式,讓管理員可以定義不同的儲存配置。它是動態供應的核心元件。

StorageClass 組成

欄位說明
provisioner指定使用哪個 Volume Plugin 來供應 PV
parameters傳遞給 provisioner 的參數
reclaimPolicyPV 回收策略(Delete 或 Retain)
volumeBindingMode綁定模式(Immediate 或 WaitForFirstConsumer)
allowVolumeExpansion是否允許擴展 Volume 容量

建立 StorageClass

AWS EBS StorageClass

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: aws-ebs-gp3
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

Azure Disk StorageClass

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: azure-managed-premium
provisioner: disk.csi.azure.com
parameters:
  skuName: Premium_LRS
  cachingMode: ReadOnly
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

GCE Persistent Disk StorageClass

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gce-pd-ssd
provisioner: pd.csi.storage.gke.io
parameters:
  type: pd-ssd
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

NFS StorageClass(使用 NFS Provisioner)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storage
provisioner: nfs.csi.k8s.io
parameters:
  server: nfs-server.example.com
  share: /exports/data
reclaimPolicy: Delete
volumeBindingMode: Immediate
mountOptions:
  - hard
  - nfsvers=4.1

設定預設 StorageClass

1
2
3
4
5
6
7
8
# 查看現有 StorageClass
kubectl get storageclass

# 設定預設 StorageClass
kubectl patch storageclass <storage-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

# 移除預設設定
kubectl patch storageclass <storage-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

Volume 綁定模式

Immediate 模式

PVC 建立後立即綁定 PV(預設行為):

1
volumeBindingMode: Immediate

適用情境:

  • 單一可用區域的叢集
  • 不需要考慮 Pod 調度位置

WaitForFirstConsumer 模式

等待 Pod 調度後再綁定 PV:

1
volumeBindingMode: WaitForFirstConsumer

適用情境:

  • 多可用區域的叢集
  • 需要 PV 與 Pod 在同一區域
  • 減少跨區域資料存取延遲

動態供應

動態供應概念

動態供應允許系統根據 PVC 的需求自動建立 PV,無需管理員手動預先建立。這大幅簡化了儲存管理的工作。

動態供應流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│    使用者     │     │  Kubernetes  │     │   Provisioner │
└──────┬───────┘     └──────┬───────┘     └──────┬───────┘
       │                    │                    │
       │ 建立 PVC           │                    │
       │───────────────────>│                    │
       │                    │                    │
       │                    │ 查詢 StorageClass  │
       │                    │───────────────────>│
       │                    │                    │
       │                    │ 建立後端儲存        │
       │                    │<───────────────────│
       │                    │                    │
       │                    │ 建立 PV            │
       │                    │<───────────────────│
       │                    │                    │
       │                    │ 綁定 PVC 與 PV      │
       │                    │                    │
       │ PVC 已綁定         │                    │
       │<───────────────────│                    │
       │                    │                    │

使用動態供應

只需在 PVC 中指定 StorageClass:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dynamic-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: aws-ebs-gp3
  resources:
    requests:
      storage: 20Gi

若使用預設 StorageClass,可省略 storageClassName

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: default-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

完整動態供應範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-storage
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "4000"
  throughput: "250"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-storage
  resources:
    requests:
      storage: 50Gi
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: database
spec:
  replicas: 1
  selector:
    matchLabels:
      app: database
  template:
    metadata:
      labels:
        app: database
    spec:
      containers:
      - name: postgres
        image: postgres:15
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: database-pvc

Volume 擴展

當 StorageClass 允許擴展時,可以增加 PVC 容量:

1
2
# 編輯 PVC 增加容量
kubectl edit pvc database-pvc

或使用 patch:

1
kubectl patch pvc database-pvc -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'

注意事項:

  • 只能增加容量,不能減少
  • 某些儲存類型可能需要重新啟動 Pod
  • 擴展過程可能需要一些時間

最佳實務

1. 選擇適當的存取模式

  • 單一 Pod 使用:選擇 ReadWriteOnce
  • 多 Pod 共享讀取:選擇 ReadOnlyMany
  • 多 Pod 共享讀寫:選擇 ReadWriteMany(需要支援的儲存類型,如 NFS、EFS)

2. 設定資源配額

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: ResourceQuota
metadata:
  name: storage-quota
  namespace: production
spec:
  hard:
    requests.storage: 500Gi
    persistentvolumeclaims: 20

3. 使用 WaitForFirstConsumer

對於多可用區域叢集,建議使用此模式避免跨區域問題:

1
volumeBindingMode: WaitForFirstConsumer

4. 定期備份重要資料

使用 Volume Snapshot 功能進行備份:

1
2
3
4
5
6
7
8
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: database-snapshot
spec:
  volumeSnapshotClassName: csi-aws-vsc
  source:
    persistentVolumeClaimName: database-pvc

5. 監控儲存使用量

1
2
3
4
5
6
7
8
# 查看 PV 使用狀況
kubectl get pv

# 查看 PVC 狀態
kubectl get pvc -A

# 查看 Pod 中的儲存使用
kubectl exec -it <pod-name> -- df -h

常見問題排解

PVC 處於 Pending 狀態

1
2
3
4
5
6
7
8
# 查看 PVC 事件
kubectl describe pvc <pvc-name>

# 常見原因:
# 1. 沒有符合條件的 PV
# 2. StorageClass 不存在
# 3. 容量不足
# 4. 存取模式不匹配

PV 無法釋放

1
2
3
4
5
6
# 查看 PV 狀態
kubectl get pv <pv-name>

# 如果狀態為 Released,需要手動處理
# 清除 claimRef 以重新使用(注意:資料可能仍存在)
kubectl patch pv <pv-name> -p '{"spec":{"claimRef": null}}'

儲存效能問題

  • 選擇適當的儲存類型(SSD vs HDD)
  • 調整 IOPS 和吞吐量參數
  • 考慮使用本地儲存(Local Persistent Volume)以獲得更好效能

總結

本文介紹了 Kubernetes 持久化儲存的核心概念和實作方式:

  1. 儲存概念:理解 Volume、PV、PVC 和 StorageClass 的關係
  2. PV 建立:使用 hostPath、NFS 或雲端儲存建立持久化儲存
  3. PVC 使用:透過 PVC 請求儲存資源並在 Pod 中掛載使用
  4. StorageClass:定義儲存類別,支援不同效能和特性的儲存
  5. 動態供應:自動化儲存供應,簡化管理工作

掌握 Kubernetes 持久化儲存是建構有狀態應用的關鍵技能。建議根據應用需求選擇適當的儲存類型和配置,並遵循最佳實務確保資料的可靠性和可用性。

參考資源

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy