Theme:

PVC를 만들 때마다 관리자가 수동으로 PV를 생성해야 한다면, 수백 개의 서비스가 있는 클러스터에서는 어떻게 관리할 수 있을까요?

Static Provisioning은 관리자가 미리 PV를 생성해 두는 방식이지만, 서비스가 많아지면 관리가 불가능해집니다. StorageClass와 동적 프로비저닝은 PVC를 생성하면 자동으로 PV가 만들어지게 해서 이 문제를 해결합니다.

StorageClass란?

StorageClass는 "어떤 스토리지를, 어떤 방식으로 생성할지"를 정의하는 오브젝트입니다. PVC가 StorageClass를 지정하면, 해당 설정에 따라 PV가 자동으로 프로비저닝됩니다.

YAML
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com      # CSI 드라이버
parameters:                         # 프로비저너에 전달할 파라미터
  type: gp3
  iops: "3000"
  throughput: "125"
reclaimPolicy: Delete               # PVC 삭제 시 PV도 삭제
volumeBindingMode: WaitForFirstConsumer  # Pod 스케줄 후 바인딩
allowVolumeExpansion: true          # 볼륨 확장 허용
mountOptions:                       # 마운트 옵션
  - discard
  - noatime

CSI (Container Storage Interface)

CSI는 Kubernetes와 스토리지 시스템을 연결하는 표준 인터페이스입니다. 이전에는 스토리지 드라이버가 Kubernetes 코어에 포함되어 있었지만(in-tree), CSI를 통해 외부 플러그인(out-of-tree)으로 분리되었습니다.

CSI 아키텍처

PLAINTEXT
PVC 생성 → API Server → CSI Controller → 스토리지 API 호출 → 볼륨 생성

Pod 스케줄 → kubelet → CSI Node Plugin → 볼륨 연결(Attach) → 마운트

CSI 드라이버는 두 가지 컴포넌트로 구성됩니다.

  • Controller Plugin: 볼륨 생성/삭제/스냅샷 (Deployment로 실행)
  • Node Plugin: 볼륨 연결/마운트/포맷 (DaemonSet으로 실행)

주요 CSI 드라이버

프로비저너스토리지특징
ebs.csi.aws.comAWS EBS블록 스토리지, RWO만
efs.csi.aws.comAWS EFS파일 스토리지, RWX 지원
pd.csi.storage.gke.ioGCP Persistent Disk블록 스토리지
disk.csi.azure.comAzure Disk블록 스토리지
nfs.csi.k8s.ioNFS파일 스토리지, RWX 지원
SHELL
# 클러스터에 설치된 CSI 드라이버 확인
kubectl get csidrivers

클라우드별 StorageClass 예제

AWS

YAML
# 범용 SSD (gp3)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
# 고성능 SSD (io2)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: io2-high-perf
provisioner: ebs.csi.aws.com
parameters:
  type: io2
  iops: "10000"
  encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain

GCP

YAML
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ssd
provisioner: pd.csi.storage.gke.io
parameters:
  type: pd-ssd
volumeBindingMode: WaitForFirstConsumer

NFS

YAML
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs
provisioner: nfs.csi.k8s.io
parameters:
  server: nfs-server.default.svc.cluster.local
  share: /exports

volumeBindingMode

PV가 언제 생성되고 바인딩되는지를 제어하는 중요한 설정입니다.

Immediate (기본값)

PVC가 생성되면 즉시 PV를 생성하고 바인딩합니다.

PLAINTEXT
PVC 생성 → 즉시 PV 프로비저닝 → 바인딩
         (AZ를 모름 → Pod과 다른 AZ에 생성될 수 있음!)

문제: PV가 us-east-1a에 생성되었는데 Pod이 us-east-1b에 스케줄되면 마운트가 실패합니다.

WaitForFirstConsumer (권장)

Pod이 스케줄될 때까지 PV 생성을 지연합니다.

PLAINTEXT
PVC 생성 → Pending 상태 → Pod 스케줄 → Pod의 노드/AZ에서 PV 생성 → 바인딩
YAML
volumeBindingMode: WaitForFirstConsumer  # 거의 항상 이것을 사용

이 설정으로 Pod과 PV의 토폴로지(AZ) 불일치 문제를 방지할 수 있습니다.

Default StorageClass

storageClassName을 지정하지 않은 PVC에 자동 적용됩니다.

YAML
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"  # Default 설정
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
SHELL
# Default StorageClass 확인 (default) 표시됨
kubectl get sc
# NAME                 PROVISIONER          RECLAIM   VOLUMEBINDINGMODE
# standard (default)   ebs.csi.aws.com      Delete    WaitForFirstConsumer
# fast-ssd             ebs.csi.aws.com      Retain    WaitForFirstConsumer

동적 프로비저닝 전체 흐름

YAML
# 1. StorageClass 정의 (한 번만)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# 2. PVC 생성 (개발자)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi
---
# 3. Pod에서 사용 (개발자)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: my-app:1.0
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: app-data

StorageClass 설계 전략

환경에 맞는 StorageClass를 미리 정의해 두면 개발자가 용도에 따라 선택할 수 있습니다.

YAML
# 개발/테스트용 — 저비용
fast-ssd:
  type: gp3
  reclaimPolicy: Delete

# 프로덕션 일반용
standard-prod:
  type: gp3
  reclaimPolicy: Retain
  encrypted: true

# 프로덕션 DB용 — 고성능
high-perf-prod:
  type: io2
  iops: 10000
  reclaimPolicy: Retain
  encrypted: true

# 공유 파일시스템 (RWX)
shared-efs:
  provisioner: efs.csi.aws.com
  reclaimPolicy: Retain

트러블슈팅

SHELL
# PVC가 Pending인 경우
kubectl describe pvc app-data
# 가능한 원인:
# - StorageClass가 존재하지 않음
# - CSI 드라이버가 설치되지 않음
# - AZ에 사용 가능한 볼륨이 없음
# - 할당량 초과

# CSI 드라이버 Pod 상태 확인
kubectl get pods -n kube-system | grep csi

# 볼륨 이벤트 확인
kubectl get events --field-selector reason=ProvisioningFailed

정리

StorageClass는 동적 프로비저닝의 핵심으로, PVC를 생성하면 자동으로 PV가 만들어지게 합니다. CSI 드라이버가 실제 스토리지 생성을 담당하고, volumeBindingMode: WaitForFirstConsumer로 토폴로지 문제를 방지합니다. 환경별/용도별 StorageClass를 미리 설계해 두면 스토리지 관리가 크게 단순화됩니다.

댓글 로딩 중...