Kubernetes 스토리지 아키텍처
Kubernetes에서 컨테이너는 기본적으로 ephemeral(휘발성)입니다. Pod가 재시작되면 파일시스템의 모든 데이터가 사라집니다. 데이터베이스, 파일 업로드, 로그 등 영속적 데이터를 다루려면 PersistentVolume(PV), PersistentVolumeClaim(PVC), StorageClass를 이해해야 합니다.
이 글에서는 스토리지 3계층 아키텍처, 동적 프로비저닝, 볼륨 모드와 접근 모드, CSI 드라이버, 그리고 StatefulSet과의 결합 패턴을 다룹니다.
PV · PVC · StorageClass 관계
StorageClass (관리자 정의)
↓ 동적 프로비저닝
PersistentVolume (실제 스토리지)
↑ 바인딩
PersistentVolumeClaim (개발자 요청)
↑ 마운트
Pod → Container
- PersistentVolume(PV): 클러스터 수준의 스토리지 리소스. NFS, AWS EBS, GCE PD 등 실제 볼륨
- PersistentVolumeClaim(PVC): 개발자가 스토리지를 요청하는 선언. “10Gi SSD 주세요”
- StorageClass: 스토리지 프로비저너와 파라미터 정의. PVC 생성 시 자동으로 PV 생성
StorageClass: 동적 프로비저닝의 핵심
# AWS EBS StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: ebs.csi.aws.com
parameters:
type: gp3 # EBS 볼륨 타입
iops: "5000"
throughput: "250" # MB/s
encrypted: "true"
fsType: ext4
reclaimPolicy: Retain # PVC 삭제 후 PV 유지
allowVolumeExpansion: true # 온라인 볼륨 확장 허용
volumeBindingMode: WaitForFirstConsumer # Pod 스케줄링 시점에 바인딩
mountOptions:
- noatime
- nodiratime
주요 설정 상세
- reclaimPolicy:
Delete(기본): PVC 삭제 시 PV와 실제 볼륨도 삭제Retain: PVC 삭제 후에도 PV와 데이터 보존. 프로덕션 DB 필수
- volumeBindingMode:
Immediate: PVC 생성 즉시 PV 바인딩 (AZ 불일치 위험)WaitForFirstConsumer: Pod가 스케줄링될 때 바인딩 (AZ 일치 보장)
- allowVolumeExpansion: true면 PVC 용량을 늘릴 수 있음 (축소는 불가)
# 다양한 환경별 StorageClass
---
# 개발용: 저렴한 HDD
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: dev-standard
provisioner: ebs.csi.aws.com
parameters:
type: gp2
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
---
# 프로덕션 DB: 고성능 SSD + 보존
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: prod-database
provisioner: ebs.csi.aws.com
parameters:
type: io2
iops: "10000"
encrypted: "true"
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
PersistentVolumeClaim 실전 패턴
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: production
spec:
storageClassName: prod-database
accessModes:
- ReadWriteOnce # 단일 노드에서만 읽기/쓰기
resources:
requests:
storage: 100Gi
Access Modes 정리
ReadWriteOnce (RWO): 단일 노드에서 읽기/쓰기. EBS, GCE PD 등 블록 스토리지ReadOnlyMany (ROX): 여러 노드에서 읽기 전용ReadWriteMany (RWX): 여러 노드에서 읽기/쓰기. NFS, EFS, CephFS 등 파일 스토리지ReadWriteOncePod (RWOP): K8s 1.27+ 단일 Pod 전용. 가장 안전한 격리
Pod에서 PVC 마운트
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels: { app: postgres }
template:
metadata:
labels: { app: postgres }
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
- name: config
mountPath: /etc/postgresql/conf.d
readOnly: true
resources:
requests: { cpu: 500m, memory: 1Gi }
limits: { cpu: "2", memory: 4Gi }
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-data
- name: config
configMap:
name: postgres-config
StatefulSet + volumeClaimTemplates
StatefulSet은 volumeClaimTemplates로 Pod마다 고유한 PVC를 자동 생성합니다. 노드 관리 시에도 PVC가 Pod를 따라다닙니다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-cluster
spec:
serviceName: redis-cluster
replicas: 6
selector:
matchLabels: { app: redis-cluster }
template:
metadata:
labels: { app: redis-cluster }
spec:
containers:
- name: redis
image: redis:7
ports:
- containerPort: 6379
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: fast-ssd
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
이렇게 하면 data-redis-cluster-0, data-redis-cluster-1 … 각 Pod마다 독립적인 PVC가 생성됩니다.
볼륨 확장 (온라인 리사이즈)
# PVC 용량 늘리기 (축소 불가)
kubectl patch pvc postgres-data -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
# 확장 상태 확인
kubectl describe pvc postgres-data
# Conditions:
# Type: FileSystemResizePending → Resizing → 완료 후 사라짐
# 파일시스템 리사이즈는 CSI 드라이버가 자동 처리 (Pod 재시작 불필요)
볼륨 스냅샷
# VolumeSnapshot으로 백업
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: postgres-snapshot-20260227
spec:
volumeSnapshotClassName: csi-aws-snapclass
source:
persistentVolumeClaimName: postgres-data
---
# 스냅샷에서 새 PVC 복원
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data-restored
spec:
storageClassName: prod-database
dataSource:
name: postgres-snapshot-20260227
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
모니터링
# kubelet이 노출하는 PVC 메트릭
kubelet_volume_stats_capacity_bytes # 총 용량
kubelet_volume_stats_used_bytes # 사용량
kubelet_volume_stats_available_bytes # 남은 용량
kubelet_volume_stats_inodes # inode 총수
kubelet_volume_stats_inodes_used # inode 사용수
# Prometheus Alert: 볼륨 90% 사용 시 경고
groups:
- name: volume-alerts
rules:
- alert: PVCAlmostFull
expr: |
kubelet_volume_stats_used_bytes
/ kubelet_volume_stats_capacity_bytes
> 0.9
for: 5m
labels:
severity: critical
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} 90% 초과"
ResourceQuota로 네임스페이스별 스토리지 총량도 제한할 수 있습니다.
흔한 실수
1. volumeBindingMode: Immediate + 멀티 AZ
# ❌ PV가 us-east-1a에 생성되었는데 Pod가 us-east-1b에 스케줄링
# → Pod가 Pending 상태로 멈춤
# ✅ WaitForFirstConsumer 사용
2. reclaimPolicy: Delete로 프로덕션 DB 운영
# ❌ PVC 실수로 삭제 → 데이터 영구 손실
# ✅ 프로덕션 DB는 반드시 Retain + 정기 스냅샷
3. RWO 볼륨 + Deployment replicas > 1
# ❌ EBS는 RWO → 여러 노드의 Pod에서 동시 마운트 불가
# ✅ StatefulSet 사용 또는 RWX 스토리지(EFS) 전환
마무리
Kubernetes 스토리지의 핵심 원칙: StorageClass로 동적 프로비저닝, WaitForFirstConsumer로 AZ 일치, 프로덕션은 Retain 정책, VolumeSnapshot으로 백업. Helm Chart에서 StorageClass를 values로 추상화하면 환경별(dev/staging/prod) 스토리지 설정을 유연하게 관리할 수 있습니다.