ステータス: 実測(2026-10-31)。 第1部と同じネストされたクラスター(TT Labの検証用VM3台、KubeVirt v1.9.0)で測定した値です。 ポリシーのテストは条件ごとに1~3回、drainはシナリオごとに1回、同時制限は制限ごとに1回です。作成ルール:実測データを基本とし、測定できなかったものは「未測定」、 原因を確認できなかった解釈は「推定」と表記します。
目次
1. メモリを継続的に書き換えるVM
第1部のVMは何も作業を行っていませんでした。実際のVMはメモリを使用します。事前コピーは「送信中に変更されたページを再送する」という仕組みであるため、 ゲストが書き換える速度が送信速度と同等であれば終わりません。これを実際に再現してみました。
- ゲスト(Fedora、1Gi)内で
stress-ng --vm 1 --vm-bytes 512M --vm-method write64 --vm-keep -t 0を実行しました。同じ512MBを継続的に書き返し続けます。 - ポリシーは
MigrationPolicyのように1つだけ指定して移行しました。ポリシーのないデフォルト値は帯域幅無制限、completionTimeoutPerGiB: 150,progressTimeout: 150,maxDowntimeMs: 900、autoConverge・postCopyが無効でした(VMIに実際に適用された値)。
| テスト | 負荷 | ポリシー | 結果 | 所要時間 | ゲストの停止時間 | サービス切断(新規接続 / 維持接続) |
|---|---|---|---|---|---|---|
| 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、切断あり |
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はその逆方向です。まずゲストを移行先で稼働させ、まだ転送されていないページはゲストがアクセスした瞬間に移行元から取得します。Linuxのuserfaultfdでその瞬間を捉えるためノードのカーネルが許可している必要があり、Ubuntuのデフォルト値は0でした(ネストされたワーカーも0)。有効にすると成功しましたが、KubeVirtは事前コピーの制限時間を使い切ってからPostCopyに移行しました。また、ポストコピー中はゲストのメモリが2つのノードに分散するため、どちらかがダウンするとゲストが失われます。 ビジーな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を停止せずに移行します。このパスを4つのシナリオでテストしました。
| シナリオ | 内容 | 結果 |
|---|---|---|
| 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) | 退去が停止しなかった。 元に戻した後も退去マイグレーションが2回余分に発生し、負荷を停止すると最後の移行が成功してVMが 復旧元のノードから離れた |
| D | 負荷を停止したVM3台(LiveMigrate)と1台(evictionStrategy None)が存在するノードをdrain(制限400秒) | 2台は 22秒・13秒 で移行された。NoneのVMは7.4秒で停止し、17.9秒で別のノード上で新しいVMIとして起動した(再起動)。残りの1台は移行先のノードのメモリ要求量が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が移行されるかどうかにかかっています。
- 移行できないVM(RWOディスクなど)に
LiveMigrateを指定しておくと、drainは終了しません(シナリオB)。移行できないVMにはNoneまたはLiveMigrateIfPossibleを設定します(LiveMigrateIfPossibleはAPI仕様に基づくもので今回は測定していません)。 - 収束しないVMが1台あると、drainはそのVMの完了を待機して停止します(シナリオA)。第1部のautoConvergeがここでも必要になります。
- 移行先ノードに十分なメモリの空きがある必要があります(シナリオD)。ワーカーが2台の場合、一方をドレインするときはその上のVMのすべてがもう一方に収容されなければなりません。収容できない場合、移行先のポッドが5分間Pending状態になった後に失敗し、再作成されて再び失敗します。
- 退去マイグレーションも同時制限の対象となります。シナリオDで2台が制限(ノードあたり2)を使用している間、残りの1台は12秒間Pending状態でした。
- drain 中に移された VM のサービス断は、新規接続が 3607ms、維持接続が 3606ms の断絶でした。第 1 回の基本テストより少し長かったです。
2.2 戻しても残る退去表示
シーン C が最も意外でした。drain が失敗してノードを uncordon したのに、VMI の status.evacuationNodeName がそのまま残っていました。virt-controller はこの表示を見て退去マイグレーションを作り続け(2 回 uncordon したのにその後さらに 2 回作成された)、負荷を止めるとそのうちの 1 つが成功して 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 6 台を 1 つのノードに集めた後、VMIM 6 個を同時に作成しました。KubeVirt CR の parallelOutboundMigrationsPerNode のみを変更し、クラスターの制限(parallelMigrationsPerCluster: 5)はそのままにしました。
| ノードあたりの制限 | 6 台すべてが完了するまで | 同時に Running の最大数 | 1 件あたりの Running → Succeeded |
|---|---|---|---|
| 1 | 33.9 秒 | 1 | 2~6 秒 |
| 2(デフォルト) | 22.3 秒 | 2 | 3~4 秒 |
| 4 | 23.2 秒 | 4 | 8~9 秒 |
制限を 2 から 4 に増やしても速くなりませんでした。1 つのノードが送信できる帯域(約 90MiB/s)を分け合うため、1 件あたりの処理がその分遅くなります。小さな VM が多数ある場合は 1 から 2 に増やすときのメリット(34 秒 → 22 秒)が大きいですが、それ以上は 回線が上限になります。
マイグレーション専用ネットワーク(spec.configuration.migrations.network)は測定できませんでした(未計測)。ネストされたノードに NIC が 2 つしかなく、Multus がないため別ネットワークを作成できませんでした。この構成では、マイグレーション、NFS ディスク、サービス トラフィックのすべてが約 800Mbps の回線を共有しました。本番環境で VM が大きい場合や数が多い場合は、専用ネットワークの用意が先であり、制限の調整はその次です。
# KubeVirt CR 의 일부. 비워 두면 위 기본값(클러스터 5, 노드당 2, 대역폭 무제한)
spec:
configuration:
migrations:
parallelMigrationsPerCluster: 5
parallelOutboundMigrationsPerNode: 2
4. 本測定の限界
数値を参考にする前に条件をご確認ください。
- ネストされたクラスターです。 KubeVirt を VM 内で再度実行しているため、絶対時間は物理ノードよりも遅くなります。特に停止時間や速度は物理ノードの方が優れている可能性が高いです。
- 回線が約 800Mbps です。 10GbE 以上であれば、同じ負荷でも事前コピーが収束する可能性があります。第 1 節の「デフォルトで失敗」は「汚染速度 ≈ 回線」というこの条件の結果です。
- VM が小さいです。 基本テストは 1Gi ゲスト 1 つ、条件テストは 128Mi の cirros です。数十 GiB のゲストにおける時間は測定していません(未計測)。
- 試行回数が少ないです。 基本テスト 5 回、ポリシーテスト 1~3 回、drain シーンと同時制限のテストは 1 回ずつです。分布ではなく事例としてお読みください。
- プローブは試行ごとに 200ms の制限があるため、マイグレーションがないときでもごく短い失敗が記録されることがありました。最も長い連続区間のみを確認しています。
- 測定していない項目:autoConverge がゲストのスループットに与える影響、postCopy におけるゲストの停止時間(ターゲット側の作業情報が残らなかったため)、ボリューム マイグレーション、masquerade 以外のネットワーク バインディング、マイグレーション専用ネットワーク、multifd を無効にした場合の帯域幅の比較。
5. まとめ:ライブ マイグレーションを信頼する前に
| チェック項目 | 理由(第 1・2 回の実測値) |
|---|---|
| ディスクが RWX、ネットワークが masquerade | ではない場合、Webhook が 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 まで全 8 ステップです。
ティーティーの一言。 荷造りをしている最中に部屋を散らかし続ける友達がいると、引っ越しはいつまで経っても終わりません。そんなときは、その友達の作業をゆっくり優しく(autoConverge)してあげれば 30 秒で終わります。そして、引っ越しをキャンセルしたからといって終わりではありません。ドアに貼った「引っ越し中」のメモ(evacuationNodeName)を剥がさないと、荷物が勝手に外に出ていってしまいますよ!