쿠버네티스를 몇 년 굴리다 보면 "이 클러스터가 통째로 날아가면 뭘로 되살리지"라는 질문을 한 번은 마주하게 된다. etcd 스냅샷만으로는 영속 볼륨(PV) 데이터가 돌아오지 않고, GitOps로 매니페스트를 복원해도 DB 파일까지 복구되는 것은 아니다. 백업 대상을 리소스와 볼륨으로 나눠서 함께 챙겨 주는 도구가 필요하고, 그 자리에 Velero가 있다.
결론부터 말하자면, Velero v1.16과 velero-plugin-for-aws v1.12를 MinIO S3 백엔드에 붙이면 네임스페이스 단위 백업과 다른 클러스터로의 복구를 명령 몇 개로 돌릴 수 있다. 볼륨은 CSI VolumeSnapshot 경로 또는 node-agent 파일시스템 백업 두 가지로 챙긴다. 복구 클러스터의 백업 저장소는 accessMode=ReadOnly로 걸어 원본을 보호한 뒤 리허설한다.
1. Velero가 백업하는 두 덩어리
Velero는 쿠버네티스 API 오브젝트와 영속 볼륨을 함께 백업, 복구하는 오픈소스 도구다. 백업을 실행하면 지정한 네임스페이스의 리소스를 쿠버네티스 API에서 긁어 와 S3 호환 버킷에 tar로 올리고, 볼륨은 스냅샷을 찍거나 파일을 통째로 복사한다. 복구는 그 반대로, 버킷의 백업을 읽어 대상 클러스터에 리소스를 다시 만들고 볼륨 데이터를 채운다.
백업 객체가 쌓이는 S3 버킷 위치를 BackupStorageLocation(BSL)이라 부른다. 스냅샷 메타데이터가 저장되는 위치는 VolumeSnapshotLocation(VSL)이다. 이 두 개가 Velero의 저장소 좌표다.
볼륨을 챙기는 방법은 두 갈래다. 하나는 CSI VolumeSnapshot으로, CSI 드라이버가 스토리지 시스템 레벨에서 찍는 스냅샷이다. v1.14부터 CSI 지원이 Velero 본체에 합쳐져서 별도 플러그인을 깔 필요가 없다. 다른 하나는 node-agent로, 각 노드에서 데몬셋으로 돌며 볼륨의 파일을 Kopia로 읽어 백업하는 방식이다. 스냅샷을 지원하지 않는 스토리지(로컬 디스크, NFS 등)에서 이 경로를 쓴다.
2. MinIO 버킷과 Velero 설치
백엔드는 MinIO로 잡는다. 클러스터 안에 MinIO가 minio.minio.svc:9000으로 떠 있다고 가정한다. 먼저 velero 버킷을 만들고 자격증명 파일을 준비한다.
$ mc alias set local http://minio.minio.svc:9000 minioadmin minioadmin $ mc mb local/velero Bucket created successfully `local/velero`. $ cat credentials-velero [default] aws_access_key_id=minioadmin aws_secret_access_key=minioadmin
설치는 Velero CLI로 한다. 여기서 플러그인 태그가 핵심이다. Velero v1.16과 짝이 맞는 velero-plugin-for-aws는 v1.12.x다. v1.11.x는 Velero v1.15용이라 그대로 쓰면 버전 불일치가 난다. 공식 호환표를 먼저 확인하고 태그를 맞춘다.
$ velero install \ --provider aws \ --plugins velero/velero-plugin-for-aws:v1.12.0 \ --bucket velero \ --secret-file ./credentials-velero \ --use-node-agent \ --features=EnableCSI \ --backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio.minio.svc:9000 \ --snapshot-location-config region=minio
플래그 세 개를 짚는다. --use-node-agent는 파일시스템 백업용 데몬셋을 올린다. --features=EnableCSI는 CSI 스냅샷 기능을 켠다. v1.16에서도 이 기능 플래그는 기본값이 아니라 설치 때 명시해야 한다. s3ForcePathStyle="true"는 MinIO처럼 path-style 주소를 쓰는 S3 호환 스토리지에 필요하다.
설치 직후 BSL 상태가 Available로 뜨는지 확인한다. 자격증명이나 엔드포인트가 틀리면 여기서 Unavailable로 남는다.
$ velero backup-location get NAME PROVIDER BUCKET/PREFIX PHASE LAST VALIDATED ACCESS MODE DEFAULT default aws velero Available 2026-11-07 ... ReadWrite true
3. CSI VolumeSnapshot으로 볼륨 챙기기
CSI 스냅샷을 쓰려면 드라이버별로 VolumeSnapshotClass를 하나 만들고 Velero가 그 클래스를 집어 쓰도록 라벨을 붙여야 한다. 라벨 키는 velero.io/csi-volumesnapshot-class이고 값은 "true"다. 이 라벨이 없으면 Velero는 어떤 스냅샷 클래스를 쓸지 몰라 스냅샷을 건너뛴다.
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: csi-hostpath-snapclass
labels:
velero.io/csi-volumesnapshot-class: "true"
driver: hostpath.csi.k8s.io
deletionPolicy: Retain
CSI 스냅샷에는 전제 조건이 있다. 쿠버네티스 1.20 이상, v1 레벨 스냅샷을 지원하는 CSI 드라이버, 그리고 external-snapshotter 컨트롤러가 떠 있어야 한다. 관리형 클러스터(EKS, AKS, GKE)는 CSI 드라이버와 스냅샷 컨트롤러가 애드온으로 제공되지만, 직접 깐 클러스터라면 snapshot-controller와 CRD를 따로 설치해야 한다. 이게 빠져 있으면 백업은 Completed로 끝나도 볼륨 데이터가 안 담긴다.
이제 app 네임스페이스를 백업한다. CSI 경로는 네임스페이스에 스냅샷 가능한 PVC가 있으면 자동으로 VolumeSnapshot을 만든다.
$ velero backup create app-csi-01 \ --include-namespaces app \ --snapshot-volumes \ --wait Backup request "app-csi-01" submitted successfully. ... Backup completed with status: Completed.
백업이 실제로 스냅샷을 찍었는지는 describe로 확인한다. CSI Volume Snapshots 항목에 개수가 잡혀야 데이터가 담긴 것이다.
$ velero backup describe app-csi-01 --details ... CSI Volume Snapshots: Snapshot Content Name: snapcontent-.... Storage Snapshot ID: .... Snapshot Size (bytes): ....
처음 돌려 보면 이 항목이 비어 있는 경우가 흔하다. 대개 VolumeSnapshotClass 라벨을 안 붙였거나 snapshot-controller가 없어서다. 백업 로그를 velero backup logs app-csi-01로 열어 보면 스냅샷을 건너뛴 이유가 그대로 찍힌다.
4. 스냅샷이 안 되는 볼륨은 node-agent로
로컬 PV나 NFS처럼 CSI 스냅샷을 지원하지 않는 볼륨은 node-agent의 파일시스템 백업(FSB)으로 챙긴다. 설치 때 --use-node-agent를 넣었으니 데몬셋은 이미 떠 있다. 파드별로 어떤 볼륨을 FSB로 백업할지 지정하는 방법이 두 가지다.
볼륨을 하나씩 지정하는 opt-in
파드에 backup.velero.io/backup-volumes 애너테이션을 붙여 백업할 볼륨 이름을 적는다. 명시한 볼륨만 FSB로 담기므로 대상이 분명할 때 안전하다.
$ kubectl -n app annotate pod/postgres-0 \ backup.velero.io/backup-volumes=data
전부 담고 일부만 빼는 opt-out
백업할 때 --default-volumes-to-fs-backup을 주면 파드의 모든 볼륨을 FSB로 백업한다. 서비스 어카운트 토큰, Secret, ConfigMap, hostPath는 자동으로 제외된다. 빼고 싶은 볼륨만 backup.velero.io/backup-volumes-excludes로 지정한다.
$ velero backup create app-fsb-01 \ --include-namespaces app \ --default-volumes-to-fs-backup \ --wait
FSB 업로더는 v1.16 신규 설치에서 Kopia가 기본이다. 백업 결과에 Pod Volume Backups 항목이 잡히면 파일시스템 백업이 들어간 것이다. CSI 스냅샷과 FSB는 한 백업에 섞어 써도 된다. 스냅샷 가능한 볼륨은 CSI로, 나머지는 애너테이션으로 FSB를 걸면 된다.
5. 매일 자동으로 찍는 스케줄
재해복구는 백업이 꾸준히 쌓여 있어야 의미가 있다. Velero의 schedule은 cron 식으로 백업을 반복 생성한다. 매일 새벽 2시에 app 네임스페이스를 백업하고 보관 기간을 7일로 잡는 예다.
$ velero schedule create app-daily \ --schedule="0 2 * * *" \ --include-namespaces app \ --default-volumes-to-fs-backup \ --ttl 168h0m0s $ velero schedule get NAME STATUS CREATED SCHEDULE BACKUP TTL LAST BACKUP SELECTOR app-daily Enabled 2026-11-07 ... 0 2 * * * 168h0m0s n/a <none>
--ttl 168h0m0s는 백업을 168시간(7일) 뒤 자동 삭제한다. TTL을 안 주면 백업이 무한정 쌓여 버킷 용량을 먹는다. 스케줄이 만든 백업은 app-daily-20261107020000처럼 타임스탬프가 붙어 생성된다.
6. 다른 클러스터에서 복구하고 원본 지키기
복구 쪽 클러스터에도 Velero를 깔되, 같은 MinIO 버킷을 바라보게 한다. 설치 명령은 2절과 동일하되 플러그인 태그도 똑같이 v1.12.0으로 맞춘다. 여기서 반드시 챙길 것이 하나 있다. 복구 클러스터의 BSL을 읽기 전용으로 걸어야 복구 테스트 도중 원본 백업을 덮어쓰거나 지우는 사고를 막는다.
읽기 전용은 BSL 최상위 필드 spec.accessMode=ReadOnly로 설정한다. config 맵 안에 readOnly 같은 키는 없다. 설치 때 CLI 플래그로 바로 걸 수 있다.
$ velero backup-location create default \ --provider aws \ --bucket velero \ --access-mode=ReadOnly \ --config region=minio,s3ForcePathStyle="true",s3Url=http://minio.minio.svc:9000
이미 만들어진 BSL이라면 kubectl patch로 바꾼다.
$ kubectl -n velero patch backupstoragelocation default \
--type merge -p '{"spec":{"accessMode":"ReadOnly"}}'
$ velero backup-location get
NAME PROVIDER BUCKET/PREFIX PHASE ACCESS MODE DEFAULT
default aws velero Available ReadOnly true
ACCESS MODE가 ReadOnly로 바뀐 것을 꼭 눈으로 확인한다. 이 상태에서는 새 백업 생성과 백업 삭제가 막히고 복구만 가능하다. 공유 버킷으로 복구 리허설을 할 때 이 한 줄이 원본을 지켜 준다.
버킷의 백업 목록이 복구 클러스터에서 보이면 복구를 건다. 다른 네임스페이스로 받고 싶으면 --namespace-mappings로 매핑한다.
$ velero backup get NAME STATUS CREATED EXPIRES app-daily-20261107020000 Completed 2026-11-07 ... 6d app-csi-01 Completed 2026-11-07 ... 29d $ velero restore create --from-backup app-csi-01 \ --namespace-mappings app:app-dr \ --wait Restore request "app-csi-01-20261107..." submitted successfully. ... Restore completed with status: Completed.
복구 결과는 velero restore describe로 본다. 볼륨까지 돌아왔는지는 대상 네임스페이스의 PVC가 Bound인지, 파드가 데이터를 읽는지로 확인한다.
$ kubectl -n app-dr get pvc,pod NAME STATUS VOLUME CAPACITY persistentvolumeclaim/data Bound pvc-.... 1Gi NAME READY STATUS RESTARTS AGE pod/postgres-0 1/1 Running 0 40s
복구 클러스터의 CSI 드라이버가 원본과 같은 프로비저너여야 CSI 스냅샷이 복원된다. 드라이버가 다르면 CSI 백업은 못 받고 FSB 백업만 복원된다. 이기종 클러스터로 넘길 계획이면 처음부터 node-agent FSB로 백업해 두는 편이 호환성 문제가 적다.
7. 리허설을 운영 루틴으로
DR 리허설은 한 번 성공했다고 끝이 아니다. 복구가 되는지 주기적으로 검증해야 백업이 썩지 않았다는 확신이 생긴다. 격리된 복구 클러스터(또는 -dr 네임스페이스)에 스케줄 백업의 최신본을 정기적으로 복구해 보고, 복구된 파드가 헬스체크를 통과하는지까지 확인하는 작업을 월 단위로 돌려 두면 좋다.
남은 숙제가 하나 있다. 복구 소요 시간(RTO)과 데이터 손실 허용 폭(RPO)을 숫자로 정해 두는 일이다. 스케줄 주기가 곧 RPO 상한이고, 복구 실측 시간이 RTO다. 리허설을 돌릴 때마다 복구 시간을 재서 기록하면, 스케줄 주기가 지금 요구 수준에 맞는지 판단할 근거가 쌓인다.