pv pvc storageclass 에 관해서
Kubernetes 스토리지 가이드: PV / PVC / StorageClass 완전 정복
버전 무관 개념 중심 정리 (CSI 기반 일반 원칙). 집/온프레미스 K8s에도 바로 적용 가능.
목차
- 빠른 요약 (TL;DR)
- 핵심 개념
- 각 오브젝트 심화
- 프로비저닝 방식
- 접근 모드 / 볼륨 모드
- 예제로 배우는 구성 패턴
- 리사이즈/스냅샷/백업
- 보안/권한/퍼미션
- 성능/운영 팁
- 트러블슈팅 체크리스트
- 자주 하는 실수
- 명령어 치트시트
- 부록: 템플릿 모음
빠른 요약 (TL;DR)
- PV: 클러스터에 실제 존재하는 저장소 리소스 (디스크 자체).
- PVC: 개발자/워크로드가 요구하는 스토리지 요청서 (용량/접근모드/클래스).
- StorageClass: PVC를 만들면 PV를 자동으로 생성하는 규칙서(드라이버 설정).
- 관계:
Pod → PVC → PV → (Storage Backend)
- 온프레미스/홈랩: NFS 또는 Local Path Provisioner, 규모가 커지면 Longhorn/Rook-Ceph 등 고려.
[Storage Backend] ←(CSI/프로비저너)— [StorageClass 규칙]
↑ ↑
└────────────── 바인딩 ───────────┘
[PVC 요청서] ← Pod가 참조
│
▼
[PV 실물]
핵심 개념
- Static Provisioning: 관리자가 먼저 PV를 생성 → 사용자는 PVC로 그 PV를 바인딩.
- Dynamic Provisioning: 사용자가 PVC만 만들면, StorageClass 규칙에 따라 PV가 자동 생성되어 바인딩.
- 접근 모드(accessModes):
ReadWriteOnce(RWO)
,ReadOnlyMany(ROX)
,ReadWriteMany(RWX)
등.- 다중 노드에서 동시에 쓰기하려면 보통 RWX가 필요(NFS/분산 스토리지).
- 볼륨 모드(volumeMode):
Filesystem
(기본) /Block
(원시 블록 디바이스). - 보존 정책(reclaimPolicy):
Delete
/Retain
/ (드라이버에 따라Recycle
은 보통 미사용).- 중요 데이터는 Retain으로 두고 수동 정리 권장.
- 바인딩 시점(volumeBindingMode):
Immediate
vsWaitForFirstConsumer
.- 후자는 Pod 스케줄링 시점에 노드/가용영역을 고려해 바인딩(클라우드/토폴로지에 유리).
- 확장(allowVolumeExpansion): PVC 사이즈 증가 허용 여부.
각 오브젝트 심화
PV (PersistentVolume)
- 무엇: “실제 디스크”를 나타내는 K8s 오브젝트.
- 주요 필드
spec.capacity.storage
: 용량(예:10Gi
)spec.accessModes
: RWO/ROX/RWXspec.volumeMode
: Filesystem/Blockspec.persistentVolumeReclaimPolicy
: Delete/Retain- 백엔드 설정:
nfs
,hostPath
,iscsi
,csi:
등 nodeAffinity
: 특정 노드에만 붙도록 제한 가능(로컬 디스크 시 유용).
- 수명: Pod와 독립. Pod가 죽어도 데이터 유지.
PVC (PersistentVolumeClaim)
- 무엇: “스토리지 요청서”(용량/접근 모드/클래스).
- 주요 필드
spec.resources.requests.storage
spec.accessModes
spec.storageClassName
spec.volumeMode
- (정적 바인딩 시)
spec.selector.matchLabels
로 특정 PV만 선택 가능.
- 상태:
Pending → Bound
로 바인딩되면 사용 가능.
StorageClass
- 무엇: “PV를 생성하는 규칙/드라이버 설정”
- 주요 필드
provisioner
: 예)kubernetes.io/no-provisioner
(정적),rancher.io/local-path
,nfs.csi.k8s.io
,ebs.csi.aws.com
등parameters
: 드라이버별 설정 (디스크 타입, fsType 등)reclaimPolicy
: Delete/RetainallowVolumeExpansion
: true/falsevolumeBindingMode
: Immediate / WaitForFirstConsumermountOptions
:noatime
등 마운트 옵션allowedTopologies
: 영역/존 한정
프로비저닝 방식
1) Static Provisioning
- 관리자가 PV를 미리 정의(NFS 경로/로컬 디스크 등).
- 사용자가 PVC를 만들면 조건 일치하는 PV와 바인딩.
- 주로 온프레미스에서 기존 NAS/NFS 공유를 잡아줄 때 단순/명확.
2) Dynamic Provisioning
- 관리자가 StorageClass와 **프로비저너(또는 CSI 드라이버)**를 배포.
- 사용자가 PVC 생성 시
storageClassName
을 명시. - 컨트롤러가 조건에 맞는 PV를 자동 생성하고 바인딩.
- 클라우드/CSI 환경, 또는 nfs-subdir-external-provisioner, local-path-provisioner 등에서 편리.
접근 모드 / 볼륨 모드
- accessModes
- RWO: 한 노드의 한 Pod(여러 Pod 가능하나 같은 노드 내)에서 읽기/쓰기.
- ROX: 여러 노드에서 읽기 전용 공유.
- RWX: 여러 노드/Pod에서 읽기/쓰기 공유(NFS/분산스토리지 필요).
- (클러스터/드라이버에 따라) ReadWriteOncePod 모드가 제공되기도 함.
- volumeMode
- Filesystem: 일반 마운트(디렉토리로 사용). 대부분의 워크로드 기본.
- Block: 파일시스템 없이 블록 디바이스로 노출(DB 특수 케이스).
예제로 배우는 구성 패턴
아래 예제의 네임스페이스는 기본(
default
) 가정. 필요 시metadata.namespace
추가.
예제 A: NFS 정적 프로비저닝
전제
- NFS 서버:
192.168.0.10
- 내보낸(export) 경로:
/export/data01
- 클라이언트에서 접근 가능(방화벽/exports 설정 완료)
PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-static-01
labels:
app: demo-nfs
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.0.10
path: /export/data01
PVC (해당 PV만 매칭)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs-static-01
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
selector:
matchLabels:
app: demo-nfs
storageClassName: "" # 정적 PV에 바인딩(클래스 없이)
Pod 사용 예시
apiVersion: v1
kind: Pod
metadata:
name: app-uses-nfs
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: pvc-nfs-static-01
예제 B: NFS 동적 프로비저닝 (nfs-subdir-external-provisioner)
전제
- 클러스터에
nfs-subdir-external-provisioner
가 배포되어 있음(Helm 등으로 설치). - 이 프로비저너는 NFS에 서브디렉터리를 자동으로 만들고 바인딩.
StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-dynamic
provisioner: cluster.local/nfs-subdir-external-provisioner # 설치 시 배포된 이름 확인 필요
parameters:
pathPattern: "${.PVC.namespace}/${.PVC.name}"
reclaimPolicy: Delete
allowVolumeExpansion: true
mountOptions:
- vers=4.1
volumeBindingMode: Immediate
PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs-dyn-01
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: nfs-dynamic
Deployment에서 사용
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-with-nfs
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:stable
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumes:
- name: www
persistentVolumeClaim:
claimName: pvc-nfs-dyn-01
예제 C: Local Path Provisioner (동적, 개발/소규모용)
- 각 노드의 로컬 디렉토리를 PVC마다 자동 서브폴더로 할당.
- 단, 파드가 다른 노드로 이동하면 데이터 접근 불가(같은 노드에만 존재).
StorageClass (예시)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer # 파드가 배치되는 노드의 로컬 디스크에 생성
reclaimPolicy: Delete
allowVolumeExpansion: true
PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-local-01
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-path
예제 D: hostPath (개발/테스트 한정)
- 특정 노드의 경로를 직접 마운트. 생산 환경에는 비권장(스케줄 고정/보안/이동성 문제).
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-hostpath-01
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /var/data/demo
persistentVolumeReclaimPolicy: Retain
예제 E: StatefulSet + volumeClaimTemplates
- 파드별 고유한 볼륨 자동 생성(예:
mysql-0
,mysql-1
…).
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
env:
- name: MYSQL_ALLOW_EMPTY_PASSWORD
value: "yes"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: local-path # 또는 nfs/longhorn 등
resources:
requests:
storage: 20Gi
리사이즈/스냅샷/백업
볼륨 리사이즈
- StorageClass에
allowVolumeExpansion: true
필요. - PVC에서
spec.resources.requests.storage
값을 증가. - 드라이버에 따라 온라인/오프라인 리사이즈 지원이 다름(일반적으로 Filesystem 확장 포함).
스냅샷/백업(개요)
- VolumeSnapshotClass / VolumeSnapshot / VolumeSnapshotContent 리소스를 사용(CSI 스냅샷 지원 필요).
- 주기 스냅샷+원격 백업(예: NFS → 외부 NAS/클라우드) 전략 고려.
- DB는 어플리케이션 일관성을 위한 pre/post 훅 또는 논리 백업 병행 권장.
보안/권한/퍼미션
- fsGroup: 마운트된 볼륨의 파일 소유 그룹을 지정해 쓰기 권한 문제 해결에 유용.
spec:
securityContext:
fsGroup: 1000
- runAsUser/runAsGroup: 컨테이너 프로세스 UID/GID 지정.
- SELinux/AppArmor: 보안 정책 활성 환경에서는 컨텍스트/프로파일 확인.
- NFS 주의: 서버 측
root_squash
기본값이면 컨테이너 root가 실제 root로 매핑되지 않음.- 보안상 root_squash 유지 권장. 필요시 애플리케이션 권한/소유권 조정으로 해결.
- readOnlyRootFilesystem: 컨테이너 보안 강화 시 고려(데이터는 PVC에만 쓰기).
성능/운영 팁
- **RWX 공유 스토리지(NFS 등)**는 편리하지만 고IO DB에는 한계. DB는 로컬/블록 스토리지(RWO) 권장.
- volumeBindingMode: WaitForFirstConsumer로 스케줄링/토폴로지 일치.
- mountOptions: noatime 등으로 소폭 성능 향상 가능(워크로드 검증 필수).
- Retain 정책으로 중요 데이터 보호 후, 수동 정리 절차 문서화.
- 홈랩에서 확장성/HA가 필요하면 Longhorn(간편) 또는 Rook-Ceph(유연/고급) 고려.
- 캐시/버퍼/임시파일은 emptyDir 사용으로 비용 절감.
트러블슈팅 체크리스트
- PVC가 Pending에서 멈춤
kubectl describe pvc <name>
이벤트 확인- StorageClass 존재/이름 오타 확인
- 접근 모드/RWX 지원 여부 확인(NFS/드라이버)
- 동적 프로비저너/CSI 컨트롤러 Pod 상태 확인
- 마운트 실패
kubectl describe pod <pod>
이벤트/에러 메시지 확인- NFS 서버 방화벽/exports, 경로 권한 확인
- 컨테이너 UID/GID와 볼륨 퍼미션(fsGroup) 불일치 확인
- 노드 재스케줄 후 데이터 “없음”
- Local Path/hostPath 사용 중인지 확인(노드 종속). StatefulSet + PodAntiAffinity 고려.
- 리사이즈가 반영 안 됨
- StorageClass
allowVolumeExpansion
/드라이버 지원 여부 확인.
- StorageClass
자주 하는 실수
- StorageClass 이름 오타 → PVC Pending
- RWO 볼륨을 여러 노드에서 동시에 마운트하려 시도 → 실패/경합
- NFS 서버 권한/UID/GID 불일치 → “Permission denied”
- 중요한 데이터인데
reclaimPolicy: Delete
로 둠 → PVC 삭제 시 데이터 소실 - 로컬 스토리지로 DB 운영 + 파드 스케줄 이동 → 데이터 유실
명령어 치트시트
# 전체 스토리지 리소스 상태
kubectl get sc
kubectl get pv
kubectl get pvc -A
# 상세 정보
kubectl describe sc <sc-name>
kubectl describe pv <pv-name>
kubectl describe pvc <pvc-name>
# 이벤트/오류 파악
kubectl describe pod <pod-name>
kubectl get events -A --sort-by=.lastTimestamp
부록: 템플릿 모음
1) 최소 StorageClass (동적, 가상의 예시)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: example.csi.driver
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
2) 최소 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-claim
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
storageClassName: standard
3) Pod + PVC
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "echo hello > /data/hi && sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: data-claim
어떤 걸 언제 쓰나? (요약 정리)
상황 | 권장 방식 |
---|---|
홈랩에서 간단히 RWX 공유 필요 | NFS 동적(nfs-subdir-external-provisioner) or 정적 NFS |
단일 노드/개발용, 빠르고 간단 | Local Path Provisioner |
실사용/HA/확장성, 블록·파일 혼합 | Longhorn(쉬움) / Rook-Ceph(고급) |
고IO DB(단일 인스턴스) | RWO 블록/로컬 + StatefulSet |
임시 캐시/버퍼 | emptyDir |
마지막 팁
- 기본 StorageClass를 하나 지정해두면(
storageclass.kubernetes.io/is-default-class: "true"
) PVC에서storageClassName
생략 가능. - 중요한 워크로드는 StatefulSet + volumeClaimTemplates 패턴으로 파드 개별 볼륨을 안전하게 운용.
- 권한 문제는 우선
fsGroup
으로 해결 시도 → 앱 사용자/그룹 맞추기.
행운을 빕니다! 🚀 홈 K8s에서도 안정적이고 유연한 스토리지 운영 하세요.