# 🔍 Troubleshoot PVC Pending và lỗi mount volume

PVC Pending là dạng bài gần như chắc chắn xuất hiện trong đề CKA, và nó cũng là dạng bài mà nhiều bạn mất điểm oan vì... đi sửa lung tung trước khi đọc event. Bài này luyện đúng một kỹ năng: đọc event để tìm nguyên nhân, rồi sửa đúng chỗ.

Thời lượng gợi ý: 25 phút. Trong đó 15 phút bạn ngồi lab thật, đừng chỉ đọc.

## Mục tiêu bài học

Sau bài này, bạn phải làm được:

- Phân biệt pod ở `Pending` và pod ở `ContainerCreating`, vì hai trạng thái này dẫn tới hai nhánh troubleshoot khác nhau.
- Đọc `kubectl describe pvc` và `kubectl describe pod` để lấy đúng dòng event quyết định.
- Nhận diện 6 nguyên nhân PVC Pending phổ biến nhất và cách sửa từng loại.
- Nhận diện lỗi mount volume: Multi-Attach, thiếu Secret/ConfigMap, provisioner chết, CSI/node plugin lỗi.
- Biết khi nào PVC Pending **không phải** là lỗi.

## Ôn nhanh bài trước

Bài trước bạn đã học StorageClass và dynamic provisioning: PVC chỉ cần khai báo `storageClassName`, provisioner sẽ tự tạo PV. StorageClass có nhãn `(default)` sẽ được dùng khi PVC không ghi `storageClassName`, và `volumeBindingMode: WaitForFirstConsumer` làm PVC chờ có pod tiêu thụ rồi mới bind.

Hai chi tiết đó chính là nguồn gốc của phần lớn lỗi trong bài này.

## Ví dụ có hướng dẫn

### Ví dụ 1: PVC Pending vì gõ sai tên StorageClass

Bước 1, xác nhận trạng thái:

```bash
kubectl get pvc -n demo
```

```
NAME       STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-app   Pending                                      fast-ssd       45s
```

Bước 2, đọc event. Đây là bước quyết định, và event luôn nằm ở **cuối** output:

```bash
kubectl describe pvc data-app -n demo
```

```
Events:
  Type     Reason             Age   From                         Message
  ----     ------             ----  ----                         -------
  Warning  ProvisioningFailed 10s   persistentvolume-controller  storageclass.storage.k8s.io "fast-ssd" not found
```

Bước 3, đối chiếu với danh sách StorageClass có thật:

```bash
kubectl get sc
```

```
NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
fast (default)       rancher.io/local-path   Delete          WaitForFirstConsumer   false                  12d
```

Không có class nào tên `fast-ssd`. Nguyên nhân đã rõ: gõ sai tên.

Bước 4, sửa. `storageClassName` là field gần như bất biến, bạn **không** patch được, phải xóa và tạo lại PVC:

```bash
kubectl delete pvc data-app -n demo
kubectl apply -f pvc-data-app.yaml
kubectl get pvc -n demo -w
```

Kết quả mong đợi: `data-app   Bound   pvc-...   1Gi   RWO   fast   5s`.

> [!NOTE]
> Nếu PVC đang bị pod sử dụng, lệnh delete sẽ treo ở `Terminating` vì finalizer `kubernetes.io/pvc-protection`. Phải xóa pod trước, hoặc đợi pod dừng hẳn.

### Ví dụ 2: Pod kẹt ContainerCreating vì Multi-Attach

Bước 1, xem pod đang ở đâu:

```bash
kubectl get pod -n demo -o wide
```

```
NAME         READY   STATUS              RESTARTS   AGE   NODE
web-7c9f-1   0/1     ContainerCreating   0          2m    node01
web-7c9f-2   0/1     ContainerCreating   0          2m    node02
```

Bước 2, đọc event của pod:

```bash
kubectl describe pod web-7c9f-2 -n demo | tail -n 15
```

```
Events:
  Type     Reason              Age   From                     Message
  ----     ------              ----  ----                     -------
  Warning  FailedAttachVolume  1m    attachdetach-controller  Multi-Attach error for volume "pvc-3f2a9c" Volume is already exclusively attached to one node and can't be attached to another
```

Bước 3, hiểu nguyên nhân: PVC là `ReadWriteOnce`, tức là chỉ mount được trên **một node** tại một thời điểm. Deployment có 2 replicas và scheduler đặt 2 pod lên 2 node khác nhau, nên pod thứ hai không attach được volume.

Bước 4, sửa bằng một trong các cách sau, chọn cách nhanh nhất trong phòng thi:

```bash
# Cách 1: chỉ chạy 1 replica
kubectl scale deploy web -n demo --replicas=1

# Cách 2: ép mọi pod về cùng một node
kubectl patch deploy web -n demo --type=merge -p \
  '{"spec":{"template":{"spec":{"affinity":{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/hostname","operator":"In","values":["node01"]}]}]}}}}}}}'
```

Sau khi sửa, xóa pod đang kẹt để nó được tạo lại, rồi theo dõi bằng `kubectl get pod -w`.

## Thực hành có hướng dẫn

Chuẩn bị: một cluster lab có StorageClass mặc định (local-path hoặc tương tự), namespace `demo`.

```bash
kubectl create ns demo
```

### Bài 1: PV không khớp capacity (5 phút)

Tạo PV và PVC lệch nhau về dung lượng:

```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-manual
spec:
  capacity:
    storage: 1Gi
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /mnt/data-manual
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-big
  namespace: demo
spec:
  storageClassName: manual
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 2Gi
```

Việc bạn làm:

1. Apply file trên.
2. Chạy `kubectl get pv,pvc -n demo`. Ghi lại trạng thái của từng object.
3. Chạy `kubectl describe pvc pvc-big -n demo` và **chép nguyên văn** dòng `Message` trong Events.
4. Sửa lỗi bằng cách xóa PVC và tạo lại với `storage: 1Gi`, rồi kiểm tra lại.

Kết quả mong đợi: PVC `Pending`, PV ở trạng thái `Available` (chưa bind). Event báo không có PV phù hợp hoặc không tìm thấy StorageClass `manual` (vì `manual` chỉ là tên class ghi trong PV, không phải object StorageClass có thật).

Câu hỏi tự kiểm tra: vì sao PV `Available` mà PVC vẫn `Pending`?

### Bài 2: PVC Pending do gõ sai storageClassName (3 phút)

1. Tạo PVC với `storageClassName: standar` (thiếu chữ `d`).
2. Chạy `kubectl describe pvc` và đọc event.
3. Xóa, tạo lại với `storageClassName: standard` (hoặc tên class mặc định trong cluster của bạn).

Kết quả mong đợi: event `storageclass.storage.k8s.io "standar" not found`, sau khi sửa thì PVC `Bound`.

### Bài 3: PVC Pending nhưng không phải lỗi (3 phút)

1. Tạo PVC dùng StorageClass có `volumeBindingMode: WaitForFirstConsumer`.
2. `kubectl get pvc`: PVC `Pending`. Đừng hoảng.
3. Đọc event, bạn sẽ thấy `waiting for first consumer to be created before binding`.
4. Tạo một pod mount PVC đó, rồi xem lại PVC: nó chuyển sang `Bound` ngay sau khi pod được schedule.

Đây là điểm rất dễ mất điểm trong đề thi: thí sinh thấy Pending liền đi sửa PVC, trong khi hành vi đúng là tạo pod tiêu thụ.

### Bài 4: Pod ContainerCreating vì thiếu Secret (4 phút)

Tạo pod mount một Secret không tồn tại:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-secret-missing
  namespace: demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
    volumeMounts:
    - name: cfg
      mountPath: /etc/cfg
  volumes:
  - name: cfg
    secret:
      secretName: app-config
```

Các bước:

1. Apply, rồi `kubectl get pod -n demo`. Pod sẽ ở `ContainerCreating`.
2. `kubectl describe pod pod-secret-missing -n demo | tail -n 20`, tìm dòng `MountVolume.SetUp failed` hoặc `secret "app-config" not found`.
3. Sửa bằng `kubectl create secret generic app-config -n demo --from-literal=key=value`.
4. Đợi khoảng 30 đến 60 giây, kubelet sẽ tự thử lại và pod chuyển `Running`. Không cần xóa pod.

### Bài 5: Bài tổng hợp, không có gợi ý (5 phút)

Giảng viên hoặc đồng đội tạo ngẫu nhiên **một** trong các tình huống sau, bạn không được xem YAML:

- PVC trỏ tới StorageClass không tồn tại.
- PVC xin `ReadWriteMany` nhưng PV chỉ `ReadWriteOnce`.
- Pod mount PVC mà PVC chưa Bound.
- Provisioner pod trong `kube-system` bị scale về 0.

Nhiệm vụ: trong 5 phút, xác định nguyên nhân và khắc phục. Chỉ dùng 4 lệnh: `get`, `describe`, `get sc`, `get events`.

Gợi ý quy trình chuẩn:

```bash
kubectl get pod -n demo
kubectl describe pod <pod> -n demo | tail -n 20
kubectl get pvc -n demo
kubectl describe pvc <pvc> -n demo | tail -n 15
kubectl get sc
kubectl get events -n demo --sort-by=.lastTimestamp | tail -n 15
```

## Bảng tra nhanh event

| Thông báo trong event | Xuất hiện ở | Nguyên nhân thường gặp | Cách sửa |
| --- | --- | --- | --- |
| `storageclass.storage.k8s.io "x" not found` | PVC | Sai tên StorageClass, hoặc SC chưa tạo | `kubectl get sc`, sửa và tạo lại PVC |
| `no persistent volumes available for this claim and no storage class is set` | PVC | PVC không có `storageClassName`, không có PV khớp | Tạo PV khớp, hoặc thêm `storageClassName` |
| `waiting for first consumer to be created before binding` | PVC | SC dùng `WaitForFirstConsumer` | Không phải lỗi, tạo pod tiêu thụ |
| `failed to provision volume with StorageClass "x"` | PVC | Provisioner/CSI controller lỗi hoặc bị scale về 0 | Xem log pod provisioner trong `kube-system` |
| `pod has unbound immediate PersistentVolumeClaims` | Pod | PVC chưa Bound | Xử lý PVC trước, pod tự chạy lại |
| `Multi-Attach error for volume "pvc-..."` | Pod | Volume RWO đang gắn ở node khác | Scale về 1 replica hoặc gom pod về cùng node |
| `MountVolume.SetUp failed ... secret "x" not found` | Pod | Secret/ConfigMap chưa tồn tại | Tạo Secret/ConfigMap, chờ kubelet retry |
| `Unable to attach or mount volumes ... timed out waiting for the condition` | Pod | CSI node plugin lỗi, node NotReady, hết đĩa | Kiểm tra CSI pod và kubelet trên node |

> [!TIP]
> Câu thần chú của bài này: ==luôn đọc Events ở cuối output của kubectl describe==. 90% thời gian bạn sẽ thấy nguyên nhân nằm ở đó, không cần suy đoán.

## Lỗi thường gặp và cách xử lý

- **Nhầm `Pending` với `ContainerCreating`.** Pod `Pending` thường là chưa schedule được hoặc PVC chưa Bound. Pod `ContainerCreating` là đã có node, đang kéo image và mount volume. Đọc sai trạng thái là đi sai cả nhánh.
- **Đi sửa pod khi lỗi gốc nằm ở PVC.** Pod chỉ là nạn nhân. Nếu event của pod ghi `unbound immediate PersistentVolumeClaims`, mọi thao tác trên pod đều vô nghĩa.
- **Cố patch `storageClassName` của PVC đang tồn tại.** Không được. Phải xóa và tạo lại.
- **Xóa PVC khi pod còn chạy.** PVC sẽ treo ở `Terminating` vì finalizer bảo vệ. Xóa pod trước.
- **Quên PV ở trạng thái `Released`.** Với `reclaimPolicy: Retain`, sau khi PVC bị xóa, PV chuyển `Released` và vẫn giữ `claimRef` cũ, nên không bind cho PVC mới. Phải xóa `claimRef` hoặc tạo lại PV.
- **Quên namespace.** `kubectl describe pvc` không có `-n` sẽ ra `NotFound` và bạn tưởng PVC chưa được tạo.
- **Event đã hết hạn.** Event mặc định chỉ giữ 1 giờ. Nếu cụm lab đã chạy lâu, hãy xóa pod (không phải PVC) để tạo lại và sinh event mới.
- **Xóa PV có `Retain` để "làm lại cho nhanh".** Trong đề thi, thao tác này có thể làm mất dữ liệu của bài khác. Đọc kỹ reclaim policy trước khi xóa.

Tự kiểm tra nhanh bằng cloze:

```cloze
PVC chỉ bind được với PV khi khớp cả ba yếu tố: capacity, {{c1::accessModes}} và storageClassName.
Với volumeBindingMode: {{c2::WaitForFirstConsumer}}, PVC sẽ ở trạng thái Pending cho tới khi có pod sử dụng.
PV ở trạng thái {{c3::Released}} vẫn giữ claimRef cũ nên không bind cho PVC mới.
Lỗi {{c4::Multi-Attach}} xảy ra khi volume ReadWriteOnce đang được gắn ở một node khác.
Event "MountVolume.SetUp failed" thường do thiếu {{c5::Secret}} hoặc ConfigMap được mount vào pod.
Pod Pending với event "pod has unbound immediate PersistentVolumeClaims" nghĩa là {{c6::PVC chưa Bound}}.
```

## Góc chuyên gia

Vài thói quen mình rút ra sau khi thi và sau nhiều lần làm hỏng cluster lab:

1. **Ba lệnh đầu tiên, theo đúng thứ tự:** `kubectl get pod`, `kubectl describe pvc`, `kubectl get sc`. Đừng sáng tạo thêm trước khi chạy đủ ba lệnh này.
2. **Đọc từ dưới lên.** Trong `kubectl describe`, event mới nhất ở cuối. Đọc từ dưới lên gặp nguyên nhân trước, tiết kiệm thời gian.
3. **Phân biệt "lỗi cấu hình" và "lỗi hạ tầng".** Sai tên class, sai access mode, sai capacity là lỗi cấu hình, sửa trong 1 phút. Provisioner chết, CSI pod lỗi, node NotReady là lỗi hạ tầng, thường tốn thời gian hơn. Nếu rơi vào nhóm thứ hai trong phòng thi, đánh dấu và đi tiếp, quay lại sau.
4. **Quy tắc 5 phút.** Một câu storage không đáng để bạn mất 15 phút. CKA có khoảng 15 đến 20 câu trong 2 giờ, trung bình 6 đến 7 phút mỗi câu.
5. **Kiểm tra bằng hành vi, không bằng cảm giác.** Sau khi sửa, phải thấy `Bound` và pod `Running` với `kubectl get ... -w`, đừng chỉ tin vào việc apply thành công.
6. **Tạo PVC nhanh bằng heredoc**, vì `kubectl create` không có generator cho PVC:

```bash
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
  namespace: demo
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi
EOF
```

7. **Ghi nhớ cặp đôi kinh điển:** PVC Pending thì nhìn StorageClass và PV; Pod ContainerCreating thì nhìn event mount và node.

## Tóm tắt và bước tiếp theo

Chốt lại trong bốn dòng:

- PVC Pending: đọc event của PVC, kiểm tra ba thứ capacity, accessModes, storageClassName.
- `WaitForFirstConsumer` làm PVC Pending có chủ đích, không phải lỗi.
- Pod `ContainerCreating`: đọc event của pod, tìm `FailedMount`, `Multi-Attach`, `MountVolume.SetUp failed`.
- Sửa PVC thì thường phải xóa và tạo lại; sửa pod thì chỉ cần xóa pod.

Bộ thẻ ôn tập cho bài này, bạn nên chạy lại sau 1 ngày và sau 3 ngày:

```flashcard
[
  { "front": "PVC ở trạng thái Pending, lệnh đầu tiên nên chạy là gì?", "back": "kubectl describe pvc <tên>, đọc phần Events ở cuối output." },
  { "front": "Event 'storageclass.storage.k8s.io \"fast-ssd\" not found' nghĩa là gì?", "back": "storageClassName trong PVC không tồn tại, phải sửa tên class hoặc tạo StorageClass đó." },
  { "front": "PVC Pending với event 'waiting for first consumer to be created before binding' có phải lỗi không?", "back": "Không. StorageClass dùng WaitForFirstConsumer nên PVC chỉ bind khi có pod tiêu thụ." },
  { "front": "PV ở trạng thái Released có bind được cho PVC mới không?", "back": "Không. PV còn giữ claimRef cũ, phải xóa và tạo lại PV hoặc gỡ claimRef." },
  { "front": "Pod Pending với event 'pod has unbound immediate PersistentVolumeClaims' nghĩa là gì?", "back": "PVC mà pod dùng chưa Bound, phải xử lý PVC trước khi sửa pod." },
  { "front": "Lỗi 'Multi-Attach error for volume' xảy ra khi nào?", "back": "Khi volume ReadWriteOnce đang gắn ở một node khác, pod mới không attach được." },
  { "front": "Thiếu Secret được mount vào pod thì event của pod nói gì?", "back": "MountVolume.SetUp failed, kèm thông báo secret không tìm thấy." },
  { "front": "Làm sao biết StorageClass nào là mặc định?", "back": "kubectl get sc, tên class mặc định có hậu tố (default)." },
  { "front": "PVC đã tạo có sửa được storageClassName không?", "back": "Không, spec gần như bất biến, phải xóa và tạo lại PVC." },
  { "front": "Lệnh xem event của riêng một PVC?", "back": "kubectl get events --field-selector involvedObject.name=<pvc> --sort-by=.lastTimestamp" },
  { "front": "PV 1Gi nhưng PVC xin 2Gi thì chuyện gì xảy ra?", "back": "PVC Pending vì không có PV khớp về capacity." },
  { "front": "PVC xin ReadWriteMany nhưng PV chỉ hỗ trợ ReadWriteOnce?", "back": "Không bind được, PVC ở trạng thái Pending." },
  { "front": "Provisioner pod trong kube-system bị chết thì PVC ra sao?", "back": "PVC Pending với event failed to provision volume with StorageClass." },
  { "front": "Vì sao xóa PVC khi pod còn dùng lại bị treo ở Terminating?", "back": "Finalizer kubernetes.io/pvc-protection giữ PVC cho tới khi không còn pod sử dụng." },
  { "front": "Khác nhau cốt lõi giữa pod Pending và pod ContainerCreating?", "back": "Pending là chưa schedule hoặc PVC chưa Bound, ContainerCreating là đã có node và đang mount volume." }
]
```

**Bài tiếp theo:** Checkpoint: Storage. Đó là bài kiểm tra tổng hợp cả module 5, gồm tạo volume từ ConfigMap/Secret, PV/PVC binding, StorageClass và đúng dạng troubleshoot bạn vừa luyện. Trước khi sang đó, hãy chắc rằng bạn làm được Bài 5 mà không cần gợi ý.
