IT/Kubernets

Velero restic에서 Kopia로 옮기는 법

gfrog 2026. 6. 27. 12:54
SMALL

 

쓰던 Velero 백업 파이프라인이 restic 기반이면 슬슬 갈아탈 때가 됐다. v1.15에서 restic이 deprecated로 마킹된 게 작년 말이고, v1.16에서는 새로운 file system backup의 기본 업로더가 Kopia다. 거기다 PV 백업 흐름 자체도 CSI snapshot data movement 쪽으로 무게추가 옮겨가는 중이라, 운영 환경을 그대로 두면 1~2년 안에 업그레이드 경로에서 발이 묶일 수 있다.

이 글은 EKS 클러스터에서 돌아가는 restic 기반 Velero를 Kopia + CSI data mover 조합으로 옮기는 실전 절차다. 우리 팀이 노드 50대짜리 프로덕션 클러스터에서 한 달에 걸쳐 진행한 작업을 정리한 거라, 가능한 단계마다 함정도 같이 적었다.

왜 지금 옮겨야 하는가

restic 자체가 나쁜 도구는 아니다. 잘 동작하고, 우리도 2년 가까이 별 사고 없이 썼다. 그런데 Velero가 손을 떼고 있는 게 명확하다. 1.15부터 새 설치는 Kopia가 기본이고, 1.16에서는 restic uploader가 새 백업 생성에서 disabled로 바뀌고 있다. Velero 로드맵상 1.18쯤에서 restic path가 완전히 제거될 전망인데, 이건 곧 "복구는 가능한데 새 백업은 못 만든다"는 시점이 온다는 뜻이다.

그래서 우선순위는 두 가지다.

첫째, Kopia 기반 file system backup으로 옮긴다. 둘째, EBS CSI snapshot이 가능한 PV는 가능한 한 CSI snapshot data movement로 백업 경로를 바꾼다. 둘은 별개 작업처럼 보이지만 실제로는 같은 v1.16 설정 안에서 같이 진행하는 게 자연스럽다.

사전 준비

기존 환경늤 상태부터 정리한다.

# 현재 Velero 버전 확인
velero version

# restic 데몬셋이 떠 있는지 확인
kubectl -n velero get ds restic

# 진행 중인 백업/스케줄
velero backup get
velero schedule get

# Backup Storage Location의 prefix 구조 파악
velero backup-location get -o yaml

restic은 BSL 안에 restic/ prefix로 데이터를 쌓는다. Kopia로 옮긴 뒤에도 기존 restic 백업의 복구는 한동안 유지해야 하므로, BSL을 새로 만드는 게 아니라 같은 버킷 안에서 prefix 분리만 잘하면 된다. 새 백업은 kopia/ prefix로 들어간다.

S3 권한 점검은 잊지 말 것. IRSA로 묶어 둔 Velero ServiceAccount의 IAM policy에 s3:GetObject, s3:PutObject, s3:DeleteObject, s3:ListBucket은 당연히 있을 텐데, CSI snapshot data movement까지 쓰려면 ec2:CreateSnapshot, ec2:DescribeSnapshots, ec2:DeleteSnapshot 권한이 추가로 필요하다. EBS CSI Driver 쪽 권한이 충분한지 같이 확인한다.

단계 1: Velero를 1.16으로 업그레이드

업그레이드 자체는 Helm이면 그냥 차트 버전만 올리면 된다. 다만 values에 손볼 게 좀 있다.

# values.yaml
image:
  tag: v1.16.0

deployNodeAgent: true   # 과거 deployRestic 자리를 대체

nodeAgent:
  podVolumePath: /var/lib/kubelet/pods
  privileged: false

initContainers:
  - name: velero-plugin-for-aws
    image: velero/velero-plugin-for-aws:v1.12.0
    volumeMounts:
      - mountPath: /target
        name: plugins

configuration:
  uploaderType: kopia              # 핵심: 기본 업로더를 명시
  defaultVolumesToFsBackup: false  # opt-in 방식 권장
  features: EnableCSI              # CSI snapshot 사용
  defaultSnapshotMoveData: false   # 일단 false로 시작

deployRestic는 옛 키워드이고, 1.16에서는 deployNodeAgent가 같은 데몬셋을 띄운다. 이름이 바뀌었을 뿐 컨테이너 안에서는 Kopia와 restic 둘 다 들어 있어서, prefix와 메타데이터에 따라 알맞은 쪽이 실행된다. 즉, 마이그레이션 기간에는 두 종류의 백업이 공존할 수 있다.

업그레이드 후 첫 확인:

kubectl -n velero get pods
# velero-... Running
# node-agent-... Running (각 노드마다)

# uploader 확인
kubectl -n velero exec deploy/velero -- velero version
# Server Version: v1.16.0

여기서 node-agent가 일부 노드에서 안 뜨면 십중팔구 hostPath 마운트 문제다. EKS의 경우 /var/lib/kubelet/pods가 디폴트인데, Bottlerocket 같은 AMI를 쓰면 경로가 다를 수 있다. kubectl describe node에서 kubeletRoot 비슷한 키를 확인하면 된다.

단계 2: 새 백업을 Kopia로 만들기

기존 스케줄을 갑자기 죽이면 안 된다. 병행 운영부터 한다.

# 새 스케줄을 별도로 생성 (Kopia 기반)
velero schedule create daily-kopia \
  --schedule="0 17 * * *" \
  --include-namespaces=production,staging \
  --default-volumes-to-fs-backup=false \
  --snapshot-volumes=true \
  --ttl 720h

--default-volumes-to-fs-backup=false로 두면 PV는 기본적으로 CSI snapshot 경로로 간다. file system backup이 필요한 PV(EFS, NFS 등)만 pod annotation으로 opt-in한다.

# pod metadata
annotations:
  backup.velero.io/backup-volumes: "logs,uploads"

이렇게 하면 EBS PV들은 CSI snapshot, 명시한 볼륨은 Kopia file system backup으로 갈라진다. 첫 백업은 일부러 작은 네임스페이스 하나만 잡아서 돌려본다.

velero backup create kopia-smoke-1 \
  --include-namespaces=test-sandbox \
  --default-volumes-to-fs-backup=true

velero backup describe kopia-smoke-1 --details

Backup Item Operations에 PodVolumeBackup이 Kopia로 처리됐다는 게 보이면 성공이다. 우리 팀에서는 첫 시도에서 backup이 PartiallyFailed로 떨어졌는데, 원인은 node-agent의 메모리 limit이었다. restic은 200~300Mi로도 잘 돌았는데 Kopia는 PV가 크면 1Gi 가까이 먹는다. limit을 2Gi로 올리고 다시 돌리니 깔끔하게 끝났다.

단계 3: CSI snapshot data movement 켜기

PV 백업을 진짼 EBS snapshot으로 뺽u�고, 그걔 S3로 올기는 흐름이다. snapshot이 같은 계정/리전에 갇혀 있으면 DR 관점에서 의미가 적기 때문에 data movement까지 쓰는 게 맞다.

VolumeSnapshotClass에 Velero 라벨이 붙어 있어야 한다.

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: ebs-csi-velero
  labels:
    velero.io/csi-volumesnapshot-class: "true"
driver: ebs.csi.aws.com
deletionPolicy: Retain
parameters:
  tagSpecification_1: "Name=velero-{{ .VolumeSnapshotContentName }}"

deletionPolicy: Retain이 중요하다. 처음에 별생각 없이 Delete로 두고 운영하다가, backup TTL이 만료될 때 snapshot�� 같이 날아가서 백업 자체가 문횀화되는 사고를 한 번 겪안다. Velero가 data movement 로 S3에 옮긴 뒤에는 EBS snapshot은 정리해도 되째拌, 그건 Velero가 자기 라이프사이큼로 관리하게 두는 것 안전하다.

이된 백업에 data movement 를 활성화한다.

velero backup create csi-mover-1 \
  --include-namespaces=production-api \
  --snapshot-move-data=true

snapshot-move-data 옵션을 켜면 EBS snapshot을 뜬 뒤 Velero가 그 snapshot에서 데이터를 읽어 Kopia repository로 S3에 저장한다. 진행 상황은 DataUpload 리소스로 확인할 수 있다.

kubectl -n velero get datauploads.velero.io
# NAME                  STATUS      STARTED   ...
# csi-mover-1-abcde     Completed   ...

여기서 자주 막히는 곳이 두 군데다. 하나는 VolumeSnapshotClass 라벨 누락(위에서 처리). 다른 하나는 dataMover pod이 뜨는 노드에서 PVC 마운트가 안 되는 케이스다. Velero는 dataMover pod을 임시로 띄워서 snapshot을 마운트하는데, 이 pod이 가용 영역 매칭이 안 되면 멈춘다. EBS는 같은 AZ에서만 마운트되므로, EKS 클러스터의 AZ가 여러 개인 경우 dataMover pod의 nodeSelector나 affinity를 조정하지 않아도 Velero가 알아서 해주는 게 정상인데, 특이한 노드 taint를 걸어둔 환경에서는 nodeAgent.podConfig.tolerations를 손봐야 한다.

단계 4: 옛 restic 백업 정리

새 파이프라인이 한 주 정도 안정적으로 돌아가면 옛 스케줄을 제거한다.

velero schedule delete daily-restic

이미 만들어진 restic 백업은 손대지 말 것. Velero 1.16은 prefix를 보고 어느 uploader로 복구해야 할지 자동 판단한다. 그래서 적어도 TTL이 끝날 때까지는 그대로 두고, 마지막 restic 백업의 TTL이 지난 뒤에 BSL 안의 restic/ prefix를 정리하면 된다. 미리 지우면 만약의 복구가 막힌다.

복구 테스트를 한 번씩 돌려보는 것도 권장한다.

# Kopia 백업으로 복구
velero restore create --from-backup csi-mover-1 \
  --namespace-mappings production-api:production-api-restore

# 옛 restic 백업도 한 번 더 (남아 있는 동안)
velero restore create --from-backup daily-restic-20260601-...

두 경로 모두 동작하는 게 확인되면 일단 마이그레이션은 끝났다고 봐도 된다.

운영 중 부딪힌 자잘한 것들

Kopia repository는 처음 한 번 init이 일어나는데, 이 단계가 느리거나 실패하면 첫 백업이 길게 멈춰 보인다. kubectl -n velero logs deploy/velero | grep repository로 확인하면 된다.

restic 시절에는 prune이 별도 명령이었는데, Kopia는 백그라운드에서 자체 maintenance가 돈다. 처음 몇 일은 S3 비용이 좀 출렁일 수 있다. 우리는 마이그레이션 첫 주에 일일 S3 PUT 요청靴 평소 대비 1.6배쯤 올라갔다가 안정화됐다.

진짜로 가끔 일어나는 일인데, node-agent가 OOMKilled로 주으면 PodVolumeBackup이 멈춘 상태로 남는다. backup describe 상에는 InProgress로 영원히 떠 있어서 헷갈린다. velero backup logs <name>을 봐서 timeout이나 OOM 흔적이 보이면 그 백업은 그냥 cancel하고 limit 올린 뒤 다시 돌리는 게 빠르다.

마지막으로, 우리 환경에서 한 가지 더 발견한 건 EKS 1.30 이상에서 volumeAttachment 정리가 늦어지는 경우다. data mover pod이 끝난 뒤에 PVC가 즉시 해제 안 되면서 다음 백업이 충돌하는 식이었는데, 이건 EBS CSI Driver 쪽 이슈로 보였고 컨트롤러 버전을 v1.36 이상으로 올려서 정리됐다.

정리

요약하면, 결국 두 줄이다. 새 백업 uploader는 Kopia, EBS PV 백업은 가능하면 CSI snapshot data movement. 둘 다 v1.16에 들어 있고, 옛 restic 백업을 죽이지 않은 상태에서 점진적으로 옮길 수 있다.

restic 완전 제거 시점이 언제일지는 Velero 릴리스 노트를 따로 챙겨보면 좋다. 우리는 분기에 한 번씩 릴리스 노트를 훑고, deprecated 표시된 기능에 한 분기 정도 유예를 두고 옮기는 식으로 운영 중이다.

BIG