QEMU/KVM 라이브 마이그레이션 원리와 실습

하이퍼바이저 호스트를 재부팅해야 할 때, 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 미수렴: domjobinfoData 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를 단계적으로 적용할 것.

참고

답글 남기기