TT Lab
블로그

한국어

시작하기
블로그

블로그

KubeVirt 라이브 마이그레이션 실측 2: 바쁜 메모리, 노드 비우기, 동시 한도

상태: 실측 (2026-10-31). 1편과 같은 중첩 클러스터(TT Lab 실습 VM 세 대, KubeVirt v1.9.0)에서 잰 값입니다. 정책 시험은 조건마다 1–3번, drain 은 장면마다 한 번, 동시 한도는 한도마다 한 번입니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 원인을 확인하지 못한 해석은 「추정」 으로 표시합니다.

목차

1. 메모리를 계속 쓰는 VM

1편의 VM 은 아무 일도 하지 않았습니다. 실제 VM 은 메모리를 씁니다. 사전 복사는 「보내는 사이 바뀐 페이지를 다시 보낸다」 는 방식이라, 게스트가 고치는 속도가 보내는 속도와 비슷하면 끝나지 않습니다. 이것을 직접 만들어 보았습니다.

시험 부하 정책 결과 걸린 시간 게스트 멈춤 서비스 끊김(새 연결 / 유지 연결)
light-1 128MB 없음 Failed Live migration has been aborted 302초 없음(원본에서 계속 돎)
heavy-1 512MB 없음 Failed 301초 없음
heavy-2 512MB bandwidthPerMigration: 32Mi Failed 301초 없음
heavy-3 512MB allowAutoConverge: true Succeeded 33초 566ms 2807ms / 2805ms, 끊김
uffd-3 512MB allowAutoConverge: true Succeeded 36초 547ms 1604ms / 1603ms, 끊김
heavy-5 512MB 32Mi + autoConverge Succeeded 43초 433ms 4287ms / 4384ms, 끊김
heavy-4 512MB allowPostCopy: true Failed Postcopy is not supported: Userfaultfd not available: Operation not permitted 2초 없음
heavy-6 512MB 32Mi + postCopy Failed(같은 이유) 1초 없음
uffd-1 512MB allowPostCopy: true, 노드 vm.unprivileged_userfaultfd=1 Succeeded(PostCopy) 303초 미실측 2204ms / 3006ms, 끊김
uffd-4 512MB 같음 Succeeded(PostCopy) 304초 미실측 6214ms / 6670ms, 끊김
uffd-2 512MB 32Mi + postCopy, 노드 설정 켬 Succeeded(PostCopy) 306초 미실측 6211ms / 6213ms, 끊김
메모리를 계속 쓰는 VM: 정책별 결과 1Gi 게스트에 512MB 쓰기 부하, 결과가 날 때까지(실측) 기본(정책 없음) 실패 · 301초 대역폭 32Mi 실패 · 301초 autoConverge 성공 3/3 · 33–43초 postCopy, 노드 설정 없음 실패 2/2 · 1–2초 postCopy, 노드 설정 켬 성공 3/3 · 303–306초 0초 100초 200초 300초 왜 기본값으로는 끝나지 않나 게스트가 더럽히는 속도 74–92MiB/s ≈ 보내는 속도 → 매 반복 같은 양이 다시 남아 멈출 순간이 오지 않는다
그림: 메모리 쓰기 부하에서 정책별 결과와 걸린 시간

1.1 왜 기본값으로는 끝나지 않나

진행 중인 libvirt 작업 정보를 5초마다 읽어 보니, 수렴하지 못한 실행들은 300초 동안 사전 복사를 184–244번 되풀이했습니다. 남은 양은 15–90MiB 사이를 오갔고, 이것을 보내는 데 걸릴 예상 멈춤은 1,782–1,913ms 였습니다. 목표(300ms 에서 시작해 최대 900ms)에 끝내 닿지 못했습니다. 게스트가 더럽히는 속도는 초당 약 19,000–23,000쪽(74–92MiB/s)으로, 보내는 속도와 거의 같았습니다. 매 반복 같은 양이 다시 남는 것입니다.

128MB 만 계속 써도 실패했습니다. 그동안 게스트는 원본에서 멀쩡히 돌았기 때문에 서비스 끊김은 없었습니다. 대신 VM 이 옮겨지지 않으니 그 노드를 비울 수 없습니다(2절).

1.2 정책별로 정리하면

정책 512MB 부하에서 대가
없음(기본) 수렴하지 못하고 약 300초 뒤 포기 노드를 비울 수 없다
대역폭 제한 32Mi 마찬가지로 포기 제한을 걸었는데 실제 전송은 제한을 넘었다(아래)
autoConverge 33–43초 만에 성공(3/3), 멈춤 433–566ms 게스트 CPU 를 점점 늦춰 더럽히는 속도를 떨어뜨린다. 게스트 처리량이 얼마나 떨어지는지는 재지 않았다(미실측)
postCopy 성공(3/3)이지만 사전 복사 제한 시간을 다 쓴 뒤에야 PostCopy 로 넘어감. 서비스 끊김 2.2–6.7초 노드에 vm.unprivileged_userfaultfd=1 필요. 사후 복사 중 원본이나 대상이 죽으면 게스트를 잃는다(문서 기준)

autoConverge 는 이름 그대로 「수렴하게 만드는」 장치입니다. 진행이 막히면 QEMU 가 게스트 vCPU 를 조금씩 붙잡아 더럽히는 속도를 낮춥니다. 이번 부하에서는 이것 하나로 결과가 실패에서 30초대 성공으로 바뀌었습니다.

postCopy 는 반대 방향입니다. 일단 게스트를 대상에서 돌리고, 아직 오지 않은 페이지는 게스트가 건드리는 순간 원본에서 끌어옵니다. 리눅스의 userfaultfd 로 그 순간을 잡기 때문에 노드 커널이 허락해야 하고, 우분투의 기본값은 0 이었습니다(중첩 워커도 0). 켜면 성공했지만, KubeVirt 는 사전 복사 제한 시간을 다 쓴 뒤에야 PostCopy 로 넘겼습니다. 그리고 사후 복사 동안 게스트의 메모리가 두 노드에 나뉘어 있으므로 어느 쪽이든 죽으면 게스트를 잃습니다. 바쁜 VM 이 있는 환경이라면 autoConverge 부터 켜 보는 것이 이 실측에서 나온 순서입니다.

1.3 대역폭 제한이 지켜지지 않았다

bandwidthPerMigration: 32Mi 를 걸었을 때 설정은 모든 층에 들어가 있었습니다. VMI 의 적용 설정에 32Mi, QEMU 의 max-bandwidth: 33554432, virsh migrate-getspeed 32 가 모두 찍혔습니다. 그런데 QEMU info migrate 의 처리량은 630.76Mbps(약 75MiB/s)였고, 10초 동안 처리한 양도 17.316GiB 에서 18.040GiB 로 약 73.5MiB/s 였습니다. 전송은 multifd 8채널로 나갔습니다. 제한이 없을 때(86–90MiB/s)와 거의 같습니다.

multifd 와 속도 제한의 관계 때문인지, KubeVirt 쪽 설정 때문인지는 multifd 를 끈 대조 시험을 해야 가를 수 있습니다(미실측). 그래서 지금은 「제한을 걸면 회선이 보호된다」 를 믿지 않고, 실제 전송량을 virsh domjobinfo 로 확인하는 것을 권합니다. 참고로 사용자 가이드에 적힌 기본 대역폭 64MiB/s 는 옛값이고 v1.9.0 의 실제 기본값은 무제한(0)이었습니다. 문서를 고치는 PR 은 이미 열려 있습니다.

1.4 정책을 거는 법

MigrationPolicy 는 네임스페이스나 VMI 라벨로 대상을 고릅니다. 바쁜 VM 이 모인 네임스페이스에 autoConverge 만 켜는 예입니다.

apiVersion: migrations.kubevirt.io/v1alpha1
kind: MigrationPolicy
metadata:
  name: busy-vms-auto-converge
spec:
  allowAutoConverge: true
  selectors:
    namespaceSelector:
      kubernetes.io/metadata.name: busy-vms

적용됐는지는 마이그레이션 뒤 VMI 의 status.migrationState.migrationConfiguration 과 status.migrationState.migrationPolicyName 으로 확인합니다.

2. 노드 비우기(drain)

라이브 마이그레이션을 가장 많이 쓰는 때는 노드를 비울 때입니다. VM 에 evictionStrategy: LiveMigrate 를 두면 kubectl drain 이 VM 을 끄지 않고 옮깁니다. 이 경로를 네 장면으로 시험했습니다.

노드 비우기(drain)가 VM 을 옮기는 길 KubeVirt v1.9.0, evictionStrategy: LiveMigrate(실측) kubectl drain 파드 축출 요청, 5초마다 다시 승인 웹훅이 축출을 거절 PDB 가 아니다. VMI 에 축출 표시를 남김 축출 마이그레이션 생성 동시 한도(노드당 2)를 따른다 성공 원본 파드가 사라져 다음에 통과 실패하면 다시, 또 다시 수렴 못 함: 약 5분마다 포기 받는 노드 메모리 부족: 5분 Pending 뒤 실패 옮길 수 없는 VM: 끝없이 거절 실측: 한 대 22초, 다른 한 대 13초 되돌려도(uncordon) 축출 표시가 남는다 status.evacuationNodeName 이 그대로라 마이그레이션이 계속 생기고, 결국 VM 이 되돌린 노드를 떠났다 virtctl evacuate-cancel (v1.9.1 이상 virtctl)
그림: drain 이 VM 을 옮기는 길: 축출 요청, 승인 웹훅의 거절과 축출 마이그레이션, 성공과 실패, 되돌린 뒤에도 남는 축출 표시
장면 무엇을 결과
A 512MB 부하가 걸린 VM(LiveMigrate)이 있는 노드를 drain(제한 600초) drain 실패(600.9초). 웹훅이 Evacuation in progress: Eviction triggered evacuation of VMI … 로 거절하며 축출 마이그레이션을 만들지만, 수렴하지 못해 약 5분마다 포기하고 다시 만든다
B 디스크가 RWO 인데 evictionStrategy: LiveMigrate 인 VM 이 있는 노드를 drain(제한 120초) drain 실패(120.8초). VMI … is configured with an eviction strategy but is not live-migratable 로 계속 거절
C A·B 뒤 노드를 다시 스케줄 가능으로(uncordon) 축출이 멈추지 않았다. 되돌린 뒤에도 축출 마이그레이션이 두 번 더 생겼고, 부하를 끄자 마지막 것이 성공해 VM 이 되돌린 노드를 떠났다
D 부하 끈 VM 셋(LiveMigrate)과 한 대(evictionStrategy None)가 있는 노드를 drain(제한 400초) 두 대는 22초·13초 만에 옮겨졌다. None 인 VM 은 7.4초에 꺼지고 17.9초에 다른 노드에서 새 VMI 로 떴다(재부팅). 나머지 한 대는 받는 노드의 메모리 요청이 98% 라 Insufficient memory, 5분 뒤 실패하고 다시 시도하다 drain 400초 시간 초과

2.1 PDB 가 아니라 웹훅이다

drain 중에 kubectl get pdb -A 는 No resources found 였습니다. 사용자 가이드의 노드 유지보수 문서는 「VMI 마다 PodDisruptionBudget 을 만든다」 고 적고 있지만, v1.9.0 은 virt-launcher 파드의 축출 요청을 승인 웹훅(virt-launcher-eviction-interceptor.kubevirt.io)으로 받습니다. 웹훅은 축출을 거절하면서 VMI 에 축출 표시(status.evacuationNodeName)를 남기고, virt-controller 가 그것을 보고 축출 마이그레이션(kubevirt-evacuation-…)을 만듭니다. drain 은 5초마다 다시 축출을 요청하고, 마이그레이션이 끝나 원본 파드가 사라지면 그때 통과합니다. 이 문서의 어긋남은 고치는 문서 PR 을 올렸습니다(user-guide#1051).

그래서 drain 이 끝나느냐는 그 노드의 모든 VM 이 옮겨지느냐에 달려 있습니다.

2.2 되돌려도 남는 축출 표시

장면 C 가 가장 뜻밖이었습니다. drain 이 실패해서 노드를 uncordon 했는데, VMI 의 status.evacuationNodeName 이 그대로 남아 있었습니다. virt-controller 는 이 표시를 보고 축출 마이그레이션을 계속 만들었고(두 번 uncordon 했는데 그 뒤로 두 번 더 생김), 부하를 끄자 그중 하나가 성공해 VM 이 방금 되돌린 노드를 떠났습니다. 「일단 drain 을 멈추고 되돌리자」 고 해도 VM 은 계속 이사 중인 셈입니다.

이 표시를 지우는 명령이 virtctl evacuate-cancel 입니다. 그런데 v1.9.0 virtctl 로 실행하면 이렇게 실패했습니다.

Internal error occurred: the server rejected our request due to an error in our request

서버(virt-api)는 「지금 축출 중인 노드 이름이 요청과 같을 때만 지운다」 는 조건을 거는데, v1.9.0 virtctl 이 요청에 노드 이름을 넣지 않아 생기는 버그입니다. 같은 요청에 본문 {"evacuationNodeName": "<노드>"} 를 넣어 보내면 200 으로 지워졌습니다. 이미 고쳐져서(kubevirt#17008, release-1.9 백포트 #18864) v1.9.1 이상의 virtctl 을 쓰면 됩니다. 서버가 v1.9.0 이어도 virtctl 만 새것이면 됩니다.

3. 동시 마이그레이션 한도

cirros VM 여섯 대를 한 노드에 모은 뒤 VMIM 여섯 개를 한꺼번에 만들었습니다. KubeVirt CR 의 parallelOutboundMigrationsPerNode 만 바꿨고 클러스터 한도 (parallelMigrationsPerCluster: 5)는 그대로 두었습니다.

노드당 한도 여섯 대 모두 끝날 때까지 동시에 Running 최대 한 건의 Running → Succeeded
1 33.9초 1 2–6초
2(기본) 22.3초 2 3–4초
4 23.2초 4 8–9초

한도를 2에서 4로 올려도 빨라지지 않았습니다. 한 노드가 내보낼 수 있는 대역(약 90MiB/s)을 나눠 쓰니 한 건이 그만큼 느려집니다. 작은 VM 이 여럿이면 1 에서 2로 올릴 때의 이득(34초 → 22초)이 크고, 그 위는 회선이 상한입니다.

마이그레이션 전용 망(spec.configuration.migrations.network)은 재지 못했습니다(미실측). 중첩 노드에 NIC 가 둘뿐이고 Multus 가 없어 별도 망을 만들지 못했습니다. 이 구성에서는 마이그레이션, NFS 디스크, 서비스 트래픽이 모두 같은 약 800Mbps 선을 나눠 썼습니다. 운영에서 VM 이 크거나 많다면 전용 망이 먼저이고, 한도는 그다음입니다.

# KubeVirt CR 의 일부. 비워 두면 위 기본값(클러스터 5, 노드당 2, 대역폭 무제한)
spec:
  configuration:
    migrations:
      parallelMigrationsPerCluster: 5
      parallelOutboundMigrationsPerNode: 2

4. 이 측정의 한계

숫자를 가져다 쓰기 전에 조건을 확인해 주십시오.

5. 정리: 라이브 마이그레이션을 믿기 전에

점검 이유(1·2편의 실측)
디스크가 RWX, 네트워크가 masquerade 아니면 웹훅이 VMIM 생성부터 거절
CPU 모델을 노드들이 함께 내는 이름 모델로 host-model 은 True 로 보여도 5분 뒤 실패
바쁜 VM 에 autoConverge 기본값은 128MB 쓰기에도 300초 뒤 포기, autoConverge 는 33–43초
postCopy 를 쓰려면 노드 sysctl 부터 없으면 1–2초 만에 실패
대역폭 제한은 실제 전송량으로 확인 32Mi 를 걸었는데 75MiB/s 가 나갔다
옮길 수 없는 VM 은 evictionStrategy None LiveMigrate 면 drain 이 끝나지 않는다
받는 노드의 메모리 여유 98% 면 5분마다 실패·재시도
v1.9.1 이상 virtctl uncordon 뒤에도 남는 축출 표시를 지우려면
클라이언트 재연결 서비스 끊김은 게스트 멈춤보다 몇 배 길다

직접 해 보고 싶다면 TT Lab 의 실습 코스 「KubeVirt 라이브 마이그레이션」(사이트 경로 /course/kubevirt-live-migration)이 이 측정과 같은 VM 셋, 같은 판으로 준비돼 있습니다. 공통 CPU 모델 고르기부터 옮기고 멈춤 시간 읽기, RWO 거절 읽기, drain 까지 여덟 단계입니다.

티티 한마디. 짐을 싸는 동안 계속 어지르는 친구가 있으면 이사가 영영 안 끝나요. 그럴 땐 그 친구를 살살 느리게(autoConverge) 해 주면 30초 만에 끝나요. 그리고 이사를 취소했다고 끝난 게 아니에요. 문에 붙은 「이사 중」 쪽지(evacuationNodeName)를 떼지 않으면 짐이 혼자 나가 버린답니다!

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다