IT/DB 운영

Velero 1.15 데이터 무버 마이그레이션 삽질기

gfrog 2026. 5. 7. 03:12
반응형

지난주 새벽 3시, 알람으로 깨서 백업 잡이 또 깨진 걸 확인했다. PVC 30개 짜리 워크로드 백업이 두 시간 째 매달려 있었고, node-agent 데몬셋의 메모리는 8Gi를 찍고 OOM. 이게 벌써 이번 분기 들어 세 번째다. 1.14에서 1.15로 올린 다음부터 백업 패턴이 이상해졌고, 솔직히 말하면 우리 팀은 한 달 가까이 이 마이그레이션을 우습게 봤다.

원인은 단순하지 않았다. Velero 1.15에서 데이터 업로드 액션이 node-agent에서 떨어져 나와 DataUpload 단위 마이크로서비스 파드로 분리됐는데, 그 변화가 우리 클러스터 토폴로지와 안 맞았다. 이 글은 그 한 달간의 삽질을 정리한 노트다.

처음에 뭐가 바뀐 건지 제대로 안 봤다

릴리즈 노트를 한 번은 읽었다. "data movement is now run in dedicated pods per DataUpload"라는 문장도 봤고, 끄덕끄덕 했다. 근데 그게 우리 운영에 어떤 영향을 미치는지는 마이그레이션 PR을 머지하기 전까지 진지하게 따져보지 않았다.

1.14까지는 백업 시 CSI 스냅샷 → node-agent 파드가 Kopia로 오브젝트 스토리지에 업로드, 이게 한 호스트에서 직렬로 일어났다. node-agent 한 개가 동시에 여러 PV를 처리해도 같은 프로세스 안이라 메모리 사용량이 어느 정도 예측 가능했다.

1.15부터는 DataUpload CR이 만들어질 때마다 별도 파드가 뜬다. 각 파드는 자기만의 Kopia 워커를 들고 있고, 청크 캐시를 따로 잡는다. 우리는 이걸 "효율적이고 격리됐네" 정도로 받아들였는데, 실제로는 동시 백업 PV 수가 곧 동시에 뜨는 데이터 무버 파드 수가 된다. 야간 백업 윈도우에 PVC 200개가 한꺼번에 큐잉되는 우리 클러스터에선 이게 곧 200개 파드를 의미했다.

스케줄러는 이걸 다 띄우려고 노드를 막 깨우기 시작했다. Karpenter가 새벽 2시에 노드 8대를 추가로 프로비저닝하고 있었다. 그리고 백업이 끝나면? 파드는 사라지지만 우리는 클러스터 자동 스케일 다운까지 30분을 더 기다려야 했다. 비용 계산기 돌려보니 이번 달 EC2 청구액이 12% 늘어났다. 백업만 바꿨는데.

동시성 제한을 잘못 잡았다

Velero에는 --data-mover-prepare-timeout이랑 node-agent-config ConfigMap의 loadConcurrency 설정이 있다. 처음엔 globalConfig만 만져도 된다고 생각했다.

# 우리가 처음에 적용한 설정 (잘못됨)
apiVersion: v1
kind: ConfigMap
metadata:
  name: node-agent-config
  namespace: velero
data:
  node-agent-config.json: |
    {
      "loadConcurrency": {
        "globalConfig": 4
      }
    }

문제는 1.15에서 이 loadConcurrency는 node-agent의 동시 처리량이 아니라, 노드별로 동시에 띄울 수 있는 데이터 무버 파드의 수를 제어한다는 점이다. 노드가 30대면 이론상 120개 파드까지 동시에 뜬다. globalConfig 값을 줄여도 별 의미가 없었던 이유다.

우리는 결국 backup-repository pod 수와 노드 capacity의 곱으로 동시성을 잡아야 한다는 걸 알았다. 노드 풀을 백업 전용으로 분리하는 게 사실상 강제됐다.

# 백업 전용 노드 풀 + nodeSelector
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: velero-datamover
spec:
  template:
    metadata:
      labels:
        workload: velero-datamover
    spec:
      taints:
        - key: dedicated
          value: velero
          effect: NoSchedule
      requirements:
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m"]
        - key: karpenter.k8s.aws/instance-cpu
          operator: In
          values: ["4", "8"]
  limits:
    cpu: 64

여기에 Velero 쪽에는 podConfig를 추가했다.

data:
  node-agent-config.json: |
    {
      "loadConcurrency": {
        "globalConfig": 2,
        "perNodeConfig": [
          {
            "nodeSelector": {"matchLabels": {"workload": "velero-datamover"}},
            "number": 4
          }
        ]
      },
      "podResources": {
        "cpuRequest": "250m",
        "cpuLimit": "1000m",
        "memoryRequest": "512Mi",
        "memoryLimit": "2Gi"
      }
    }

이렇게 하니 백업 윈도우 동안에만 카펜터가 4~6대 노드를 띄우고, 백업 끝나면 5분 안에 사라졌다. 비용은 마이그레이션 전 수준으로 거의 돌아왔다.

정작 문제는 PVC GC 누락이었다

설정 다 잡고 한 주 정도 안정적으로 돌았다. 그러다 또 새벽 3시에 알람.

이번엔 백업 자체는 다 성공했는데 클러스터에 alone-standing PVC가 80개 정도 쌓여 있었다. velero-datamover-XXXXX 형태 이름. 데이터 무버 파드가 백업 중 만든 임시 PVC가 정상적으로 GC가 안 되고 있었다.

원인을 파보니 우리 스토리지 클래스가 volumeBindingMode: WaitForFirstConsumer로 잡혀 있는데, 데이터 무버 파드가 OOM으로 죽으면서 finalizer를 못 떼고 있었다. PVC에는 velero.io/snapshot-data-movement finalizer가 매달려 있었고, controller가 그걸 reconcile하지 못한 채로 남아 있었다.

문제는 node-agent 파드가 OOM 직후 빠르게 재시작되면서, 이전 DataUpload CR과 PVC 간 매핑 정보가 상태에 따라 어긋났다는 거다. 1.15의 mover 파드는 stateless인데, finalizer 정리는 node-agent에서 하는 비대칭 구조가 함정이었다.

수동 클린업 스크립트가 한동안 cron으로 돌았다.

#!/bin/bash
# 임시 클린업 — 영구 해결책 아님
kubectl get pvc -n velero -o json | \
  jq -r '.items[]
    | select(.metadata.name | startswith("velero-datamover-"))
    | select(.status.phase=="Pending" or (.metadata.deletionTimestamp != null))
    | .metadata.name' | \
  while read pvc; do
    kubectl patch pvc "$pvc" -n velero --type=json \
      -p='[{"op":"remove","path":"/metadata/finalizers"}]' 2>/dev/null
    kubectl delete pvc "$pvc" -n velero --grace-period=0 --force 2>/dev/null
  done

이걸 매시간 돌리는 건 누가 봐도 정상이 아니다. 결국 이 이슈는 1.15.4에서 패치됐다 (DataUpload reconciler 쪽에서 mover 파드 종료 시 finalizer를 명시적으로 정리). 1.15.4 올리고 클린업 cron은 지웠다. 단, 이 수정은 릴리즈 노트에서 "internal stability fix" 한 줄로만 언급돼서, 못 보고 지나가기 쉽다. 1.15 도입한 팀이라면 1.15.4 이상으로 올리는 걸 권장한다.

1.16으로 또 갈까 말까

1.16이 나왔고, Windows 워크로드도 데이터 무버를 지원한다고 한다. 우리 환경엔 윈도우 노드가 없으니 직접 이득은 없는데, 1.16에서 mover 파드 자체가 좀 더 가벼워진 것 같다는 얘기를 KubeCon EU 2026 BoF에서 들었다. 메모리 풋프린트가 평균 30% 줄었다는 측정치를 누가 공유해줬는데, 우리 환경에서 검증한 건 아니다.

올릴까 말까 한 달 정도 미루고 있다. 백업 시스템은 한 번 안정되면 바로 안 만지는 게 좋다는 걸 이번 분기에 확실히 배웠다. 다음에 올린다면 staging에서 최소 2주 동안 야간 백업 패턴을 그대로 재현해보고, 그제서야 prod로 갈 거다.

혹시 1.16으로 이미 마이그레이션한 분 있으면 어떻게 됐는지 댓글로 알려주시면 감사하겠다. 다음 분기 OKR에 또 이거 올릴지 말지 고민 중이다.

반응형