---
# System prepended metadata

title: '[元件][儲存] PV & PVC'
tags: [k8s, pvc, PersistentVolume, K8s / 元件, PersistentVolumeClaim, pv, Kubernetes]

---

[元件][儲存] PV & PVC
===
###### tags: `K8s / 元件`
###### tags: `Kubernetes`, `k8s`, `PersistentVolume`, `pv`, `PersistentVolumeClaim`, `pvc`

<br>

[TOC]

<br>

## 術語
- Claim: 一張請求
- PVC: 是使用者的請求
- PV: 是系統或管理者提供的資源
- 靜態的 StorageClass: 是指 `sc.provisioner: kubernetes.io/no-provisioner` 

<br>

## 錯誤原因 - 翻譯對照
- no persistent volumes available for this claim and no storage class is set
    - 找不到可用來滿足此要求的 Persistent Volume，且未設定 StorageClass。
- Error from server (BadRequest): error when creating "xxx.yaml": PersistentVolume in version "v1" cannot be handled as a PersistentVolume: strict decoding error: unknown field "spec.persistantVolumeReclaimPolicy
    - 從伺服器返回的錯誤（BadRequest）：
        - 在建立 `xxx.yaml` 時發生錯誤：
        - API 版本為 `"v1"` 的 PersistentVolume 無法被處理：
        - 嚴格解碼錯誤：未知欄位 `"spec.persistantVolumeReclaimPolicy"`
    - diff
        ```
        persistantVolumeReclaimPolicy
        persistentVolumeReclaimPolicy
        -------^---------------------
        ```

<br>

## spec

### 📦 SC - StorageClass.spec 欄位整理

| 欄位 | 必填 | 預設值 | Enum | 常用性 | 說明 |
|------|------|--------|------|--------|------|
| `provisioner` | ✅ | 無 | 無限制（取決於 CSI 插件） | ⭐ 常用 | 動態卷提供者，如：<br>`kubernetes.io/no-provisioner`, `nfs.csi.k8s.io`, `driver.longhorn.io` |
| `parameters` | ❌ | 無 | 無 | ⭐ 常用 | 提供給 provisioner 的設定參數，例如 NFS server、path 等 |
| `reclaimPolicy` | ❌ | `Delete` | `Retain`, `Delete`, `Recycle`（Recycle 已淘汰） | ⭐ 常用 | Volume 被 release 後要如何處理資料 |
| `volumeBindingMode` | ❌ | `Immediate` | `Immediate`, `WaitForFirstConsumer` | ⭐ 常用 | PVC 什麼時候綁定 PV（建議使用 `WaitForFirstConsumer`） |
| `allowVolumeExpansion` | ❌ | `false` | `true`, `false` | 🔸 偶用 | 是否允許 PVC 擴充儲存空間 |
| `mountOptions` | ❌ | 無 | 無固定 | 🔸 偶用 | 掛載時的附加參數，如：`["vers=4.1"]` |

---

### 📦 PV - PersistentVolume.spec 欄位整理

| 欄位 | 必填 | 預設值 | Enum | 常用性 | 說明 |
|------|------|--------|------|--------|------|
| `capacity.storage` | ✅ | 無 | - | ⭐ 常用 | 容量，如 `"5Gi"`、`"500Mi"` |
| `accessModes` | ✅ | 無 | `ReadWriteOnce`, `ReadOnlyMany`, `ReadWriteMany`, `ReadWriteOncePod` (v1.22+) | ⭐ 常用 | Volume 支援的存取模式 |
| `storageClassName` | ❌ | 無 | 無限制 | ⭐ 常用 | 對應的 StorageClass 名稱，與 PVC 對接 |
| `persistentVolumeReclaimPolicy` | ❌ | `Retain` | `Retain`, `Delete`, `Recycle` | ⭐ 常用 | PV 被 release 後要如何處理資料 |
| `volumeMode` | ❌ | `Filesystem` | `Filesystem`, `Block` | ⭐ 常用 | 掛載成目錄或裸區塊 |
| `hostPath.path` | ✅ (for `hostPath`) | 無 | 無 | ⭐ 常用（開發用） | 指定 host 上的路徑，僅適用於單節點或測試環境 |
| `local.path` | ✅ (for `local`) | 無 | 無 | ⭐ 常用 | Local Volume 的實體掛載路徑（需搭配 nodeAffinity） |
| `nodeAffinity` | ✅ (for `local`) | 無 | 無 | ⭐ 常用 | 限制 PV 綁定在哪個 node（對應 local.path） |
| `csi.driver` | ✅ (for CSI) | 無 | 無 | 🔸 特殊用途 | 指定使用哪個 CSI 插件 |
| `nfs.server`, `nfs.path` | ✅ (for NFS) | 無 | 無 | 🔸 特殊用途 | NFS 存取參數，若使用 `nfs` provisioner |

---

### 📦 PVC - PersistentVolumeClaim.spec 欄位整理

| 欄位 | 必填 | 預設值 | Enum | 常用性 | 說明 |
|------|------|--------|------|--------|------|
| `accessModes` | ✅ | 無 | `ReadWriteOnce`, `ReadOnlyMany`, `ReadWriteMany`, `ReadWriteOncePod` | ⭐ 常用 | 要求使用的存取方式，需與 PV 匹配 |
| `resources.requests.storage` | ✅ | 無 | - | ⭐ 常用 | 請求容量，如 `5Gi` |
| `storageClassName` | ❌ | default StorageClass | 任意已定義的名稱 | ⭐ 常用 | 若省略則綁定 default StorageClass |
| `volumeMode` | ❌ | `Filesystem` | `Filesystem`, `Block` | ⭐ 常用 | 申請哪種掛載方式 |
| `volumeName` | ❌ | 無 | 任意已存在的 PV 名稱 | 🔸 偶用 | 明確指定要綁定的 PV |
| `selector.matchLabels` | ❌ | 無 | 無 | 🔸 偶用 | 用來篩選對應的 PV |
| `dataSource.kind` | ❌ | 無 | `PersistentVolumeClaim`, `VolumeSnapshot`, `Populator` | 🔸 特殊用途 | 從現有 PVC / 快照 clone（需 CSI 支援） |
| `dataSource.name` | ✅（若使用 dataSource） | 無 | 無 | 🔸 特殊用途 | 要 clone 的來源名稱 |
| `dataSource.apiGroup` | ❌ | 視 driver 而定 | 無 | 🔸 特殊用途 | 例如：`snapshot.storage.k8s.io` |

---

### 🧠 延伸說明：accessModes 各模式適用場景

| 模式 | 說明 | 適用場景 |
|------|------|-----------|
| `ReadWriteOnce` | 單節點可讀寫 | 常見情境，local hostPath, EBS |
| `ReadOnlyMany` | 多節點唯讀 | 少見（如多 Pod 同時讀取） |
| `ReadWriteMany` | 多節點可讀寫 | NFS、Ceph、GlusterFS |
| `ReadWriteOncePod` | 單 Pod 限定綁定 | v1.22+ 特性，提升安全性 |

---

### (PVC 綁定到 PV) 比對邏輯順序（簡化解釋）

Kubernetes 在尋找可以與 PVC 綁定的 PV 時，大致會：

1. 找出 `status.phase == Available` 的 PV
2. 過濾出與 PVC 相同 `storageClassName` 的 PV
3. 過濾掉容量不足的 PV（PV 必須 ≥ PVC 請求）
4. 比對 accessModes，PVC 的模式必須是 PV 的子集
5. 比對 volumeMode（通常都是 `Filesystem`）
6. 若 PVC 有 `volumeName`，**只考慮指定的 PV**
7. 若 PVC 有 `selector`，比對 PV 的 labels
8. 綁定第一個符合條件的 PV（非排序式匹配）


<br>

<hr>

<br>

## 常見最小配置

### SC (StorageClass) 最小配置
```yaml=
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
```


### PV (PersistentVolume) 最小配置
```yaml=
...
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  local:
    path: /mnt/data/my_local_storage
  storageClassName: standard
```


### PVC (PersistentVolumeClaim) 最小配置
```yaml=
...
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard
```

### `pv.spec.nodeAffinity` 設定範例
```yaml=
spec:
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - worker-node-1
```

<br>

<hr>

<br>

## 範例：將 storage 綁定到特定 node (`kubernetes.io/hostname=esc4000`)
> 使用「**local PV + nodeAffinity**」的做法，讓 PVC **只能綁到** `esc4000` 這台 node 上的 **`/models`**。
> by gpt-5.2 (2026/01/15)

### 需求
- **labels**
    ```
    kubernetes.io/hostname=esc4000
    nvidia.com/gpu.machine=ESC4000
    ```
- **PV 用 `local.path: /models`**
- **PV 用 `nodeAffinity` 鎖死 `kubernetes.io/hostname = esc4000`**
    - **綁定 PV 到單一 node：用 `hostname`（必要）**
    - GPU machine label 只是「可用條件」，但**不適合拿來鎖單一 node**（可能存在多台同型號）。
- StorageClass 用 `kubernetes.io/no-provisioner`（靜態 local PV）
- 建議 `volumeBindingMode: WaitForFirstConsumer`，讓排程更穩

---

### (1)（選用）確認 hostname label

K8s 預設就會有 `kubernetes.io/hostname`：

```bash
kubectl get node esc4000 --show-labels | tr ',' '\n' | grep hostname
```

你會看到類似：
`kubernetes.io/hostname=esc4000`

---

### (2)（選用）建 StorageClass（local 靜態 PV）

> `WaitForFirstConsumer` 建議保留：Pod 用到 PVC 時才真正做 binding / 排程決策。

```yaml=
# sc-local-models.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-models
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
```

套用：

```bash
kubectl apply -f sc-local-models.yaml
```

:::info
:bulb: **如果不建立 StorageClass 物件，PV & PVC storageClassName 設定如下**：
```yaml
# 可以允許 local-models 物件不存在
spec:
  storageClassName: local-models
```
或是：
```yaml
# - 不使用 StorageClass
# - 若 PVC 用 storageClassName: ""，那 PV 也必須是 storageClassName: "" 才能配對
spec:
  storageClassName: ""
```
- **PV/PVC 的 `storageClassName` 必須一致**（字串相同就會配對），而且 **K8s 不會強制那個 StorageClass 一定要存在**。
- 若要用 `WaitForFirstConsumer`，就**應該建立 StorageClass 物件**。
- 若不建立 StorageClass，就要明講：你放棄 `WaitForFirstConsumer` 這個優點，且要理解可能的排程差異。
:::

---

### (3) 建 PV（綁定到 node + /models）

容量你可以自行調整，這邊範例使用 `4Ti`。

```yaml=
# pv-models-esc4000.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-models-esc4000
  labels:
    purpose: models
    node: esc4000
spec:
  storageClassName: local-models
  volumeMode: Filesystem
  capacity:
    storage: 4Ti
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  local:
    path: /models
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            # Lock to this exact node
            - key: kubernetes.io/hostname
              operator: In
              values:
                - esc4000
```

套用：

```bash
kubectl apply -f pv-models-esc4000.yaml
```

- 為什麼不用 label `nvidia.com/gpu.machine=ESC4000`？
    - 因為那個 label 可能會同時命中多台相同機型；你要「綁死某一台」，`hostname` 最準。
- `spec.capacity.storage` **不會真的對 /models 做 quota**（local volume 不是 LVM quota），它只是給 scheduler / binder 參考。實際可用空間仍取決於 node 的檔案系統剩餘空間。

---

### (4) 建 PVC（指定 storageClass + 選到那顆 PV）

用 selector 鎖 PV label，避免誤綁到別的 PV：

```yaml=
# pvc-models-esc4000.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-models-esc4000
  namespace: default
spec:
  storageClassName: local-models
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Ti
  selector:
    matchLabels:
      purpose: models
      node: esc4000
```

套用：

```bash
kubectl apply -f pvc-models-esc4000.yaml
```

---

### (5) Pod / Deployment 使用範例（掛載到容器內）

```yaml=
apiVersion: v1
kind: Pod
metadata:
  name: test-models-mount
spec:
  # Optional: make it explicit
  nodeSelector:
    kubernetes.io/hostname: esc4000

  containers:
    - name: app
      image: ubuntu:24.04
      command: ["bash", "-lc", "ls -al /mnt/models && sleep 3600"]
      volumeMounts:
        - name: models
          mountPath: /mnt/models
  volumes:
    - name: models
      persistentVolumeClaim:
        claimName: pvc-models-esc4000
```
- **不一定需要 nodeSelector**
    - 只要 StorageClass 是 `WaitForFirstConsumer`，PVC 會驅動排程把 Pod 放到符合 PV nodeAffinity 的那台 node。
    - 若你想更直觀也可以加上 nodeSelector。
- **如果你沒有 WaitForFirstConsumer**（例如 StorageClass 沒建），PVC 可能先被綁定，然後 Pod 排程再遇到限制，會更容易出現 Pending / 綁定不如預期。

---

### 常見坑提醒（很重要）

1. **`/models` 目錄必須在該 node 真實存在**
2. **權限**：容器內 user 可能沒權限讀寫 `/models` 的檔案。必要時：
   * 讓容器用 root 跑（快速驗證）
   * 或用 `securityContext.fsGroup` / 調整 host 端檔案權限
3. **ReadWriteOnce**：多個 Pod 只要都在同一台 node，通常仍可共用（但要注意你的應用是否同時寫入造成競態）。

<br>

<hr>

<br>


## 討論 - StorageClass
### 純 K8s 是否可以建立 storageClassName: standard？
- ### gpt-4o (2025/05/31)
    
    是的，**在純 Kubernetes 環境中**，您**可以自行建立名為 `standard` 的 `StorageClass`**，只要您提供一個 **對應的 Volume Provisioner**。

    但要注意的是：

    > 🚫 Kubernetes 不會自動幫你建立 `standard` 或任何 `StorageClass`  
    > ✅ 您必須**自己建立**這個 `StorageClass`，並且確保有一個 **Provisioner** 來處理 PVC 配置

    ---

    ### ✅ 方法一：建立一個簡易的 hostPath StorageClass（單節點開發用）

    #### 適合情境：
    - 開發或測試用
    - 節點固定，不考慮動態多節點共享

    #### 建立 StorageClass：
    ```yaml=
    # storageclass-hostpath.yaml
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: standard
    provisioner: kubernetes.io/no-provisioner
    volumeBindingMode: WaitForFirstConsumer
    ```

    > 這個 `standard` 不會自動產生 PV，而是用於搭配 **手動靜態建立的 PV**

    套用：
    ```
    kubectl apply -f storageclass-hostpath.yaml
    ```

    然後您需要建立對應的 `PersistentVolume`（hostPath）來手動對應。

    ---

    ### ✅ 方法二：安裝動態 StorageClass（推薦：NFS provisioner）

    若您希望 PVC 能自動綁定 Volume（像 Minikube 那樣），則需安裝動態 provisioner，例如：

    - [nfs-subdir-external-provisioner](https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner)
    - [openebs-hostpath](https://openebs.io/)
    - [longhorn](https://longhorn.io/)（支援高可用）

    這類 Provisioner 安裝後會幫您建立類似：
    ```yaml
    provisioner: nfs.csi.k8s.io
    ```

    ---

    ### 🧩 實務上常見做法

    | 環境         | 建議 StorageClass 名稱 | Provisioner 說明                      |
    |--------------|------------------------|---------------------------------------|
    | Minikube     | `standard`             | `k8s.io/minikube-hostpath`（預設）    |
    | Dev/單節點   | `manual` / `standard`  | `kubernetes.io/no-provisioner` + PV   |
    | NFS 共用環境 | `nfs-client`           | `nfs-subdir-external-provisioner`     |
    | 高可用需求   | `longhorn`             | `driver.longhorn.io`                  |

    ---

    ### ✅ 總結

    | 問題 | 是否可以在純 K8s 建立 `standard` StorageClass？ |
    |------|------------------------------------------------|
    | 答案 | ✅ 可以，只要您定義它，並指定一個合法 provisioner |


<br>

<hr>

<br>

### 如果不建立 StorageClass，會有問題嗎？PV/PVC 兩邊用相同字串就好？
- ### gpt-5.2 (2026/01/15)
    可以**不建立 StorageClass**，但要注意兩個點：

    1. **PV/PVC 的 `storageClassName` 必須一致**（字串相同就會配對），而且**K8s 不會強制那個 StorageClass 一定要存在**；
    2. **PVC 如果不寫 `storageClassName`**，在大多數叢集（有 default StorageClass 時）會被**自動補成 default SC**，結果就會去走動態供應/或配不到你這顆 local PV。

    ---

    ### 你問的「PV/PVC 兩邊用相同字串就好？」— 可以

    **可以**，即使你沒有建立那個 StorageClass 物件，只要：

    * PV：`storageClassName: local-models`
    * PVC：`storageClassName: local-models`

    兩邊一致，PVC 就能跟該 PV 綁起來（如果還有 `selector` + `nodeAffinity` 更穩）。

    ---

    ### 但我更常用的「不建 StorageClass」寫法：兩邊都用空字串

    這個做法的目標是：**完全避免 default StorageClass 介入**。

    #### PV（不填或填空都行）

    ```yaml
    spec:
      storageClassName: ""
    ```

    #### PVC（一定要明確填空字串，避免被補 default）

    ```yaml
    spec:
      storageClassName: ""
    ```

    > 如果 PVC 省略 `storageClassName`（不寫），很容易被套上 default SC，然後就不會去綁你的 local PV 了。

    ---

    ### 不建 StorageClass 的代價是什麼？

    **你就用不到 `volumeBindingMode: WaitForFirstConsumer`**（那是 StorageClass 的屬性）。

    * 有 SC + WaitForFirstConsumer：Pod 要用 PVC 時才做最後綁定/排程，對「有多顆 local PV、或 pod 有更多限制」的情境更安全。
    * 沒 SC：PVC 可能會「先綁死」某顆 PV，再發現 Pod 因其他限制排不上那台 node（若 PV 已經用 hostname 鎖死單一 node，通常問題不大）。

    ---

    ### 結論（照你的需求最簡單）

    * **可以不建 StorageClass。**
    * 要嘛 **PV/PVC 都寫同一個字串**（例如 `local-models`），要嘛 **兩邊都寫 `storageClassName: ""`**。
    * 我會建議你用 **`storageClassName: ""`** 這套，最不怕 default StorageClass 搞事。

    如果你貼一下 `kubectl get storageclass` 的輸出，我也可以直接告訴你：你目前叢集是不是有 default SC、以及你現在這個 PVC 省略 `storageClassName` 會不會被自動套上 default。




<br>

<hr>

<br>

## 討論

### 單節點 Kubernetes 環境(control-plane & worker 共用)是否需要設定 nodeAffinity？
- ### gpt-4o (2025/05/31)

    ### 🎯 結論先講：
    > ✅ **如果您是單節點 K8s 環境（control-plane 與 worker 同一台）**，那麼：
    >
    > 🔹 **`nodeAffinity` 是「非必要」的，可以省略。**  
    > 🔹 但如果您 **指定了 `local.path`（hostPath）**，就必須確保該目錄存在於這唯一的節點上。

    ---

    ### 📌 為什麼可以省略 `nodeAffinity`？

    - 當 Kubernetes 找到 PVC → PV 綁定時，
    - 若該 PV 是 `local.path` 類型（即 `hostPath`），K8s 只要找得到可掛載的 PV 即會使用；
    - 若整個 cluster 只有一個 node，則根本無選擇問題，不需 affinity 篩選。

    ### 🧩 延伸補充：什麼時候建議寫 `nodeAffinity`？

    | 使用情境 | 是否建議寫 nodeAffinity？ |
    |----------|-----------------------------|
    | 多節點環境（worker-1 / worker-2） | ✅ 建議（避免 PVC 被排到不對的 node） |
    | local volume 特定硬碟 mount point | ✅ 建議（確保 PV 是在正確 node） |
    | 單節點開發 / 測試 | ❌ 可省略 |


<br>

<hr>

<br>
  

### 單節點：使用 local volume 建立 Hello World 資料卷，不含 nodeAffinity
> 無動態 StorageClass 環境的配置
- ### gpt-4o (2025/05/31)
    
    在單節點 Kubernetes 環境下（control-plane 與 worker 同台），我們可以建立一個不含 nodeAffinity 的 `PersistentVolume`（PV），並配合 `PersistentVolumeClaim`（PVC）來掛載 `hostPath` 本地資料夾。本範例以 `hello-pv` / `hello-pvc` 為例：

    ---

    #### 步驟 1：建立對應目錄於主機上

    請先登入該單一節點，並建立實體資料夾：

    ```bash
    ssh <your-node-name>
    sudo mkdir -p /mnt/data/hello
    sudo chmod 777 /mnt/data/hello
    ```

    ---

    #### 步驟 2：建立 StorageClass（若尚未存在）

    ```yaml=
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: standard
    provisioner: kubernetes.io/no-provisioner
    volumeBindingMode: WaitForFirstConsumer
    ```

    套用：

    ```bash
    kubectl apply -f storageclass.yaml
    ```

    ---

    #### 步驟 3：建立 PersistentVolume（PV）

    ```yaml=
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: hello-pv
    spec:
      capacity:
        storage: 2Gi
      volumeMode: Filesystem
      accessModes:
        - ReadWriteOnce
      persistentVolumeReclaimPolicy: Retain
      storageClassName: standard
      local:
        path: /mnt/data/hello
    ```

    ---

    #### 步驟 4：建立 PersistentVolumeClaim（PVC）

    ```yaml=
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: hello-pvc
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 2Gi
      storageClassName: standard
    ```

    ---

    #### 步驟 5：建立測試 Pod 並掛載 PVC

    ```yaml=
    apiVersion: v1
    kind: Pod
    metadata:
      name: hello-pod
    spec:
      containers:
        - name: hello
          image: busybox
          command: [ "sleep", "3600" ]
          volumeMounts:
            - mountPath: /mnt/hello
              name: hello-volume
      volumes:
        - name: hello-volume
          persistentVolumeClaim:
            claimName: hello-pvc
    ```

    ---

    #### 檢查結果

    ```bash
    kubectl get pvc
    kubectl get pv
    kubectl get pod hello-pod
    ```

    確認 PVC 狀態為 `Bound`，Pod `Running` 即成功掛載。

    ---

    #### 適用情境與注意事項

    | 情境         | 是否需要 nodeAffinity |
    |--------------|------------------------|
    | 單節點開發環境 | ❌ 可省略             |
    | 多節點部署     | ✅ 建議設定           |
    | 需要特定節點存取本地磁碟 | ✅ 建議設定 |

    此範例適用於無動態 StorageClass 環境，可協助您使用本地磁碟進行簡單測試與開發。

<br>

<hr>

<br>

### 為什麼 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 都要指定類似的屬性？
- ### gpt-4o (2025/05/31)
    
    ### ❓是否只寫 PVC 就能用？
    > ❌ 不行，只寫 PVC 是不夠的，因為：
    - Kubernetes 要將 PVC 綁定到 PV，需要一個「匹配邏輯」來確認該 Volume 能滿足需求
    - 所以：
      - ✏️ PVC 是使用者的**請求**
      - 📦 PV 是系統或管理者提供的**資源**

    ---

    ### ✅ 結論先講：

    | 問題 | 答案 |
    |------|------|
    | PVC 和 PV 是否要同時存在？ | ✅ 是的，**兩個都要有**（除非使用動態 provisioner） |
    | 兩者 `accessModes`、`storageClassName` 是否都要寫？ | ✅ 是的，系統透過這些欄位**匹配** PVC ↔ PV |
    | 屬性重複合理嗎？ | ✅ 合理，因為 PVC 和 PV 是兩方提出要求 & 資源，要能彼此符合 |

    ---

    ### 📦 具體範例說明

    #### 🧾 1. PersistentVolume（靜態提供的資源）
    ```yaml=
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: example-pv
    spec:
      capacity:
        storage: 5Gi
      accessModes:
        - ReadWriteOnce         # ❗供應者說：我只支援 RWO 模式
      storageClassName: standard
      local:
        path: /mnt/data/example
    ```

    📌 這個 PV 的意思是：
    >「我有 5Gi 空間，在某節點的 `/mnt/data/example`，支援 `ReadWriteOnce`，屬於 `standard` 類別。」

    ---

    #### 🧾 2. PersistentVolumeClaim（使用者的需求）
    ```yaml=
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: example-pvc
      namespace: demo
    spec:
      accessModes:
        - ReadWriteOnce         # ❗使用者說：我只需要能單點寫入
      resources:
        requests:
          storage: 3Gi          # ❗我需要至少 3Gi 空間
      storageClassName: standard
    ```

    📌 這個 PVC 的意思是：
    >「我需要一個 3Gi 空間的 Volume，要能 `ReadWriteOnce`，使用 `standard` 類別。」

    ---

    ### ✅ 綁定邏輯（系統會怎麼比對）

    Kubernetes 在 PVC 建立後，會找出「能符合這些條件的 PV」：
    - `accessModes` 必須包含所需的模式（PV: `RWO`，PVC: `RWO` ✅）
    - `storageClassName` 要一致（都是 `standard` ✅）
    - PV 的容量要 >= PVC 的請求（5Gi >= 3Gi ✅）

    → 成功綁定！`example-pvc` 會被綁定到 `example-pv`。

    ---

    ### 🧠 類比：飯店訂房系統比喻

    | 對象 | 類比說明 |
    |------|---------|
    | PersistentVolume (PV) | 飯店提供的房型，例如「雙人房 / 10坪 / 有窗」 |
    | PersistentVolumeClaim (PVC) | 客人的需求：「我要雙人房、至少 8 坪、有窗」 |
    | 比對邏輯 | 只有飯店房型（PV）滿足條件，訂單（PVC）才會成功成立 |

    ---

    ### ✅ 補充：什麼情況只寫 PVC 就夠？

    當您使用的是**支援動態配置的 StorageClass（有 provisioner）**，例如：

    ```yaml
    storageClassName: nfs-client
    ```

    這時 PVC 建立後，Kubernetes 會觸發 provisioner 自動建立一個 PV 並綁定回來。所以您只需寫 PVC 即可。

    ---

    ### 🔚 小結

    | 問題 | 答案 |
    |------|------|
    | 為什麼屬性會重複？ | 因為 Kubernetes 要做比對，需求和供應雙方都必須列出 |
    | 如果沒有動態 provisioner，是否一定要手動寫 PV？ | ✅ 是的 |
    | `accessModes`、`storageClassName` 是否一定要兩邊都寫？ | ✅ 是，否則會匹配失敗或綁定不了 |


<br>

<hr>

<br>

### PV 與 PVC 的 `accessModes` 不一致時，是否可綁定？
- ### gpt-4o (2025/05/31)

    這是一個非常細緻、但**實務上重要**的問題。

    ### 🔍 問題重述：
    若：

    ```
    PV.spec.accessModes: [ReadWriteMany]
    PVC.spec.accessModes: [ReadWriteOnce]
    ```

    是否能配對？

    ---

    ### ✅ 正確答案：
    > **可以配對，綁定會成功。**

    ---

    ### 🎯 原因說明：

    Kubernetes 在進行 PVC ↔ PV 配對時，檢查的條件是：

    > ✅ **PV 的 `accessModes` 必須包含 PVC 所要求的模式。**

    #### ✔️ 換句話說：

    | PV 支援的模式 | PVC 要求的模式 | 是否可以配對？ | 原因 |
    |---------------|----------------|----------------|------|
    | RWO, ROX, RWX | RWO            | ✅ 可以        | PV 有支援 RWO |
    | RWX           | RWO            | ✅ 可以        | RWX 含蓋 RWO |
    | ROX           | RWO            | ❌ 不行        | 沒有 RWO |
    | RWO           | RWX            | ❌ 不行        | PV 不支援 RWX |
    - X: Many
    - O: Once

    ---

    ### 📌 所以您的例子：

    ```
    PV.accessModes: [ReadWriteMany]
    PVC.accessModes: [ReadWriteOnce]
    ```

    - ✅ **成功**（因為 RWX 能涵蓋 RWO）
    - Pod 會以 **單寫入** 的方式掛載該 PV，即使它本身支援多寫入。

    ---

    ### 🧠 類比：

    這就像：
    - 飯店提供一個「可容納 5 人」的大房間（RWX）
    - 客人只要求「一個單人房」（RWO）
    - 雖然空間太大，但依然可以入住 ✅

    ---

    ### ❗ 注意事項：

    雖然可以綁定成功，但請留意：
    - 多個 PVC 都要求 RWO，仍然不能**同時掛上同一個 RWX PV**
    - 除非 `accessModes` 是 `ReadOnlyMany` 或 Pod 限制了 Volume 的掛載行為

    ---

    ### ✅ 範例驗證 YAML

    #### PV.yaml
    ```yaml=
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: test-pv
    spec:
      capacity:
        storage: 1Gi
      accessModes:
        - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain
      storageClassName: test
      hostPath:
        path: "/mnt/data/test"
    ```

    #### PVC.yaml
    ```yaml=
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: test-pvc
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 500Mi
      storageClassName: test
    ```

    結果：
    ```
    $ kubectl get pvc test-pvc
    NAME       STATUS   VOLUME     ...
    test-pvc   Bound    test-pv
    ```

    ✅ 綁定成功！

<br>

<hr>

<br>

### PV 與 PVC 的 `storageClassName` 不一致時，是否可綁定？
- ### gpt-4o (2025/05/31)

    ### ❓ 如果 `PV.spec.storageClassName` 和 `PVC.spec.storageClassName` 不一樣，是否仍可綁定？

    > ❌ **不能綁定，一定會失敗。**

    ---

    ### ✅ 結論：

    > **Kubernetes 在綁定 PVC → PV 時，`storageClassName` 必須完全相符**（字串比對，大小寫敏感）。

    ---

    ### 📌 對照表：是否能綁定

    | PV.storageClassName | PVC.storageClassName | 可綁定？ | 原因說明 |
    |---------------------|----------------------|----------|-----------|
    | `"fast"`            | `"fast"`             | ✅ 可以  | 相同字串 |
    | `"fast"`            | `"standard"`         | ❌ 不行 | 字串不同 |
    | `""`（空字串）       | 未設定（null）         | ✅ 可以  | 兩者皆為無設定，代表預設值 |
    | `"manual"`          | 未設定（null）         | ❌ 不行 | 一邊設定，一邊沒設定，視為不匹配 |

    ---

    ### 📎 額外補充：什麼情況可以不設 `storageClassName`？

    #### ✅ 若 PVC 沒有 `storageClassName`，系統會嘗試：
    1. 使用**預設 StorageClass**（即 `kubectl get storageclass` 中帶 `(default)` 的那個）
    2. 或綁定一個 **沒有設定 `storageClassName`** 的 `PersistentVolume`（即 legacy 手動 PV）

    這是舊版 Kubernetes 的行為保留。

    ---

    ### 🧪 測試案例（會失敗）

    #### PV:
    ```
    spec:
      storageClassName: manual
    ```

    #### PVC:
    ```
    spec:
      storageClassName: standard
    ```

    結果：
    ```
    PVC   → Pending
    ```

    原因：
    ```
    no persistent volumes available for this claim and no storage class is set
    (找不到可用來滿足此要求的 Persistent Volume，且未設定 StorageClass。)
    ```

    ---

    ### ✅ 正確做法

    #### 要嘛兩者一致：

    ```
    PV:  storageClassName: standard
    PVC: storageClassName: standard
    ```

    #### 要嘛兩者都不設（legacy 模式，K8s 會嘗試綁定）

    ---

    ### 🔍 實務建議

    | 情境 | 建議做法 |
    |------|----------|
    | 靜態配置 PV + PVC | PV 與 PVC 的 `storageClassName` 要一致，或都不寫 |
    | 動態配置 PV | 讓 PVC 指定 StorageClass 即可，系統會幫你建立 PV |
    | 多種 StorageClass 共存 | 每個 PVC 要明確指定對應的 StorageClass 名稱 |

<br>

<hr>

<br>

### PV & PVC 比對邏輯
- ### gpt-4o (2025/05/31)

    ### PV 與 PVC 的匹配（Binding）邏輯一覽表

    | PV 欄位              | PVC 欄位               | 是否比對 | 說明 |
    |----------------------|------------------------|----------|------|
    | `storageClassName`   | `storageClassName`     | ✅ 必要   | 必須完全相符（空白也要一致） |
    | `capacity.storage`   | `resources.requests.storage` | ✅ 必要 | PV 必須大於等於 PVC 請求 |
    | `accessModes`        | `accessModes`          | ✅ 必要   | PVC 所要求的模式必須是 PV 的子集（例如：PVC 要求 RWO，PV 提供 RWO+ROX 可以匹配） |
    | `volumeMode`         | `volumeMode`           | ✅ 必要（預設為 `Filesystem`） | Filesystem 或 Block，兩者需相符 |
    | `nodeAffinity`       | 無（由調度器處理）    | ⚠️ 條件式 | 僅在使用 `local` 類型 PV 時會強制要求，需與 Pod 安排在相同節點 |
    | `persistentVolumeReclaimPolicy` | 無             | ❌ 不影響匹配 | 僅影響 PV 被釋放後的行為 |
    | `volumeName`         | PVC.spec.volumeName    | ✅ 條件式 | 如果 PVC 指定了 `volumeName`，**只會綁定該 PV** |
    | PV.labels            | PVC.spec.selector.matchLabels | ✅ 條件式 | 若 PVC 有指定 selector，PV 必須匹配對應 label |
    | PV.annotations       | 無                     | ❌ 不參與匹配 | 只供人類與外部工具讀取 |
    | PV.status.phase      | 無                     | ✅ 隱性   | 只有 `Available` 的 PV 才會被考慮匹配 |

    ---

    ### 比對邏輯順序（簡化解釋）

    Kubernetes 在尋找可以與 PVC 綁定的 PV 時，大致會：

    1. 找出 `status.phase == Available` 的 PV
    2. 過濾出與 PVC 相同 `storageClassName` 的 PV
    3. 過濾掉容量不足的 PV（PV 必須 ≥ PVC 請求）
    4. 比對 accessModes，PVC 的模式必須是 PV 的子集
    5. 比對 volumeMode（通常都是 `Filesystem`）
    6. 若 PVC 有 `volumeName`，**只考慮指定的 PV**
    7. 若 PVC 有 `selector`，比對 PV 的 labels
    8. 綁定第一個符合條件的 PV（非排序式匹配）

    ---

    ### volumeBindingMode 差異也影響時機

    | volumeBindingMode | 行為說明 |
    |-------------------|----------|
    | `Immediate`       | 立即綁定，只要 PVC 發出就會立即比對與綁定 PV（**無論 Pod 是否已被排程**） |
    | `WaitForFirstConsumer` | 等到 Pod 真的要使用時才會綁定，並考慮 Pod 調度需求，例如 `nodeAffinity` |

    ---

    ### 常見匹配失敗情況

    | 原因 | 解釋 |
    |------|------|
    | `storageClassName` 不符 | 空字串 ≠ 未設定，請保持一致或顯式設定 |
    | `volumeMode` 不一致 | 例如 PV 是 `Block`，PVC 是 `Filesystem` |
    | `accessModes` 不包含 | PVC 想要 RWX，但 PV 僅有 RWO |
    | 容量不足 | PVC 要求 `10Gi`，但 PV 只有 `5Gi` |
    | PV 被其他 PVC 綁定了 | PV 的 `status.phase` 是 `Bound`，不能再綁 |

<br>

<hr>

<br>

### SC/PV/PVC 概念
- ### Question
    SC ->  HD / SSD / NFS / ... ，指定 handler (provisioner) 來幫你對應
    PV -> 管理者：我有 2TB ，看 user要如何使用
    PVC  -> 使用者：我想要 500G ，速度要快(SSD)
    這樣的說法對嗎？
- ### gpt-o4-mini (2025/06/12)
    你的說法在概念上是正確的，下面用 Kubernetes 的術語再稍微釐清一下三者的角色與關係：

    1. **StorageClass (SC)**

       * 定義「儲存類型」與「參數模板」，例如 `provisioner`（如 `kubernetes.io/no-provisioner`、`csi-nfs`、`csi-ssd`）以及各類參數（IOPS、快取模式、路徑、NFS server 等）。
       * 就像你列的 HD／SSD／NFS，不同 SC 就對應不同底層儲存技術與特性。

    2. **PersistentVolume (PV)**

       * 由集群管理者（或動態供應機制）實際建立好的一塊儲存資源，具有固定容量（如 2 TiB）、速度（IOPS）和透過哪個 SC 來 provision。
       * PV 的 `storageClassName` 要對應一個已存在的 SC。

    3. **PersistentVolumeClaim (PVC)**

       * 由使用者提出的「儲存需求」，指定容量（如 500 Gi）與希望的 SC（例如 `fast-ssd`），也可附加額外參數（如讀寫次數、存取模式 `ReadWriteOnce`）。
       * K8s 會自動尋找或動態建立（如果 SC 支援）一個符合條件的 PV，然後把它綁定（Bind）到這個 PVC。

    ---

    ## 示例

    ```yaml=
    # StorageClass：定義 SSD 類型
    kind: StorageClass
    apiVersion: storage.k8s.io/v1
    metadata:
      name: fast-ssd
    provisioner: csi-ssd.example.com
    parameters:
      type: io1
      iopsPerGB: "10"

    ---

    # 管理者預先建立的 PV（2 TiB）
    kind: PersistentVolume
    apiVersion: v1
    metadata:
      name: pv-data-2tb
    spec:
      storageClassName: fast-ssd
      capacity:
        storage: 2Ti
      accessModes:
        - ReadWriteOnce
      persistentVolumeReclaimPolicy: Retain
      csi:
        driver: csi-ssd.example.com
        volumeHandle: vol-12345

    ---

    # 使用者提出的 PVC（500 Gi，需要 fast-ssd）
    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: pvc-myapp
    spec:
      storageClassName: fast-ssd
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 500Gi
    ```

    * **SC**：`fast-ssd`（對應 SSD）
    * **PV**：`pv-data-2tb`（管理者已準備好 2 TiB SSD）
    * **PVC**：`pvc-myapp`（使用者要 500 Gi 且要走 `fast-ssd`）

    Kubernetes 會把這三者對應起來，確保使用者拿到符合預期容量與性能的儲存。

<br>

<hr>

<br>

### 實際上 PVC 怎麼接到對應的 SSD ? HD ?
- ### gpt-o4-mini (2025/06/12)
    在 Kubernetes 裡，PVC 要「對接」到底層的 SSD、HD，其實是由 StorageClass 和對應的 CSI（Container Storage Interface）或 in-tree provisioner 來完成的一整套流程，概念上可以拆成三步：

    1. **StorageClass → CSI 驅動程式下指令**

       * 當你建立一張需要 SSD 的 PVC 時（`storageClassName: fast-ssd`），Kubernetes 的控制平面會把這個請求交給對應的 CSI driver（或舊版的 in-tree provisioner），像是 `csi-ssd.example.com`。
       * 這個 CSI driver 其實是一個在集群中以 Pod 或 DaemonSet 形態運行的元件，它跟真實的儲存後端通訊（可能是雲端 API、SAN 陣列、或本地 LVM），下指令「幫我建立一個 500 Gi 的 SSD 卷」。

    2. **後端儲存系統建立並回報 VolumeHandle**

       * 假設你是在雲端（AWS/GCP/Azure），CSI driver 會呼叫雲商 API 建立一片 EBS/PD 磁碟；若是在 on-prem，可能是跟 iSCSI SAN、Ceph RBD、或 NetApp/SolidFire 等通訊；若是本地 SSD，可能是先用 LVM 做 PV pool，再開 LV。
       * 建好之後，後端會回傳一個唯一的 VolumeHandle（例如 AWS 的 `vol-0abcdef123456`，或 LVM 的 `/dev/vg0/lv500`），這個 ID 會記錄在對應的 PV 裡。

    3. **Kubelet 在 Node 上掛載並暴露給 Pod**

       * 當 PVC 與 PV 綁定完成後，調度器把 Pod 排到某個 Node。該 Node 上的 kubelet 會呼叫 CSI driver 的 NodeStage/NodePublish 接口，實際在該節點上：

         1. 透過 iSCSI、FC、NVMe-oF、或直接本地塊裝置（如 `/dev/vg0/lv500`）把卷 attach 到 Node。
         2. 在節點的檔案系統（例如 `/var/lib/kubelet/plugins/.../mounts/...`）做格式化（ext4、xfs 等）與 mount。
         3. 最後把這個掛載好的路徑 bind-mount 到 Pod 的容器裡，例如 `/mnt/data`。

    ---

    ### 舉例：AWS EBS 動態建立 SSD 磁碟

    1. **定義 StorageClass**

       ```yaml=
       apiVersion: storage.k8s.io/v1
       kind: StorageClass
       metadata:
         name: gp3-ssd
       provisioner: ebs.csi.aws.com
       parameters:
         type: gp3
         fsType: ext4
       ```

    2. **使用者提出 PVC**

       ```yaml=
       apiVersion: v1
       kind: PersistentVolumeClaim
       metadata:
         name: pvc-data
       spec:
         storageClassName: gp3-ssd
         accessModes: ["ReadWriteOnce"]
         resources:
           requests:
             storage: 500Gi
       ```

    3. **背後流程**

       * CSI driver 呼叫 AWS API：`CreateVolume({VolumeType: "gp3", Size: 500, ...})` → 回傳 `VolumeId: "vol-0123456789abcdef0"`
       * Kubernetes 自動建立對應的 PV，並綁定到你的 PVC。
       * Pod 啟動後，kubelet 透過 CSI 把 `vol-0123456789abcdef0` attach 到該 Node，格式化、mount，最後才注入容器。

    ---

    ### 本地 SSD/HD（Local PV）示例

    如果你想直接使用節點本機的 SSD 或 HD，也可以用 Local PersistentVolume：
    
    ```yaml=
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: local-ssd-pv
    spec:
      storageClassName: local-ssd
      capacity:
        storage: 1Ti
      accessModes:
        - ReadWriteOnce
      local:
        path: /mnt/local-ssd    # 這個路徑必須在 Node 上事先準備好
      nodeAffinity:
        required:
          nodeSelectorTerms:
            - matchExpressions:
                - key: kubernetes.io/hostname
                  operator: In
                  values: ["worker-0"]   # 確保這個 PV 只綁到特定 Node
      persistentVolumeReclaimPolicy: Retain
    ```

    * 這種方式不會走 CSI 動態 provision，管理員必須事先在每台 Node 上建立好目錄（或 LVM 卷）並設定好 StorageClass。
    * 當 PVC 用到 `storageClassName: local-ssd` 時，Kubernetes 會把 Pod 「釘」到該 Node，直接把 `/mnt/local-ssd` 綁定到容器裡。

    ---

    透過上述機制，使用者只要關心「我要一個 500 Gi SSD 卷」，而 Kubernetes 與 CSI driver 會自動把請求轉給真正的後端系統，完美將 PVC 與實體或雲端的 SSD/HD 連接起來。

<br>

<hr>

<br>

### provisioner 能客製化自己處理嗎？
- ### gpt-o4-mini (2025/06/12)
    可以。Kubernetes 允許你自行開發並部署「自訂 Provisioner」，來接管 PVC 到底層儲存的動態供應邏輯。一般做法有兩種：

    ---

    ### 一、實作 CSI Driver

    1. **撰寫 CSI Controller + Node 外掛程式（Plugin）**

       * Controller 端（通常包在一個 Deployment）實作 CreateVolume/DeleteVolume 等 RPC，跟你的儲存後端（SAN、Ceph、LVM、雲端 API⋯⋯）溝通。
       * Node 端（DaemonSet）實作 NodeStage/NodePublish 等 RPC，負責把實際的塊裝置 attach、format、mount 給 kubelet。

    2. **打包成容器．並在叢集裡部署**

       * 你可以用 [CSI Spec](https://kubernetes-csi.github.io/docs/spec.html) 提供的 Go 模板來快速起步。
       * 部署後，它會在叢集中註冊自己能處理的 `provisioner` 名稱（通常像 `csi.mycompany.com`）。

    3. **定義 StorageClass**

       ```yaml
       apiVersion: storage.k8s.io/v1
       kind: StorageClass
       metadata:
         name: my-custom-ssd
       provisioner: csi.mycompany.com         # 就是你 CSI driver 報給 K8s 的這個名字
       parameters:
         foo: bar                             # 可以自由定義傳給 Controller 的參數
       reclaimPolicy: Delete
       volumeBindingMode: WaitForFirstConsumer
       ```

    ---

    ### 二、用外部 Provisioner Sidecar（External Provisioner）

    如果後端的供應邏輯不需要 CSI 的複雜度，也可以用 Kubernetes 社群提供的 [external-provisioner](https://github.com/kubernetes-csi/external-provisioner) sidecar 架構：

    1. **實作一個監聽 PVC 事件的 Controller**

       * 以 Go/Python/任何語言撰寫一個程式，監聽 API Server 上的 PVC 新增事件（用 client-go Informer 或 Kubernetes API）。
       * 收到新 PVC（符合特定 StorageClass）就呼叫後端 API 創建卷，然後回寫一個對應的 PV→PVC Bind。

    2. **打包 Sidecar 容器＋RBAC**

       * 給它最高只讀 PVC、新增 PV、更新 PV 狀態的權限。
       * 把它部署成 Deployment，並指定它要處理的 `provisioner` 名稱。

    3. **StorageClass 範例**

       ```yaml=
       apiVersion: storage.k8s.io/v1
       kind: StorageClass
       metadata:
         name: external-custom
       provisioner: external.mycompany.com   # 你 Controller 程式自己定
       parameters:
         region: us-west-1
         tier: gold
       ```

    ---

    ### 小結

    * **CSI Driver**：業界推薦做法，支援多雲、多種 attach 類型，也能被 Kubernetes 原生管理。適合需要高可靠、跨雲後端、Volume 拖移等複雜場景。
    * **External Provisioner**：快速上手，用一般程式呼叫 Kubernetes API＋後端 API 即可，適合輕量或內部系統對接。

    不論哪種方式，最終你都能在 `StorageClass.provisioner` 填上你自己註冊的名字，Kubernetes 就會把對應的 PVC 交給你開發的程式來「處理動態建立 PV」的流程。

<br>

<hr>

<br>

### pvc data 會在什麼時候觸發清除？
- ### Question
    ```
    $ kubectl get pv -n tj-slurm4 | grep slurm
    pvc-aec3ace8-8b74-407f-a807-1ed82cc32d39   4Gi   RWO   Delete    Bound    tj-slurm/statesave-tj-slurm-controller-0    default-local   <unset>   4m44s
    pvc-c4c3cee6-58f6-47a6-8263-6c553c5bf166   8Gi   RWO   Delete    Bound    tj-slurm/data-tj-slurm-mariadb-0            default-local   <unset>   4m51s
    ```
    pvc data 會在什麼時候觸發清除？
- ### gpt-o4-mini-high (2025/06/19)
    在你這兩個 PV（`ReclaimPolicy=Delete`）的設定下，底層儲存（也就是「data」所對應的實體或目錄）只有在對應的 PVC 被刪除時，才會由 Storage Provisioner 自動清除。具體流程是：

    1. **刪除 PVC**
       當你執行

       ```bash
       kubectl delete pvc -n tj-slurm4 data-tj-slurm-mariadb-0
       ```

       這個 PVC 資源就會被刪除。

    2. **PV 狀態轉為 Released**
       Kubernetes 會先將對應的 PV 狀態從 `Bound` 變更為 `Released`（表示它的佔用關係已解除），同時因為 `ReclaimPolicy=Delete`，接著進入回收流程。

    3. **Provisioner 刪除底層資源**
       Dynamic Provisioner（這裡是 `default-local`）偵測到 PVC 被刪，會自動刪除底層儲存卷（對 Local PV 就是刪除當地目錄；對雲端卷就是呼叫雲端 API 刪除磁碟），清空上面的所有資料。

    4. **PV 資源也一併移除**
       最後，PV 物件本身也會被 Kubernetes 自動刪除。

    ---

    > **重點**：
    >
    > * 只刪除 Pod 或 Deployment 並不會清除底層資料，必須刪除 PVC。
    > * Namespace 刪除時會一併刪除裡面的 PVC，自動觸發相同流程。
    > * 若你想要保留資料，應將 `ReclaimPolicy` 設為 `Retain`，這樣刪除 PVC 後底層資料不會被移除。

<br>

<hr>

<br>

### RECLAIM POLICY 用途是？
- ### Question
    ```
    $ kubetl get pv
    NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                          STORAGECLASS    VOLUMEATTRIBUTESCLASS   REASON   AGE
    pvc-01a20414-1dc8-4636-9b29-c10f82a9317d   250Gi      RWO            Delete           Bound    dev/ollama-pvc                                 default-local   <unset>                          7d19h
    pvc-1bfd5aae-15ad-4514-a9b4-4ee3c7e8b59d   8Gi        RWO            Delete           Bound    slurm/data-slurm-mariadb-0                     default-local   <unset>                          19h
    pvc-584979c1-4171-49aa-bb11-6fdeaa156333   4Gi        RWO            Delete           Bound    slurm/statesave-slurm-controller-0             default-local   <unset>                          19h
    ...
    ```
    RECLAIM POLICY 用途是？
- ### gpt-5 (2025/08/09)
    簡單說：**RECLAIM POLICY（回收策略）決定當 PVC 被刪除後，這個 PV 與其底層儲存資源要怎麼處理**。常見值與行為：

    * **Retain（保留）**：PVC 刪掉後，PV 進入 *Released* 狀態但**不會刪資料也不會回到可再利用**。管理員需手動清理資料、移除 `claimRef` 讓 PV 回到 *Available*，或是用同一塊磁碟建立新的 PV。**適合資料庫等重要資料**，避免誤刪。
    * **Delete（刪除）**：PVC 刪掉後，**同時刪 PV 物件，並嘗試刪除底層儲存（由外掛/Provisioner 決定實際行為）**。像雲端磁碟（EBS、PD）會真的銷毀；本機/路徑類 Provisioner 可能只是清空目錄。**適合暫存或可重建的資料**。
    * **Recycle（回收）**：已廢棄（deprecated），過去是簡單清空後再提供使用，現在不要用。

    你目前看到的都是 **Delete**，代表「刪 PVC 通常就連同底層空間一起清掉」。若有 **MariaDB / 狀態資料（你列表裡如 `data-slurm-mariadb-0`、`statesave-*`）**，建議改成 **Retain**，降低風險。

    ---

    ### 常用操作

    **1) 僅改單一 PV 的策略**

    ```bash
    # 將某個 PV 改為 Retain（之後刪 PVC 就不會把資料一起刪）
    kubectl patch pv pvc-1bfd5aae-15ad-4514-a9b4-4ee3c7e8b59d \
      -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
    ```

    **2) 動態建立的 PV：從 StorageClass 根源調整（影響未來新建的 PV）**

    ```bash
    # 查看 StorageClass 的回收策略
    kubectl get sc default-local -o yaml | yq '.reclaimPolicy'

    # 將 StorageClass 的回收策略改為 Retain（新建 PV 才會套用）
    kubectl patch sc default-local -p '{"reclaimPolicy":"Retain"}'
    ```

    > 注意：不同 Provisioner 的 **Delete** 行為不盡相同（雲端磁碟多半會銷毀；本機路徑類型可能只是清資料夾）。調整前先確認你叢集的 Provisioner 實作。

    **3) Retain 之後如何讓 PV 再被使用？**

    ```bash
    # 刪除 PVC 後，PV 會是 Released。
    # 管理員清理底層資料 → 移除 PV 的 claimRef 讓它回到 Available：
    kubectl patch pv <PV_NAME> --type=json \
      -p='[{"op":"remove","path":"/spec/claimRef"}]'
    # 之後新 PVC（條件相符）即可再次綁定該 PV
    ```

    ---

    ### 快速選擇建議

    * **重要資料（DB、長期狀態）** → 用 **Retain**
    * **可重建或暫存資料（快取、臨時工作目錄）** → **Delete** 無妨

<br>

{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}