K8s VolumeSnapshot 백업·복원

K8s Volume Snapshot이란?

Kubernetes VolumeSnapshot은 PersistentVolume의 특정 시점 스냅샷을 생성하고, 이를 기반으로 새 PVC를 복원하는 기능입니다. 데이터베이스 백업, 블루-그린 데이터 마이그레이션, 장애 복구(DR)에 필수적인 스토리지 운영 패턴입니다. Kubernetes 1.20에서 GA되었으며, CSI 드라이버가 스냅샷을 지원해야 합니다.

VolumeSnapshot 아키텍처

Volume Snapshot은 3개의 CRD로 구성됩니다:

리소스 역할 비유
VolumeSnapshotClass 스냅샷 정책 정의 (CSI 드라이버, 삭제 정책) StorageClass와 동일 역할
VolumeSnapshot 스냅샷 요청 (사용자가 생성) PVC와 동일 역할
VolumeSnapshotContent 실제 스냅샷 데이터 (자동 생성) PV와 동일 역할

VolumeSnapshotClass 설정

# AWS EBS CSI 드라이버용 VolumeSnapshotClass
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: ebs-snapshot-class
  annotations:
    snapshot.storage.kubernetes.io/is-default-class: "true"
driver: ebs.csi.aws.com
deletionPolicy: Retain    # Delete: PVC 삭제 시 스냅샷도 삭제
                           # Retain: PVC 삭제해도 스냅샷 보존
parameters:
  tagSpecification_1: "Environment=production"
  tagSpecification_2: "ManagedBy=kubernetes"

---
# GCP PD CSI 드라이버용
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: gcp-snapshot-class
driver: pd.csi.storage.gke.io
deletionPolicy: Retain
parameters:
  storage-locations: asia-northeast3

---
# Ceph RBD CSI 드라이버용 (온프레미스)
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: ceph-snapshot-class
driver: rbd.csi.ceph.com
deletionPolicy: Delete
parameters:
  clusterID: ceph-cluster-1
  csi.storage.k8s.io/snapshotter-secret-name: ceph-secret
  csi.storage.k8s.io/snapshotter-secret-namespace: ceph-system

스냅샷 생성과 상태 확인

# PVC에서 스냅샷 생성
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-20260320
  namespace: production
  labels:
    app: mysql
    snapshot-type: scheduled
spec:
  volumeSnapshotClassName: ebs-snapshot-class
  source:
    persistentVolumeClaimName: mysql-data   # 원본 PVC
# 스냅샷 생성 및 상태 확인
kubectl apply -f mysql-snapshot.yaml

# 상태 확인
kubectl get volumesnapshot mysql-snapshot-20260320 -n production
# NAME                       READYTOUSE   SOURCEPVC    RESTORESIZE   AGE
# mysql-snapshot-20260320    true         mysql-data   50Gi          2m

# 상세 정보
kubectl describe volumesnapshot mysql-snapshot-20260320 -n production
# Status:
#   Bound Volume Snapshot Content Name: snapcontent-abc123
#   Creation Time: 2026-03-20T21:00:00Z
#   Ready To Use: true
#   Restore Size: 50Gi

# VolumeSnapshotContent 확인 (자동 생성됨)
kubectl get volumesnapshotcontent
# NAME                READYTOUSE   RESTORESIZE   DELETIONPOLICY   DRIVER
# snapcontent-abc123  true         53687091200   Retain           ebs.csi.aws.com

스냅샷에서 PVC 복원

스냅샷을 dataSource로 지정하여 새 PVC를 생성합니다:

# 스냅샷에서 새 PVC 복원
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data-restored
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: gp3-encrypted    # 원본과 동일 StorageClass
  resources:
    requests:
      storage: 50Gi                  # 스냅샷 크기 이상
  dataSource:
    name: mysql-snapshot-20260320    # 스냅샷 이름
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
# 복원 후 Pod에 마운트
apiVersion: v1
kind: Pod
metadata:
  name: mysql-restored
  namespace: production
spec:
  containers:
  - name: mysql
    image: mysql:8.0
    volumeMounts:
    - name: data
      mountPath: /var/lib/mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
        secretKeyRef:
          name: mysql-secret
          key: root-password
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: mysql-data-restored   # 복원된 PVC

자동 스냅샷: CronJob 패턴

CronJob으로 정기 스냅샷을 생성하고 오래된 스냅샷을 정리합니다:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: mysql-snapshot-cron
  namespace: production
spec:
  schedule: "0 2 * * *"        # 매일 새벽 2시
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: snapshot-manager
          restartPolicy: OnFailure
          containers:
          - name: snapshot-creator
            image: bitnami/kubectl:1.29
            command:
            - /bin/sh
            - -c
            - |
              set -e
              DATE=$(date +%Y%m%d-%H%M)
              SNAPSHOT_NAME="mysql-snap-${DATE}"
              
              # 스냅샷 생성
              cat <
# RBAC: snapshot-manager ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: snapshot-manager
  namespace: production
rules:
- apiGroups: ["snapshot.storage.k8s.io"]
  resources: ["volumesnapshots"]
  verbs: ["get", "list", "create", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: snapshot-manager-binding
  namespace: production
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: snapshot-manager
subjects:
- kind: ServiceAccount
  name: snapshot-manager
  namespace: production

StatefulSet 데이터 복구 패턴

StatefulSet의 PVC를 스냅샷에서 복구하는 실전 절차입니다:

# 1. StatefulSet 스케일 다운
kubectl scale statefulset mysql --replicas=0 -n production

# 2. 기존 PVC 백업 (이름 변경)
kubectl get pvc mysql-data-mysql-0 -n production -o yaml > pvc-backup.yaml

# 3. 기존 PVC 삭제
kubectl delete pvc mysql-data-mysql-0 -n production

# 4. 스냅샷에서 동일 이름으로 PVC 복원
cat <

크로스 네임스페이스 복원

다른 네임스페이스의 스냅샷에서 복원하려면 VolumeSnapshotContent를 직접 참조합니다:

# production 네임스페이스의 스냅샷 → staging으로 복원

# 1. VolumeSnapshotContent 이름 확인
kubectl get volumesnapshot mysql-snapshot-20260320 -n production 
  -o jsonpath='{.status.boundVolumeSnapshotContentName}'
# snapcontent-abc123

# 2. staging에 VolumeSnapshot 생성 (snapshotContentName 참조)
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-from-prod
  namespace: staging
spec:
  source:
    volumeSnapshotContentName: snapcontent-abc123

# 3. staging에서 PVC 복원
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data-staging
  namespace: staging
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: gp3-encrypted
  resources:
    requests:
      storage: 50Gi
  dataSource:
    name: mysql-snapshot-from-prod
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io

PVC 클론: 스냅샷 없이 볼륨 복제

CSI 드라이버가 지원하면 스냅샷 단계 없이 PVC-to-PVC 직접 클론도 가능합니다:

# 기존 PVC를 직접 클론 (스냅샷 생성 단계 생략)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data-clone
  namespace: production
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: gp3-encrypted
  resources:
    requests:
      storage: 50Gi
  dataSource:
    name: mysql-data            # 원본 PVC 이름
    kind: PersistentVolumeClaim  # VolumeSnapshot이 아닌 PVC

모니터링과 트러블슈팅

# 스냅샷 이벤트 확인
kubectl get events -n production --field-selector reason=ProvisioningFailed
kubectl get events -n production --field-selector involvedObject.kind=VolumeSnapshot

# CSI 드라이버 로그 확인
kubectl logs -n kube-system -l app=ebs-csi-controller -c csi-snapshotter

# 스냅샷 용량 모니터링 (Prometheus 메트릭)
# volumesnapshot_readytouse: 스냅샷 준비 상태
# volumesnapshot_restoresize_bytes: 복원 크기

# 일반적인 에러와 해결법
# 1. "snapshot class not found"
#    → VolumeSnapshotClass가 올바른 CSI 드라이버를 참조하는지 확인
kubectl get volumesnapshotclass

# 2. "failed to take snapshot: rpc error"
#    → CSI 드라이버가 스냅샷을 지원하는지 확인
kubectl get csidriver -o jsonpath='{range .items[*]}{.metadata.name}: snapshot={.spec.volumeLifecycleModes}{"n"}{end}'

# 3. "snapshot is bound to a non-existing content"
#    → VolumeSnapshotContent가 삭제됨, deletionPolicy: Retain 확인
kubectl get volumesnapshotcontent | grep -v READY

# 4. 복원 PVC가 Pending 상태
#    → storage 크기가 스냅샷의 restoreSize 이상인지 확인
kubectl get volumesnapshot -o jsonpath='{.status.restoreSize}'

핵심 정리

작업 리소스 핵심 설정
정책 정의 VolumeSnapshotClass driver, deletionPolicy
스냅샷 생성 VolumeSnapshot source.persistentVolumeClaimName
PVC 복원 PVC + dataSource dataSource.kind: VolumeSnapshot
자동화 CronJob kubectl + 보존 정책 스크립트
직접 클론 PVC + dataSource dataSource.kind: PersistentVolumeClaim

VolumeSnapshot은 K8s 스토리지 운영의 핵심 도구입니다. CronJob으로 자동 스냅샷을 생성하고 deletionPolicy: Retain으로 데이터 안전성을 확보하세요. StatefulSet 복구, 크로스 네임스페이스 마이그레이션, 개발 환경 데이터 복제 등 다양한 시나리오에 활용할 수 있습니다. K8s PV·PVC·StorageClass 심화K8s Velero 백업·복구 운영도 함께 참고하세요.

위로 스크롤