K8s PV·PVC 스토리지 운영

Kubernetes 스토리지의 핵심 개념

컨테이너는 본질적으로 임시(ephemeral)다. Pod가 재시작되면 컨테이너 내부 파일시스템은 초기화된다. 데이터베이스, 파일 업로드, 로그 등 영속적인 데이터가 필요한 워크로드에서는 Kubernetes의 Persistent Volume(PV)Persistent Volume Claim(PVC)을 통해 스토리지를 관리해야 한다.

PV · PVC · StorageClass 관계

개념 역할 비유
PersistentVolume (PV) 클러스터 레벨 스토리지 리소스 실제 디스크
PersistentVolumeClaim (PVC) Pod가 스토리지를 요청하는 선언 디스크 신청서
StorageClass 동적 프로비저닝 정책 디스크 등급 (SSD/HDD)

PVC가 요청을 보내면, StorageClass에 의해 PV가 동적으로 생성되거나, 관리자가 미리 만들어둔 PV에 정적으로 바인딩된다.

정적 프로비저닝: PV 수동 생성

NFS, 기존 디스크 등 인프라팀이 직접 관리하는 스토리지를 사용할 때:

# pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-data
  labels:
    type: nfs
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteMany          # 여러 Pod에서 동시 읽기/쓰기
  persistentVolumeReclaimPolicy: Retain  # PVC 삭제 후에도 데이터 보존
  nfs:
    server: 10.0.0.100
    path: /exports/data
---
# pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs-data
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 50Gi
  selector:
    matchLabels:
      type: nfs              # 특정 PV를 라벨로 선택

정적 프로비저닝은 PV와 PVC를 직접 매칭해야 하므로 운영 부담이 크다. 대부분의 클라우드 환경에서는 동적 프로비저닝을 사용한다.

동적 프로비저닝: StorageClass

PVC를 생성하면 StorageClass가 자동으로 PV를 프로비저닝한다:

# storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com      # AWS EBS CSI 드라이버
parameters:
  type: gp3                        # SSD 타입
  iops: "5000"
  throughput: "250"
  encrypted: "true"
reclaimPolicy: Delete              # PVC 삭제 시 PV도 삭제
volumeBindingMode: WaitForFirstConsumer  # Pod 스케줄링 시점에 바인딩
allowVolumeExpansion: true         # 볼륨 확장 허용
---
# PVC만 생성하면 PV는 자동 생성
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  storageClassName: fast-ssd       # StorageClass 지정
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi

volumeBindingMode: 스케줄링 타이밍

StorageClass에서 가장 중요한 설정 중 하나:

모드 동작 사용 시점
Immediate PVC 생성 즉시 PV 프로비저닝 존 제약 없는 스토리지
WaitForFirstConsumer Pod가 스케줄링될 때 PV 생성 존 바인딩이 필요한 경우 (권장)
# ❌ Immediate 문제: PV가 us-east-1a에 생성됐는데 Pod가 us-east-1b에 스케줄링
# → Pod가 Pending 상태로 멈춤

# ✅ WaitForFirstConsumer: Pod가 스케줄링된 노드의 존에 PV 생성
# → 항상 같은 AZ에 매칭

K8s Affinity 스케줄링과 함께 사용할 때 WaitForFirstConsumer가 특히 중요하다. Affinity로 특정 존에 Pod를 배치하면 PV도 같은 존에 생성된다.

Access Modes: 접근 모드 이해

모드 약어 설명 대표 스토리지
ReadWriteOnce RWO 단일 노드에서 읽기/쓰기 EBS, GCE PD
ReadOnlyMany ROX 여러 노드에서 읽기 전용 NFS, GlusterFS
ReadWriteMany RWX 여러 노드에서 읽기/쓰기 EFS, CephFS, NFS
ReadWriteOncePod RWOP 단일 Pod에서만 읽기/쓰기 CSI 드라이버 지원 시
# DB는 RWO (단일 노드 마운트)
# 업로드 파일 공유는 RWX (여러 Pod 동시 접근)
# 민감 데이터는 RWOP (단일 Pod만 접근, K8s 1.27+)

Reclaim Policy: PVC 삭제 후 동작

# Retain: PVC 삭제 후에도 PV와 데이터 보존 (수동 정리 필요)
# Delete: PVC 삭제 시 PV와 실제 스토리지도 함께 삭제 (동적 프로비저닝 기본값)
# Recycle: 더 이상 사용하지 않음 (deprecated)

# 프로덕션 DB → Retain (데이터 보호)
# 임시 캐시/빌드 → Delete (자동 정리)

실수 방지: 동적 프로비저닝의 기본 reclaimPolicy는 Delete다. 프로덕션 데이터베이스의 PVC를 실수로 삭제하면 디스크 자체가 삭제된다. 중요한 워크로드에는 반드시 Retain을 설정하자.

볼륨 확장 (Volume Expansion)

디스크 용량이 부족할 때 PVC를 수정해 확장할 수 있다:

# StorageClass에 allowVolumeExpansion: true 필요

# PVC 용량 변경
kubectl patch pvc app-data -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'

# 상태 확인 — FileSystemResizePending이면 Pod 재시작 필요
kubectl get pvc app-data -o jsonpath='{.status.conditions[*].type}'

# 오프라인 확장이 필요한 스토리지는 Pod 삭제 후 재생성
kubectl delete pod app-pod-0
# Pod 재시작 시 파일시스템 확장 자동 수행

주의: 볼륨 축소는 지원되지 않는다. 한번 늘린 용량은 줄일 수 없으므로 신중하게 확장해야 한다.

StatefulSet과 volumeClaimTemplates

StatefulSet에서 Pod마다 독립 PVC를 자동 생성하는 패턴. DB 클러스터에서 핵심적으로 사용된다:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          volumeMounts:
            - name: pgdata
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              memory: "1Gi"
              cpu: "500m"
  volumeClaimTemplates:            # Pod마다 별도 PVC 생성
    - metadata:
        name: pgdata
      spec:
        storageClassName: fast-ssd
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 100Gi

이 설정으로 pgdata-postgres-0, pgdata-postgres-1, pgdata-postgres-2 세 개의 PVC가 자동 생성된다. StatefulSet을 삭제해도 PVC는 삭제되지 않는다 — 데이터 보호를 위한 의도적 설계다.

스토리지 모니터링과 알림

# PVC 사용량 확인
kubectl get pvc -A -o wide

# 실제 디스크 사용량 확인 (kubelet metrics)
kubectl get --raw /api/v1/nodes/<node>/proxy/stats/summary 
  | jq '.pods[].volume[]? | select(.pvcRef) | {pvc: .pvcRef.name, used: .usedBytes, capacity: .capacityBytes}'

# Prometheus 알림 규칙 예시
# kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 0.85
# → 85% 초과 시 알림

K8s ConfigMap·Secret 운영에서 다룬 것처럼, 스토리지도 용량 임계치를 모니터링하고 선제적으로 확장해야 장애를 방지할 수 있다.

스냅샷과 백업 (VolumeSnapshot)

# VolumeSnapshotClass 정의
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: ebs-snapshot-class
driver: ebs.csi.aws.com
deletionPolicy: Retain

---
# 스냅샷 생성
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: pgdata-snapshot-20260305
spec:
  volumeSnapshotClassName: ebs-snapshot-class
  source:
    persistentVolumeClaimName: pgdata-postgres-0

---
# 스냅샷에서 PVC 복원
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pgdata-restored
spec:
  storageClassName: fast-ssd
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 100Gi
  dataSource:
    name: pgdata-snapshot-20260305
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io

정리: 스토리지 설계 체크리스트

항목 권장 설정
프로비저닝 동적 (StorageClass)
바인딩 모드 WaitForFirstConsumer
프로덕션 DB reclaimPolicy Retain
볼륨 확장 allowVolumeExpansion: true
다중 Pod 공유 RWX (EFS/NFS) 사용
백업 VolumeSnapshot + CronJob
모니터링 85% 임계치 알림

Kubernetes 스토리지는 “PVC만 만들면 끝”이 아니다. volumeBindingMode로 AZ 불일치를 방지하고, reclaimPolicy로 데이터 손실을 막고, VolumeSnapshot으로 백업을 자동화해야 프로덕션 수준의 스토리지 운영이 가능하다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux