들어가며
이제 live migration의 진짜 어려운 구간으로 들어가 보자. target Pod를 띄우는 것까지는 Kubernetes control plane 문제였다. 그 다음부터는 libvirt와 QEMU가 guest의 메모리, CPU 상태, 일부 디스크 상태를 어떻게 옮길지 결정하는 **데이터 전송 문제**가 된다.
이 구간의 핵심 코드는 `pkg/virt-launcher/virtwrap/live-migration-source.go`다. 여기서 KubeVirt는 libvirt migration flag를 만들고, 진행 상태를 모니터링하며, 필요하면 post-copy나 pause 같은 보조 전략을 발동한다.
migration data plane에서 실제로 옮겨지는 것
live migration이라고 해서 "VM 전체를 한 번에 복사"하는 것은 아니다. 실제로는:
- guest 메모리 페이지
- CPU 실행 상태
- 일부 장치 상태
- 필요하면 local volume 관련 데이터
같은 정보가 옮겨진다.
shared storage가 있다면 디스크 데이터 전체를 복사하지 않아도 되지만, 메모리와 실행 상태는 계속 전송해야 한다. 여기서 가장 어려운 문제가 바로 **guest가 계속 메모리를 더럽히고 있다는 것**이다.
pre-copy란 무엇인가
pre-copy는 가장 기본적인 migration 전략이다.
1. source VM은 계속 실행된다.
2. 동시에 메모리 페이지를 target으로 복사한다.
3. 복사 중 다시 변경된 페이지는 다음 라운드에서 다시 복사한다.
4. 충분히 수렴하면 짧게 stop-and-copy 전환을 한다.
이 방식의 장점은 guest가 대부분의 시간 동안 계속 실행된다는 점이다. 단점은 guest가 메모리를 너무 빠르게 변경하면 복사가 영원히 따라잡지 못할 수 있다는 점이다.
이때 등장하는 개념이 dirty page rate다.
dirty page rate가 왜 중요한가
`live-migration-source.go`의 `logMigrationInfo`를 보면 libvirt job stats에서 다음 정보를 읽는다.
- `DataRemaining`
- `MemProcessed`
- `MemRemaining`
- `MemDirtyRate`
- `Downtime`
즉 KubeVirt는 migration을 "성공 또는 실패"만으로 보지 않고, **지금 얼마나 남았는지와 guest가 얼마나 빠르게 메모리를 다시 더럽히는지** 계속 관찰한다.
VM이 매우 write-heavy하면, 복사한 페이지가 곧바로 다시 더럽혀져 pre-copy가 수렴하지 않을 수 있다. 고성능 데이터베이스, 메모리 집약 워크로드가 여기서 어렵다.
libvirt migration flag는 무엇을 말해 주는가
`generateMigrationFlags`를 보면 KubeVirt가 migration 시 어떤 모드를 libvirt에 요구하는지 드러난다.
- `MIGRATE_LIVE`
- `MIGRATE_PEER2PEER`
- `MIGRATE_PERSIST_DEST`
- block migration이면 `MIGRATE_NON_SHARED_INC`
- auto-converge 허용 시 `MIGRATE_AUTO_CONVERGE`
- post-copy 허용 시 `MIGRATE_POSTCOPY`
- parallel migration이면 `MIGRATE_PARALLEL`
즉 migration 방식은 막연한 개념이 아니라, 결국 libvirt flag 조합으로 구체화된다.
migration monitor가 하는 일
`migrationMonitor`는 data plane을 지켜보는 심장부다. 여기서 KubeVirt는:
- remaining data를 추적하고
- 진행이 멈췄는지 판단하고
- acceptable completion time을 넘겼는지 보고
- 필요하면 post-copy 또는 pause 전략을 발동하고
- 아예 stuck이면 abort한다
이 구조는 굉장히 현실적이다. migration은 "시작하면 끝까지 간다"가 아니라, **진행률 기반 적응형 제어**가 필요하다.
언제 post-copy로 넘어가는가
코드를 보면 migration이 오래 stalled 되었고 `AllowPostCopy`가 켜져 있으며 VFIO VMI가 아니면 `dom.MigrateStartPostCopy`를 호출한다. 즉 KubeVirt는 처음부터 post-copy로 시작하지 않고, 보통 pre-copy로 시작한 뒤 수렴이 안 되면 post-copy로 전환한다.
post-copy의 의미
- source가 더 이상 모든 페이지를 미리 복사하려고 애쓰지 않는다
- target이 active workload를 더 일찍 받는다
- 필요한 페이지를 나중에 가져올 수 있다
장점은 바쁜 VM도 결국 이동을 끝낼 가능성이 높아진다는 점이다. 하지만 단점은 크다. active state가 target으로 넘어간 뒤 네트워크나 target 장애가 나면 guest가 더 취약해질 수 있다.
그래서 KubeVirt 문서와 설정은 post-copy를 강력한 옵션이지만 위험한 옵션으로 다룬다.
auto-converge와 pause는 왜 필요한가
모든 환경에서 post-copy가 허용되는 것도 아니고, 모든 workload에 적합한 것도 아니다. 그래서 KubeVirt는 다른 보조 전략도 쓴다.
auto-converge
libvirt와 QEMU가 guest 성능에 어느 정도 영향을 주어 dirty rate를 낮추려는 전략이다. 쉽게 말하면 "조금 느려져도 migration을 끝내자"는 쪽이다.
pause
코드에서는 post-copy가 허용되지 않거나 적절하지 않으면 guest를 잠시 suspend해 migration을 마무리하도록 한다. 이건 downtime을 희생해 completion을 얻는 전략이다.
즉 migration은 성능, 가용성, 성공 가능성 사이의 삼각 trade-off다.
VFIO workload는 왜 더 까다로운가
`live-migration-source.go`는 VFIO 기반 VMI를 따로 다룬다. post-copy가 일반적으로 지원되지 않는 경우가 있고, 대신 매우 큰 max downtime을 설정해 QEMU 내부 switchover를 유도한다.
이는 device passthrough workload가 메모리만 옮기면 되는 일반 VM보다 훨씬 어렵다는 뜻이다. 네트워크 SR-IOV나 GPU passthrough workload에서 migration 제약이 큰 이유가 여기 있다.
migration이 stuck했다고 판단하는 기준
코드는 "진행이 없고 네트워크 또는 QEMU 연결 문제가 의심되면 abort"라는 매우 실무적인 판단을 한다. 즉 단순히 느린 것과 완전히 멈춘 것을 구분한다.
대표적인 두 경우가 있다.
- progress timeout 동안 `DataRemaining`이 줄지 않음
- overall acceptable completion time을 초과
첫 번째는 보통 stuck, 두 번째는 수렴 실패에 가깝다.
디스크 migration과 메모리 migration이 섞일 때
block migration 또는 volume migration이 있으면 난이도는 더 올라간다. `classifyVolumesForMigration`, `configureLocalDiskToMigrate` 같은 로직이 있는 이유다. 메모리와 디스크를 동시에 옮기면 bandwidth, completion time, failure surface가 커진다.
소스 코드에도 destination volume이 source보다 작을 때 migration이 실패하는 특별한 에러 메시지 개선 로직이 있을 정도로, storage는 migration 실패의 흔한 원인이다.
자주 하는 오해
오해 1: live migration은 무중단이다
완전한 무중단이 아니라, downtime을 최소화하는 이동이다. 수렴이 안 되면 pause나 post-copy로 넘어가며, 그 순간의 trade-off가 생긴다.
오해 2: post-copy가 무조건 더 좋다
아니다. 성공 가능성은 높일 수 있지만 장애 시 위험이 더 크다.
오해 3: migration 실패는 대부분 네트워크 탓이다
네트워크도 원인이지만, dirty rate가 너무 높거나 local volume 조건이 맞지 않거나 device passthrough 제약일 수도 있다.
운영자가 꼭 봐야 할 지표
- remaining data가 줄고 있는가
- dirty rate가 높은가
- post-copy로 전환되었는가
- migration mode가 paused 또는 post-copy로 바뀌었는가
- volume migration이 섞였는가
이 지표를 보면 "왜 migration이 느린가"와 "왜 migration이 실패했는가"를 분리해서 볼 수 있다.
마무리
KubeVirt live migration의 data plane은 pre-copy를 기본으로 하되, 수렴이 안 되면 auto-converge, pause, post-copy 같은 보조 전략을 사용하는 적응형 시스템이다. 이 구조는 busy VM을 가능한 오래 살려 두면서도 결국 옮길 수 있게 하려는 현실적인 타협이다. 따라서 migration을 제대로 이해하려면 libvirt flag, dirty page rate, progress timeout, post-copy 전환 조건을 함께 봐야 한다.
다음 글에서는 이 데이터 전송이 어떤 네트워크 경로와 소켓, 포트를 통해 이뤄지는지 migration proxy 관점에서 살펴보겠다.
현재 단락 (1/70)
이제 live migration의 진짜 어려운 구간으로 들어가 보자. target Pod를 띄우는 것까지는 Kubernetes control plane 문제였다. 그 다음부터는 l...