Kubernetes PV가 Bound인데도 안심하면 안 되는 이유
환경: RKE2, Local PersistentVolume, LVM, Ghost, MySQL
“edge 노드의 PV가 죽었는지 어떻게 확인하지?”라는 질문에서 점검을 시작했다. 결론부터 말하면 Kubernetes에서 PV가 Bound라고 표시되는 것만으로 실제 디스크와 파일시스템이 정상이라는 뜻은 아니다.
이번 사례는 확정된 디스크 장애를 복구한 기록이라기보다, Local PV를 사용하는 운영 환경에서 제어면 상태, 노드 마운트, 커널 I/O, 애플리케이션 읽기·쓰기를 어떻게 분리해 검사하는지 정리한 예방형 트러블슈팅이다.
Local PV의 구조
Ghost/MySQL Pod
→ PVC
→ Local PV (nodeAffinity: on-prem worker)
→ /srv/.../volume
→ Linux mount point
→ LVM logical volume
→ virtual/physical disk
Local PV는 경로가 특정 노드에 묶인다. 다른 노드에 빈 디렉터리를 같은 이름으로 만들었다고 동일한 데이터가 생기지 않는다. 그래서 PV의 nodeAffinity와 Pod scheduling이 매우 중요하다.
또한 이 구조는 고가용성 공유 스토리지가 아니다. 노드나 디스크 장애에 대비하려면 별도의 원격 백업과 복구 절차가 필요하다.
Bound가 보장하는 것과 보장하지 않는 것
Bound는 PVC와 PV 객체가 Kubernetes 제어면에서 연결됐다는 뜻이다. 다음을 직접 보장하지는 않는다.
- 실제 블록 장치가 정상인지
- LVM LV가 활성화되어 있는지
- 파일시스템이 올바른 경로에 마운트됐는지
- 파일시스템이 read-only로 전환되지 않았는지
- 커널에 I/O error가 없는지
- 애플리케이션이 데이터를 정상적으로 읽고 쓰는지
특히 마운트가 풀렸는데 mount point 디렉터리만 남아 있으면 위험하다. kubelet이나 애플리케이션이 그 빈 디렉터리를 사용하면서 원래 데이터 디스크가 아니라 root filesystem에 새 데이터를 쓸 수 있기 때문이다.
1단계: Kubernetes 제어면
kubectl get node -o wide
kubectl get pv
kubectl -A get pvc
kubectl -A get pod -o wide
kubectl get events -A --sort-by=.lastTimestamp | tail -n 100
확인할 항목은 다음과 같다.
- 저장소 노드가
Ready인가? - PVC/PV가
Bound인가? - Pod가 기대한 노드에 배치됐는가?
FailedMount,FailedAttachVolume,I/O error이벤트가 있는가?- Pod가 CrashLoopBackOff 또는 장시간 Terminating 상태인가?
PV의 nodeAffinity와 실제 경로도 확인한다.
kubectl get pv <pv-name> -o yaml
kubectl describe pv <pv-name>
kubectl describe pod -n <namespace> <pod-name>
2단계: 노드의 실제 마운트
저장소 노드에 접속해 ls보다 먼저 findmnt를 사용한다.
findmnt -T /srv/kubernetes-pv/ghost-content/volume
findmnt -T /srv/kubernetes-pv/ghost-db/volume
mountpoint /srv/kubernetes-pv/ghost-content
mountpoint /srv/kubernetes-pv/ghost-db
df -hT /srv/kubernetes-pv/ghost-content /srv/kubernetes-pv/ghost-db
findmnt -T는 해당 경로를 실제로 어느 filesystem이 담당하는지 보여 준다. 예상한 LV가 아니라 /가 나온다면 마운트가 빠진 상태일 수 있다.
다음으로 블록 장치와 LVM 연결을 본다.
lsblk -o NAME,TYPE,FSTYPE,SIZE,MOUNTPOINTS,UUID
sudo pvs
sudo vgs
sudo lvs -a -o +devices
df의 사용률 100%, LV 비활성, 사라진 device, 예상과 다른 UUID를 확인한다.
3단계: read-only와 I/O 오류
findmnt -no SOURCE,FSTYPE,OPTIONS \
/srv/kubernetes-pv/ghost-content
sudo dmesg -T | grep -Ei \
'I/O error|EXT4-fs error|XFS.*error|Buffer I/O|read-only|nvme|blk_update'
sudo journalctl -k --since '24 hours ago' | grep -Ei \
'I/O error|filesystem|read-only|nvme|scsi|blk'
mount option에 ro가 보이거나 커널에 filesystem error가 반복되면 애플리케이션 쓰기를 중단하고 증거를 보존해야 한다.
가상 머신에 물리 디스크의 SMART 정보가 전달되지 않을 수 있다. 이때 smartctl이 정상이라고 나오지 않는다고 해서 디스크가 고장났다고 단정할 수 없고, 하이퍼바이저 호스트에서 물리 장치를 검사해야 한다.
4단계: 애플리케이션 관점
파일시스템이 보인다고 Ghost와 MySQL이 정상인 것은 아니다.
# Ghost content PVC가 예상 파일을 보이는지
kubectl -n <namespace> exec <ghost-pod> -- \
sh -c 'test -d /var/lib/ghost/content && find /var/lib/ghost/content -maxdepth 1 -type d'
# MySQL 컨테이너의 자체 health 명령 예시
kubectl -n <namespace> exec <mysql-pod> -- \
mysqladmin ping -h 127.0.0.1
쓰기 시험은 임의 파일을 운영 데이터 디렉터리에 만들지 말고 애플리케이션이 허용하는 health check나 별도 검증 경로를 사용한다. 데이터베이스는 논리 쿼리와 오류 로그도 함께 확인한다.
kubectl -n <namespace> logs <mysql-pod> --tail=200
kubectl -n <namespace> logs <ghost-pod> --tail=200
절대 급하게 하지 말아야 할 것
- 마운트된 운영 파일시스템에
fsck실행 - 원인 확인 전에 Pod와 노드를 반복 재부팅
- mount point 디렉터리를 삭제하거나 새로 생성
- PV를 삭제했다가 다시 만들기
- 장애 디스크에 대량 쓰기 시험
- 복구가 검증되지 않은 백업만 믿고 포맷
fsck는 일반적으로 파일시스템을 unmount한 뒤 maintenance window에서 수행해야 한다. 데이터베이스 Pod 중지, 백업 확보, 장치 경로 재확인 없이 실행하면 피해를 키울 수 있다.
백업으로 마무리하는 검증
Local PV는 노드 종속성이 있으므로 원격 restic 저장소를 별도로 운영했다. 검증은 세 단계로 했다.
restic snapshots
restic check
restic restore <snapshot-id> --target <isolated-restore-dir>
복구 위치에서 다음을 확인한다.
- 데이터베이스 dump 파일이 존재하고 읽을 수 있는가?
- 콘텐츠 파일 수와 주요 디렉터리가 예상과 같은가?
- 실제 애플리케이션이 복구 데이터를 사용할 수 있는가?
- 원본 노드에 접근하지 않아도 복구 가능한가?
빠른 판단표
| 관찰 | 해석 |
|---|---|
PV Bound, findmnt가 root filesystem 표시 |
제어면 연결은 됐지만 실제 데이터 mount 유실 가능 |
Pod Pending, nodeAffinity 불일치 |
저장소 노드 선택 문제 |
FailedMount 이벤트 |
kubelet/경로/권한/mount 문제 |
filesystem option ro |
오류로 read-only 전환됐을 가능성 |
| 커널 I/O 오류 반복 | 하위 디스크·가상 디스크·filesystem 조사 필요 |
| 마운트 정상, MySQL ping 실패 | 애플리케이션 또는 DB 계층 문제 |
재발 방지
- mount point마다
findmnt기반 모니터링을 둔다. - root filesystem과 데이터 filesystem 사용률을 따로 알린다.
- Kubernetes event와 커널 I/O 로그를 함께 수집한다.
- Local PV의 nodeAffinity와 노드 label 변경을 관리한다.
- 원격 백업과 정기 restore drill을 수행한다.
- 장애 절차에 “쓰기 중지 → 증거 수집 → 백업 확인 → 복구” 순서를 명시한다.