TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

KubeVirt のライブマイグレーション実測 1:ゲストは0.34秒停止したのにサービスは2.4秒途切れた

ステータス:実測(2026-10-31)。 TT Lab の実習用 VM 3 台の中に Kubernetes と KubeVirt v1.9.0 を構築したネスト環境のクラスターで測定した数値です。 基本テストは 5 回、ブロックされる条件と CPU モデルのテストは条件ごとに 1 回ずつです。数値はすべて、マイグレーション 1 回ごとに残したローデータ(VMIM のフェーズ記録、libvirt のタスク情報、プローブの記録)から抽出了しました。作成ルール:実測データを基本とし、測定できなかったものは「未計測」、原因を確認できなかった解釈は「推定」と表示します。

目次

1. 結論から

ノードの修正やカーネルのアップデートを行うには、そのノード上の VM をどこかへ移行させる必要があります。KubeVirt ではこれを ライブマイグレーションで行います。ゲストを停止させずに メモリを別のノードへコピーしたあと、ごく短時間停止させてから新しいノードで処理を継続します。ドキュメントだけを読むと「ほとんど途切れない」ように聞こえますが、実際に移行させてみると 条件がかなり多くありました。

メモリを酷使する VM、ノードのドレイン(drain)、同時マイグレーションの制限については パート 2で取り上げます。

2. ライブマイグレーションが行うこと

KubeVirt において VM とは、ノード上の Pod(virt-launcher)内で稼働する QEMU プロセスです。Pod は別のノードへ「移行」させることができないため、KubeVirt は 移行先のノードに新しい Pod をもう 1 つ立ち上げ、ゲストの状態をそちらにコピーしたあと、元の Pod を終了させる という方式で移行します。このプロセスを開始するオブジェクトが VirtualMachineInstanceMigration(略称:VMIM)です。 virtctl migrate もこのオブジェクトを 1 つ作成するだけです。

라이브 마이그레이션: 게스트를 켠 채로 옮기기 부하 없는 1Gi VM, 5회 중앙값(실측) 원본 노드 대상 노드 ① 대상 파드 준비 · 2–4초 게스트 실행 중 virt-launcher 파드 A 새 파드 B 기동 빈 QEMU 가 기다림 ② 사전 복사 · 3–4회 반복, 약 88MiB/s 게스트 계속 실행 바뀐 페이지에 표시 받은 페이지 채움 아직 멈춰 있음 모두 합쳐 456MiB ③ 멈추고 마지막 복사 · 343ms 게스트 멈춤 남은 페이지 + CPU 상태 마지막 조각 받음 남은 것 ④ 대상에서 재개 파드 A 끝남 게스트 실행 파드 IP 는 새것 디스크는 옮기지 않는다 두 파드가 같은 RWX PVC(공유 스토리지)를 붙인다 ②는 1회차에 메모리 전체(0 으로 찬 페이지는 거의 안 보냄), 다음 회차부터는 그사이 바뀐 페이지만 보낸다.
図:ライブマイグレーションの 4 つのステップ:移行先 Pod の準備、事前コピー、停止と最終コピー、移行先での再開
  1. 移行先 Pod の準備。 virt-controller が移行先のノードに virt-launcher Pod を新規作成します。この Pod 内の QEMU はゲストなしで「流入するマイグレーション」を 待ち受けます。実測では、VMIM を作成してから移行先 Pod の準備が整うまでに 2~4 秒かかりました。 移行先 Pod をスケジュールできない場合、ここで停止します(5・6節)。
  2. 事前コピー(pre-copy)。 移行元の QEMU がゲストのメモリを送信します。ゲストはその間も稼働し続けるため、すでに送信したページが再度書き換えられます。そのため、 1回目のラウンドでは全体を、次のラウンドからはその間に変更された(ダーティになった)ページのみを再送信します。0 で埋められたページはほとんど送信されないため、1.016GiB のゲストにおいて実際に 送信されたデータ量は 456MiB でした。反復回数は 3~4 回、速度は約 88MiB/s でした。
  3. 停止と最終コピー(stop-and-copy)。 残りのデータ量を目標時間内に送信できるようになると、ゲストを停止し、残りのページと CPU 状態を送信します。このタイミングが ゲストが実際に停止した時間であり、libvirt が Total downtime として記録する時間です。KubeVirt v1.9 は目標値を 300ms から開始し、処理が追いつかない場合は少しずつ 引き上げます(上限は maxDowntimeMs デフォルトで 900ms)。
  4. 移行先での再開。 移行先の QEMU がゲストの処理を継続し、元の Pod は終了します。 ディスクは移行しません。 2 つの Pod が同じ PVC を同時にアタッチするためですが、そのため PVC が複数のノードから同時にアタッチ可能な RWX(ReadWriteMany)である必要があります(5節)。

メモリを送信するデータは、2 つのノードの virt-handler Pod 間、すなわち Pod ネットワーク経由で流れます。別途マイグレーション専用のネットワークを割り当てない限り、サービス用のトラフィックと 同じ回線を共有します。

3. 測定環境と方法

項目 値
外部 TT Lab の実習用 VM 3 台(各 4 vCPU・4GiB、Ubuntu 24.04、ネスト仮想化)。コントロールプレーン 1 台、ワーカー 2 台。2 台のワーカーは CPU 世代が異なる物理サーバー上に存在する
内部の Kubernetes kubeadm 1.36.4、containerd 2.3.5、Flannel v0.28.9
KubeVirt・CDI KubeVirt v1.9.0(virt-launcher 内の libvirt 11.10.0、QEMU 10.1.0、multifd 8チャンネル)、CDI v1.66.0
共有ストレージ コントロールプレーンのカーネル NFS + csi-driver-nfs 4.13.4、StorageClass 1 つ(RWX)。RWO 比較用に local-path-provisioner v0.0.32
ゲスト Fedora 44(1 vCPU、メモリ 1Gi、ルート 6GiB RWX、masquerade)、条件テストは cirros(128Mi)
ワーカー間の回線 iperf3 10秒 × 3:790~799Mbps(セッションネットワーク)、764~772Mbps(Pod ネットワーク)

測定方法は 3 つあります。

構築にかかった時間も記録しておきます。VM 3 台の準備に 276 秒、csi-driver-nfs に 52 秒、KubeVirt Available に 122 秒、CDI Available に 91 秒、Fedora のベースディスクの取得に 193 秒かかりました。CDI の取得時に一度引っかかりましたが、csi-driver-nfs に mountPermissions を指定しないとボリュームフォルダーが root 0755 になり、インポート Pod(uid 107)が permission denied で無限に再起動してしまいます。NFS は fsGroup による所有権の変更を行わないため、StorageClass に mountPermissions: "0777" を指定して解決しました。

4. 基本の実測:負荷のない 1Gi VM での 5 回のテスト

Fedora VM(1Gi、RWX、masquerade、 evictionStrategy: LiveMigrate)を 2 つのワーカー間で 5 回往復させました。マイグレーションポリシーは指定していません(デフォルト値)。CPU は 2 つのワーカーが両方とも提供できる名前付きモデル Skylake-Client-v4 に固定しました。なぜそうする必要があったのかは 6 節で解説します。

回 VMIM 作成 → Succeeded libvirt 経過時間 ゲスト停止時間 送信量 速度 反復 新規接続の途切れ 維持接続
1 11.4秒 6680ms 413ms 459MiB 86.2MiB/s 4 2406ms 2205ms、切断
2 9.0秒 6652ms 490ms 456MiB 87.1MiB/s 4 2205ms 2005ms、切断
3 10.3秒 6262ms 343ms 455MiB 89.3MiB/s 3 2607ms 2605ms、切断
4 9.1秒 6400ms 338ms 456MiB 88.3MiB/s 4 1204ms 1203ms、切断
5 10.1秒 6208ms 342ms 456MiB 90.1MiB/s 4 2806ms 3007ms、切断
中央値 10.1秒 6400ms 343ms 456MiB 88.3MiB/s 4 2406ms 2406ms、5/5 切断

VMIM を作成してから完了するまでに約 10 秒かかり、そのうち実際のコピー(libvirt 経過時間)は 6.4 秒でした。残りは移行先 Pod のスケジュールと起動にかかる時間(1~2秒)、移行先の準備(0~2秒)、完了後の状態クリーンアップの時間です。

4.1 なぜ 0.34 秒の停止が 2.4 秒の切断になるのか

게스트는 0.34초 멈췄는데 서비스는 2.4초 끊겼다 부하 없는 1Gi VM 5회, 중앙값(범위), 실측 0초 1초 2초 3초 게스트 멈춤 libvirt 가 잰 값 343ms (338–490) 새 연결 무응답 Service 너머 탐침 2.4초 (1.2–2.8) 왜 다른가: masquerade 에서는 게스트가 파드 뒤에 숨는다 옮기기 전 클라이언트 Service 파드 A IP a → NAT → 게스트 TCP 연결을 맺어 둠 옮긴 뒤 클라이언트 Service 파드 B IP b → NAT → 게스트 엔드포인트가 B 로 바뀌어야 닿음 파드 A 가 사라지면 A 에 맺은 연결도 사라진다 유지하던 TCP 연결은 5회 모두 끊겼고, 다시 맺으면 됐다
図:ゲスト停止時間 343ms と新規接続無応答 2.4 秒の比較、masquerade により Pod が置き換わる仕組みの図

KubeVirt のデフォルトネットワークバインディングである masquerade 環境では、ゲストは Pod の背後に隠れています。外部から見えるアドレスは virt-launcher Pod の IP であり、Pod 内で NAT を介してゲストに転送されます。しかし、2 節で見たようにマイグレーションは 新しい Pod を作成して移行する 作業であるため、完了するとゲストを包む Pod が切り替わり、Pod の IP も変更されます。

そのため、「ライブマイグレーションだから途切れない」と信じて設計してはいけません。 クライアントが切断された接続を再確立し、数秒単位の失敗をリトライで 耐えられるように作っておく必要があります。マスカレードではないバインディング(Multusでアタッチした別途のネットワークなど)では同じ値の測定はしていません(未計測)。

5. 移行できない条件

VMIの状態には LiveMigratable 条件があります。Falseの場合はその理由が併記され、VMIMを作ろうとすると承認Webhookが作成自体を拒否します。cirros VMで条件ごとに実際に作成してみました。

VM ディスク・ネットワーク LiveMigratable 理由とメッセージ(原文のまま) VMIM
RWO NFS StorageClassの RWO PVC False DisksNotLiveMigratable: PVC … is not shared, live migration requires that all PVCs must be shared (using ReadWriteMany access mode) 作成拒否
local-path local-path RWO PVC False 同一のメッセージ 作成拒否
hostDisk containerDisk + hostDisk False DisksNotLiveMigratable: cannot migrate VMI with non-shared HostDisk 作成拒否
bridge RWXディスク、ポッドネットワークに bridge False InterfaceNotLiveMigratable: cannot migrate VMI which does not use masquerade or a migratable plugin to connect to the pod network 作成拒否
containerDisk(ctrでプル) ノードに ctr images pull としてあらかじめプルしておいたイメージ True Schedulingで15分4秒後にFailed
containerDisk(crictlでプル) 同一のイメージを crictl pull として再度プルした後 True Succeeded

5.1 RWOはストレージがNFSでもブロックされる

KubeVirtはストレージが実際に複数のノードからアタッチ可能かどうかを確認せず、 PVCのアクセスモードのみ を確認します。ストレージがNFSであって本来共有できる状態であっても、PVCをRWOで作成した場合は移行できません。実際の環境を読み取り専用で確認したところ、既存のVMがまさにこのケースでした。ストレージのStorageProfileはRWXをサポートできるにもかかわらず、VMを作成するコードがPVCをRWOとして記述していたため、すべて LiveMigratable: False になっていました。移行できるようにするには、ルートディスクをRWX PVCとして再作成する必要があります(VMを停止してRWXに複製して変更するか、v1.9のボリュームマイグレーションを使用します)。ボリュームマイグレーションについては今回測定していません(未計測)。アクセスモードとボリュームモードの話は、 学生VMが起動するまで(第3部)に詳しく記載されています。

5.2 containerDiskと ctr の落とし穴

containerDisk(ディスクをコンテナイメージとして取得する方式)は移行可能です。書き込みレイヤーを一緒に送信するブロックマイグレーション(BlockMigration)として処理されます。しかし、 閉域網のようにイメージをあらかじめノードに配置しておく環境には落とし穴がありました。

この挙動はアップストリームにIssuesとして報告しました(kubevirt#19694)。それまでの間は、事前に配置するイメージを crictl pull でプルするか、インポート後に crictl inspecti でrepoDigestsが満たされていることを確認するのが安全です。

5.3 失敗までに要する時間

ブロックされる箇所によって待機時間が大きく異なります。Webhookが拒否する条件は即座にわかりますが、 ターゲットポッドをスケジューリングできない場合は5分、ターゲットポッドは起動したもののイメージのプルに失敗した場合は 15分 経過した後にFailedになります。待ちたくない場合はVMIMを削除すればキャンセルされます。

6. CPUモデル:どのノード間で移行できるのか

ゲストは初回起動時に認識したCPU機能を使い続けると想定しています。移行先のノードにその機能がない場合ゲストがクラッシュする恐れがあるため、KubeVirtはターゲットポッドに ノードセレクターを付加して機能が一致するノードにのみスケジューリングします。ノードごとに付与されるCPUラベル(cpu-model.node.kubevirt.io/…, cpu-feature.node.kubevirt.io/…, cpu-model-migration.node.kubevirt.io/…)はvirt-handlerのノードラベラーが付与します。

2台のワーカーがCPU世代の異なるインテルホスト(family 6のmodel 151と170)上にあったため、同一条件のままテストすることができました。ノードラベラーは2台のワーカーに 同一のhost-model名を付与しましたが、必須機能は異なっていました。一方には rfds-clearがあり、もう一方には ddpd-u·rfds-no がありませんでした。

CPU設定 LiveMigratable ターゲットポッドに付与されたセレクター(実測) 結果
名前モデル Skylake-Client-v4 True cpu-model.node.kubevirt.io/Skylake-Client-v4 Succeeded
host-model(デフォルト) True cpu-model-migration.node.kubevirt.io/<원본의 host-model 이름> + 元ノードの必須機能27個 + ベンダー ターゲットポッドがUnschedulable、 正確に5分後にFailed
host-passthrough True cpu-vendor.node.kubevirt.io/Intel 1つのみ Succeeded、世代の異なるノードへ移行
どのノードも提供できないモデル cpu-model.node.kubevirt.io/<그 모델> VMI自体がSchedulingにとどまる(VMが起動しない)

ここから読み取れるポイントは3つあります。

  1. LiveMigratable はCPUの互換性を計算しません。 host-modelのVMはTrueでしたが移行先がなく、ターゲットポッドが5分間Pending状態になったのち、 unschedulable pod … timeout period exceeded で終了しました。
  2. host-modelのターゲット条件は「元と同じhost-model名 + 元ノードの必須機能すべて」です。 名前が同じであっても、機能が1つでも不足していると移行できません。そしてhost-model名は「最も近い名前モデル」であるだけで、世代を教えてくれません。世代の異なる2台のホストが同じ名前を受け取りました。
  3. host-passthroughはベンダーのみを確認します。 そのため、世代の異なるノードにも移行できました。今回のゲストは移行先に存在しない機能(ddpd-u·rfds-no など)を認識した状態だったため、その機能を実際に使用するプログラムであれば危険でした。passthroughはCPUが完全に一致するノード同士でのみ使用すべきです。

この関係はユーザーガイドに整理されていなかったため、該当セクションを追加するドキュメントPRを提出しました(user-guide#1052).

6.1 混在したハードウェアではどうなるか

同じルール(上記のhost-modelセレクターが実測で検証したルール)を、インテル製ノード4台とAMD製ノード1台が混在する実際のノードのラベルに適用して計算してみました。ノード名を除外してペアの種類ごとにまとめると以下のようになります。

섞인 노드: CPU 설정별로 옮길 수 있는 쌍 두꺼운 칸은 중첩 클러스터 실측, 나머지는 노드 라벨로 계산 host-model (기본값) 이름 모델 공통 모델 지정 host- passthrough 같은 CPU 기능 라벨이 같음 ✓ 옮겨짐 ✓ 옮겨짐 ✓ 안전 host-model 이름만 같음, 기능 1–3개 차이 ✗ 5분 뒤 실패 ✓ 옮겨짐 ⚠ 옮겨짐, 위험 다른 세대 인텔 기능 8–15개 차이 ✗ 대상 없음 ✓ 공통 모델로 ⚠ 스케줄됨, 위험 인텔 ↔ AMD 공급사가 다름 ✗ 대상 없음 △ 옛 모델뿐 ✗ 대상 없음 ✗ 대상 파드가 뜨지 못한다. LiveMigratable 은 그래도 True 로 보인다. ⚠ 공급사만 보고 옮긴다. 게스트가 대상에 없는 기능을 보고 있었다. △ 공통 이름 모델이 IvyBridge 이하뿐이라 AVX2 를 잃는다. 다른 세대 인텔끼리의 공통 모델 예: Skylake-Client-v4
図:混在ノードにおけるCPU設定別の移行可能ノードペア:CPUが同一、host-model名のみ同一、世代の異なるインテル、インテルとAMD
ノードペア ラベルから見た差異 host-model 名前モデル host-passthrough
CPUが同一の2台 cpu-feature ラベルが完全に一致 可能 可能 可能(安全)
host-model名のみ同一の2台 元の必須機能のうち移行先にないものが1〜3個(rfds-clear が1つ、または ddpd-u·rfds-no·vmx-any-errcode) 両方でブロック 共通モデルとして可能 スケジュールは可能だが危険
世代の異なるインテル 一方にはAVX-512系が12個、もう一方には waitpkg·bhi-ctrl など8個がない 両方でブロック 共通モデルとして可能 スケジュールは可能だが危険
インテルとAMD 相互に15〜22個(仮想化拡張から vmx·svm まで異なる) ブロック 共通モデルが古いもののみ ブロック(ベンダーが異なる)

この表はラベルの計算に基づいています。ネストされたテストで検証されたのは「host-model名のみ同一の2台」の1行のみです。

6.2 自環境で確認する方法

2つのノードのCPU機能ラベルを比較すれば、host-modelのVMが移行できるか事前に把握できます。

diff <(kubectl get node A -o json | jq -r '.metadata.labels|keys[]|select(startswith("cpu-feature."))') \
     <(kubectl get node B -o json | jq -r '.metadata.labels|keys[]|select(startswith("cpu-feature."))') \
  && echo "A·B CPU 기능 같음"

2つのノードがともにサポートできる名前モデルは、 cpu-model.node.kubevirt.io/ で始まるラベルの積集合です。混在環境で移行するVMには、そのうちの1つを spec.domain.cpu.model に記述するか、KubeVirt CRの spec.configuration.cpuModel でクラスターのデフォルト値を変更します。

7. まとめ

確認すべき項目 理由(本記事での実測)
PVCのアクセスモードがRWXであるか RWOの場合、ストレージがNFSであってもWebhookが拒否する
ポッドネットワークのバインディングがマスカレード(またはマイグレーション可能なプラグイン)であるか bridgeの場合は拒否される
containerDiskイメージを ctr で配置していないか 存在しないダイジェストをターゲットがプルしようとして15分後に失敗する
CPUモデルを指定しているか host-modelはTrueに見えても5分後に失敗し、passthroughは危険な状態で成功する
クライアントが再接続・リトライを実装しているか 停止時間が0.34秒であっても新規接続は2.4秒間途切れ、既存接続は切断される

本記事の実測は、TT Labの実習コース「KubeVirt ライブマイグレーション」(サイトパス /course/kubevirt-live-migration)でそのまま追体験できます。同じVMセットとバージョンを用い、共有ストレージ、KubeVirt、共通CPUモデルの選択、移行および停止時間の測定、RWO拒否の確認、ドレインの手順を進めます。KubeVirtのアーキテクチャが初めての場合は、 学生VMが起動するまでと KubeVirtのソースコードリーディング シリーズが基礎となります。

ティティの一言。 引っ越し中にお客さんがドアの前で待っていた時間は0.34秒でしたが、お客さんは2.4秒間インターホンが鳴らなかったと言っていました。住所(ポッドIP)が変わったからです!そして引っ越しトラックは「同じ地区(CPU機能)」にしか行きません。名前だけ似ている地区は、到着して初めて道がないことに気づくので、あらかじめ地図を合わせておいてください。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます