pv pvc storageclass 에 관해서

Kubernetes 스토리지 가이드: PV / PVC / StorageClass 완전 정복

버전 무관 개념 중심 정리 (CSI 기반 일반 원칙). 집/온프레미스 K8s에도 바로 적용 가능.

목차

  1. 빠른 요약 (TL;DR)
  2. 핵심 개념
  3. 각 오브젝트 심화
  4. 프로비저닝 방식
  5. 접근 모드 / 볼륨 모드
  6. 예제로 배우는 구성 패턴
  7. 리사이즈/스냅샷/백업
  8. 보안/권한/퍼미션
  9. 성능/운영 팁
  10. 트러블슈팅 체크리스트
  11. 자주 하는 실수
  12. 명령어 치트시트
  13. 부록: 템플릿 모음

빠른 요약 (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 vs WaitForFirstConsumer.
    • 후자는 Pod 스케줄링 시점에 노드/가용영역을 고려해 바인딩(클라우드/토폴로지에 유리).
  • 확장(allowVolumeExpansion): PVC 사이즈 증가 허용 여부.

각 오브젝트 심화

PV (PersistentVolume)

  • 무엇: “실제 디스크”를 나타내는 K8s 오브젝트.
  • 주요 필드
    • spec.capacity.storage: 용량(예: 10Gi)
    • spec.accessModes: RWO/ROX/RWX
    • spec.volumeMode: Filesystem/Block
    • spec.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/Retain
    • allowVolumeExpansion: true/false
    • volumeBindingMode: Immediate / WaitForFirstConsumer
    • mountOptions: noatime 등 마운트 옵션
    • allowedTopologies: 영역/존 한정

프로비저닝 방식

1) Static Provisioning

  1. 관리자가 PV를 미리 정의(NFS 경로/로컬 디스크 등).
  2. 사용자가 PVC를 만들면 조건 일치하는 PV와 바인딩.
  3. 주로 온프레미스에서 기존 NAS/NFS 공유를 잡아줄 때 단순/명확.

2) Dynamic Provisioning

  1. 관리자가 StorageClass와 **프로비저너(또는 CSI 드라이버)**를 배포.
  2. 사용자가 PVC 생성 시 storageClassName을 명시.
  3. 컨트롤러가 조건에 맞는 PV를 자동 생성하고 바인딩.
  4. 클라우드/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

리사이즈/스냅샷/백업

볼륨 리사이즈

  1. StorageClass에 allowVolumeExpansion: true 필요.
  2. PVC에서 spec.resources.requests.storage 값을 증가.
  3. 드라이버에 따라 온라인/오프라인 리사이즈 지원이 다름(일반적으로 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 이름 오타 → 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에서도 안정적이고 유연한 스토리지 운영 하세요.