하이퍼바이저 호스트를 재부팅해야 할 때, VM을 끄고 다른 호스트에서 다시 켜는 cold migration은 서비스가 몇 분씩 죽는다. QEMU/KVM의 live migration은 VM을 실행 상태 그대로 유지하며 메모리/디바이스 상태를 다른 호스트로 옮기고 마지막 순간에만 짧게 전환한다. 이 글에서는 pre-copy/post-copy 동작 원리, `virsh migrate` 실전 절차, 로컬에서 직접 재현해본 마이그레이션 결과를 정리한다.
pre-copy vs post-copy
| pre-copy (기본값) | post-copy | |
|---|---|---|
| 순서 | 메모리 먼저 다 옮기고 마지막에 실행 전환 | 최소 상태만 옮기고 바로 목적지에서 실행, 나머지는 접근 시점에 가져옴(userfaultfd) |
| 실패 시 | 소스가 계속 살아있어 안전 | 소스/네트워크 끊기면 목적지 VM 멈춤 |
| 약점 | dirty page rate > 대역폭이면 영원히 안 끝남 | 없음 (반드시 끝남) |
libvirt는 혼합 사용도 지원한다: --live로 pre-copy 시작 후 --postcopy-after-precopy/--timeout-postcopy로 전환.
공유 스토리지가 필요한 이유
live migration은 메모리·vCPU·디바이스 상태만 옮기고 디스크는 옮기지 않는다. 그래서 목적지도 소스와 같은 경로로 디스크 이미지를 봐야 하며(NFS 등으로 /var/lib/libvirt/images 공유), 없으면 --copy-storage-all(전체 복사) / --copy-storage-inc(차이분만)로 디스크까지 함께 옮겨야 해 훨씬 느려진다.
virsh migrate 실전
$ virsh list --all
Id Name State
----------------------
3 webvm01 running
$ ssh dest-host virsh list --all
Id Name State
--------------------
- - -
# 기본 라이브 마이그레이션
virsh migrate --live --persistent --undefinesource \
webvm01 qemu+ssh://root@dest-host/system
# 공유 스토리지 없이 디스크까지 함께
virsh migrate --live --persistent --undefinesource \
--copy-storage-all \
webvm01 qemu+ssh://root@dest-host/system
# dirty rate가 높아 안 끝날 때: auto-converge / post-copy 전환
virsh migrate --live --persistent --undefinesource --auto-converge \
webvm01 qemu+ssh://root@dest-host/system
virsh migrate --live --persistent --undefinesource \
--postcopy --postcopy-after-precopy \
webvm01 qemu+ssh://root@dest-host/system$ virsh domjobinfo webvm01
Job type: Unbounded
Time elapsed: 4213 ms
Data processed: 612.500 MiB
Data remaining: 38.219 MiB
Expected downtime: 87 ms
# 대역폭 제한(≈1Gbps), 최대 다운타임 조정
$ virsh migrate-setspeed webvm01 125
$ virsh migrate-setmaxdowntime webvm01 500
로컬 QEMU 2-프로세스로 직접 검증
호스트 두 대 없이도 같은 머신에서 QEMU 프로세스 두 개를 localhost로 마이그레이션시키면 pre-copy 메커니즘 자체는 확인할 수 있다. 시리얼로 카운터를 계속 찍는 16비트 부트섹터를 직접 만들어 확인했다.
.code16
.text
.global _start
_start:
cli
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw $0x7c00, %sp
movb $0, counter
main_loop:
movw $msg, %si
print_msg:
lodsb
testb %al, %al
je print_counter
movw $0x3F8, %dx # COM1
outb %al, (%dx)
jmp print_msg
print_counter:
movb counter, %al
addb $0x30, %al
movw $0x3F8, %dx
outb %al, (%dx)
movb $0x0A, %al
outb %al, (%dx)
incb counter
cmpb $10, counter
jl delay
movb $0, counter
delay:
movl $0x00500000, %ecx
delay_loop:
decl %ecx
jnz delay_loop
jmp main_loop
msg:
.asciz "CNT="
counter:
.byte 0
.fill 510 - (. - _start), 1, 0
.word 0xAA55$ as counter.S -o counter.o
$ ld --oformat binary -Ttext 0x7c00 -e _start -o boot.img counter.o
# 소스 (KVM 그룹 권한 없으면 -enable-kvm 없이 TCG로도 충분)
$ qemu-system-x86_64 -name source -m 128 -nographic -display none \
-drive file=boot.img,format=raw,if=ide \
-serial file:source_serial.log \
-monitor tcp:127.0.0.1:4444,server,nowait &
# 목적지 (같은 boot.img, -incoming으로 수신 대기)
$ qemu-system-x86_64 -name dest -m 128 -nographic -display none \
-drive file=boot.img,format=raw,if=ide \
-serial file:dest_serial.log \
-monitor tcp:127.0.0.1:4445,server,nowait \
-incoming tcp:127.0.0.1:5555 &
$ (echo "migrate tcp:127.0.0.1:5555"; sleep 1; echo "info migrate") | nc -q1 127.0.0.1 4444
Migration status: completed
total time: 33 ms
downtime: 1 ms
transferred ram: 1864 kbytes
throughput: 462.97 mbps
$ tail -3 source_serial.log
CNT=6
CNT=7
CNT=8
$ echo "info status" | nc -q1 127.0.0.1 4444
VM status: paused (postmigrate)
$ head -3 dest_serial.log
CNT=9
CNT=0
CNT=1
$ echo "info status" | nc -q1 127.0.0.1 4445
VM status: running
소스는 CNT=8에서 멈추고 목적지는 정확히 CNT=9부터 이어받았다 — 재부팅 없이 CPU/메모리 상태가 그대로 전송·재개됐다는 증거다. virsh migrate가 두 호스트 사이에서 하는 일도 원리는 동일하고, 전송 채널이 SSH+QEMU 마이그레이션 프로토콜이고 디스크가 공유 스토리지로 이미 양쪽에 보인다는 점만 다르다.
성능 요소
| 요소 | 영향 |
|---|---|
| 네트워크 대역폭 | 낮으면 초기 풀 카피와 dirty page 재전송 모두 느려짐 |
| dirty page rate | 대역폭보다 크면 pre-copy는 이론상 절대 수렴하지 않음 |
| VM 메모리 크기 | 클수록 초기 풀 카피 시간 증가 |
| CPU/호스트 부하 | 바쁘면 페이지 전송·압축 처리가 밀려 지연 |
실무 대응: 전용 마이그레이션 네트워크 확보, dirty rate 높은 워크로드는 마이그레이션 대상에서 제외하거나 유지보수 시간대에 부하를 낮춰둔다.
주의사항
- 디스크 이미지 경로 불일치: 공유 스토리지 마운트가 빠지거나 경로가 다르면 목적지에서 VM 시작 실패.
- CPU 모델 불일치: 세대가 다르면 목적지에서 크래시 가능. XML에
host-model대신 공통 CPU 모델 명시. - NFS 파일 락: KVM은 NFS 파일 락킹 미지원 — 마운트 옵션에 락 강제 설정 있으면 문제.
- pre-copy 미수렴:
domjobinfo의Data remaining이 안 줄면 auto-converge/post-copy 전환 또는--timeout후 재시도. - pre-copy는 stop-and-copy 전까지 소스가 살아있어 실패/취소해도 안전. post-copy 전환 후 실패는 VM 손상 위험 — dirty rate 높은 워크로드에만 한정 사용 권장.
마무리
pre-copy는 안전하지만 dirty page 많은 워크로드에서 안 끝날 수 있고, post-copy는 확실히 끝나지만 장애에 취약하다. 공유 스토리지·전용 네트워크·CPU 모델 통일을 미리 준비하고, 소규모 환경에서 virsh migrate --live로 먼저 연습한 뒤 필요하면 auto-converge/post-copy를 단계적으로 적용할 것.