[Daily morning study] Kubernetes Persistent Volumes와 StorageClass 스토리지 관리
#daily morning study
왜 스토리지가 필요한가
컨테이너는 기본적으로 에페머럴(ephemeral)하다. Pod가 재시작되거나 다른 노드로 이동하면 컨테이너 내부에 저장된 데이터는 모두 사라진다. 데이터베이스나 파일 업로드 서비스처럼 상태를 유지해야 하는 애플리케이션은 Pod 수명과 독립적인 영속성 스토리지가 필요하다.
Kubernetes는 이를 위해 세 가지 핵심 리소스를 제공한다:
- PersistentVolume (PV): 클러스터에 프로비저닝된 실제 스토리지 조각
- PersistentVolumeClaim (PVC): Pod가 PV에 대해 요청하는 명세
- StorageClass: PV를 동적으로 프로비저닝하는 방법을 정의하는 템플릿
PersistentVolume (PV)
PV는 클러스터 관리자가 미리 준비하거나 StorageClass에 의해 자동 생성되는 스토리지 리소스다. 노드가 클러스터의 컴퓨팅 리소스인 것처럼 PV는 클러스터의 스토리지 리소스다.
PV는 Pod와 무관하게 독립적인 수명 주기를 가지므로, Pod가 삭제되어도 PV 안의 데이터는 보존된다.
PV 예시 (NFS 기반)
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-example
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/exports
server: 192.168.1.100
주요 필드 설명:
| 필드 | 설명 |
|---|---|
capacity.storage | 스토리지 용량 |
accessModes | 마운트 방식 |
persistentVolumeReclaimPolicy | PVC 삭제 후 PV 처리 방식 |
Access Modes
PV가 어떤 방식으로 마운트될 수 있는지를 정의한다.
| 모드 | 약어 | 설명 |
|---|---|---|
| ReadWriteOnce | RWO | 단일 노드에서 읽기/쓰기 가능 |
| ReadOnlyMany | ROX | 여러 노드에서 읽기 전용 마운트 |
| ReadWriteMany | RWX | 여러 노드에서 읽기/쓰기 가능 |
| ReadWriteOncePod | RWOP | 단일 Pod에서만 읽기/쓰기 (Kubernetes 1.22+) |
EBS 같은 블록 스토리지는 보통 RWO만 지원하고, NFS 같은 네트워크 스토리지는 RWX를 지원한다.
Reclaim Policy
PVC가 삭제될 때 PV를 어떻게 처리할지 결정한다.
| 정책 | 설명 |
|---|---|
| Retain | PV를 그대로 보존. 관리자가 수동으로 처리해야 함 |
| Delete | PV와 외부 스토리지(EBS, GCE PD 등)를 모두 삭제 |
| Recycle | 데이터를 삭제하고 PV를 재사용 가능 상태로 (deprecated) |
프로덕션에서는 중요한 데이터가 있을 경우 Retain을 써서 실수로 데이터가 삭제되는 걸 막는 게 안전하다.
PersistentVolumeClaim (PVC)
PVC는 Pod가 PV에 대한 요구 사항을 선언하는 방식이다. 사용자는 PV의 구현 세부 사항을 몰라도 PVC를 통해 필요한 용량과 접근 방식만 명시하면 Kubernetes가 적합한 PV를 바인딩해준다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: standard
PVC를 Pod에서 사용하기
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: nginx
volumeMounts:
- mountPath: /data
name: storage
volumes:
- name: storage
persistentVolumeClaim:
claimName: my-pvc
PV-PVC 바인딩 프로세스
PVC 생성 요청
↓
Kubernetes Control Plane이 조건에 맞는 PV 탐색
(capacity, accessModes, storageClassName 일치 여부)
↓
적합한 PV 발견 → 바인딩 (1:1 관계)
↓
PVC 상태: Pending → Bound
↓
Pod가 PVC를 마운트하여 사용
바인딩은 1:1 관계다. PV 하나에 PVC 하나만 바인딩될 수 있다. PVC가 삭제되면 PV는 Released 상태가 되며 Reclaim Policy에 따라 처리된다.
StorageClass와 동적 프로비저닝
정적 프로비저닝(관리자가 PV를 미리 만들어두는 방식)은 스토리지 요청이 많아질수록 관리 부담이 커진다. StorageClass를 사용하면 PVC 생성 시 자동으로 PV를 프로비저닝할 수 있다.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iops: "3000"
throughput: "125"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
주요 필드:
| 필드 | 설명 |
|---|---|
provisioner | 스토리지를 생성할 CSI 드라이버 또는 내장 플러그인 |
parameters | provisioner에 전달할 설정값 (벤더마다 다름) |
reclaimPolicy | 이 StorageClass로 만들어진 PV의 기본 Reclaim 정책 |
allowVolumeExpansion | PVC 용량 확장 허용 여부 |
volumeBindingMode | PV 바인딩 타이밍 |
volumeBindingMode
Immediate: PVC 생성 즉시 PV 프로비저닝 및 바인딩WaitForFirstConsumer: PVC를 사용하는 Pod가 스케줄링될 때 해당 노드의 AZ(가용 영역)를 고려해 PV 생성
멀티 AZ 환경에서는 WaitForFirstConsumer를 써야 Pod와 PV가 같은 AZ에 생성된다. 그렇지 않으면 Pod가 스케줄된 AZ와 다른 AZ에 PV가 생성되어 마운트 실패가 발생할 수 있다.
동적 프로비저닝 흐름
사용자가 PVC 생성 (storageClassName: fast-ssd 명시)
↓
Kubernetes가 fast-ssd StorageClass 확인
↓
provisioner(aws-ebs CSI 드라이버)에 볼륨 생성 요청
↓
실제 EBS 볼륨 생성
↓
PV 오브젝트 자동 생성 및 PVC에 바인딩
↓
Pod에서 사용 가능
CSI (Container Storage Interface)
CSI는 Kubernetes와 외부 스토리지 시스템 사이의 표준 인터페이스다. 과거에는 스토리지 드라이버가 Kubernetes 코어에 포함되어 있었지만, CSI를 통해 벤더가 플러그인을 독립적으로 개발 및 배포할 수 있게 됐다.
주요 CSI 드라이버 예시:
| 드라이버 | 설명 |
|---|---|
ebs.csi.aws.com | AWS EBS |
efs.csi.aws.com | AWS EFS (NFS, RWX 지원) |
pd.csi.storage.gke.io | GCE Persistent Disk |
disk.csi.azure.com | Azure Disk |
rook-ceph.rbd.csi.ceph.com | Rook-Ceph (온프레미스) |
볼륨 스냅샷
Kubernetes 1.17+ 에서 VolumeSnapshot을 통해 PVC의 스냅샷을 찍고 복원할 수 있다.
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: db-snapshot
spec:
volumeSnapshotClassName: csi-aws-vsc
source:
persistentVolumeClaimName: db-pvc
스냅샷에서 PVC를 복원하려면:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-pvc-restored
spec:
dataSource:
name: db-snapshot
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
StatefulSet과 volumeClaimTemplates
StatefulSet에서는 각 Pod마다 별도의 PVC가 필요하다. volumeClaimTemplates를 사용하면 Pod 생성 시 자동으로 PVC가 만들어진다.
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.0
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi
이렇게 하면 mysql-0, mysql-1, mysql-2 각 Pod에 대해 data-mysql-0, data-mysql-1, data-mysql-2라는 PVC가 각각 생성된다. StatefulSet을 삭제해도 이 PVC들은 자동으로 삭제되지 않아 데이터를 보존할 수 있다.
용량 확장
StorageClass에 allowVolumeExpansion: true가 설정되어 있다면 PVC의 용량을 늘릴 수 있다.
kubectl patch pvc my-pvc -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
단, 볼륨 축소(shrink)는 지원하지 않는다.
정리
| 개념 | 역할 |
|---|---|
| PV | 실제 스토리지 리소스 (클러스터 수준) |
| PVC | Pod의 스토리지 요청 명세 (네임스페이스 수준) |
| StorageClass | 동적 PV 프로비저닝 방법 정의 |
| CSI 드라이버 | 외부 스토리지와 Kubernetes를 연결하는 표준 인터페이스 |
스토리지 설계 시 핵심 포인트:
- RWX가 필요하면 EFS, NFS, Ceph 같은 분산 파일시스템을 써야 한다
- 멀티 AZ 환경에서는
WaitForFirstConsumer바인딩 모드 필수 - 프로덕션 데이터는 Reclaim Policy를
Retain으로 설정해 실수로 삭제되지 않도록 한다 - StatefulSet의 volumeClaimTemplates으로 Pod별 독립 스토리지를 간편하게 관리할 수 있다