KVMとvirshによる仮想マシンのライブマイグレーション:ダウンタイムなしでホスト間をVM移行する

Virtualization tutorial - IT technology blog
Virtualization tutorial - IT technology blog

ライブマイグレーションが必要な場面 — VMをシャットダウンするだけでは済まない理由

VMをシャットダウンせずに移行しなければならない場面がある。物理ホストのRAM交換が必要な時、カーネルアップデートのためにリブートが必要な時、あるいは一方のホストのCPU負荷が高すぎて、もう一方のホストがアイドル状態の時などだ。「VMを止めて→移してから→再起動」という手順は一見シンプルに聞こえるが、トラフィックを受けているデータベースサーバーやWebサーバーにとって、たとえ30秒のダウンタイムでも実際には大きな問題になる。

KVMのライブマイグレーションはまさにこのような問題を解決するために生まれた。仕組みはなかなか興味深い:VMが通常通り動き続ける間に、RAMの状態が少しずつ移行先ホストへコピーされていく。最終段階で、元のVMはわずか数ミリ秒だけ停止し、残っているダーティページを同期させる。VMに接続しているユーザーはほとんど気づかない——pingロスは通常1〜2パケット程度だ。

私はProxmox VEで12台のVMとコンテナを管理するホームラボを運用している——本番環境に投入する前に何でもテストできる遊び場だ。ProxmoxにはライブマイグレーションのUIが備わっていてとても便利だが、実際にはその裏側でvirsh migrateを呼び出しているに過ぎない。本記事ではボタンを押すだけでなく、各ステップで何が起きているかを理解するためにraw KVM + libvirtに焦点を当てて解説する。

最初に区別しておくべきライブマイグレーションの種類が2つある:

  • シェアドストレージマイグレーション:VMのディスクがホスト間のNFS/Ceph共有ストレージ上にある → RAMの状態のみ転送するため、高速でVMへの影響が少ない。
  • ブロックマイグレーション(非共有):RAMとディスクの両方を転送する → かなり遅くなるが、共有ストレージの準備が不要。

環境の準備

ハードウェアとネットワークの要件

設定に入る前に、以下の条件を確認しておく必要がある——1つでも欠けるとマイグレーションはすぐに失敗する:

  • 両方のホストにKVM/libvirtがインストールされ、動作していること
  • CPUは同じベンダーであること——IntelからIntel、AMDからAMD。Intel/AMD間のマイグレーションは理論上ワークアラウンドがあるが、実際にはほとんど失敗する
  • 2つのホスト間のネットワーク疎通が取れていること、特にTCPポート16509(libvirtd)とポート範囲49152〜49215(QEMUマイグレーションチャンネル)
  • ブリッジネットワークの名前が両方のホストで同一であること——host1がbr0を使い、host2がvirbr0を使っていると、マイグレーション後にVMはネットワーク接続を失う
# 各ホストのブリッジネットワーク名を確認する
ip link show type bridge

必要なパッケージのインストール

両方のホストで実行する:

# Ubuntu/Debian
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients \
    virtinst bridge-utils

# RHEL/CentOS/Rocky Linux
sudo dnf install -y qemu-kvm libvirt virt-install bridge-utils
sudo systemctl enable --now libvirtd
# libvirtdが起動していることを確認する
sudo systemctl status libvirtd
sudo virsh list --all

ライブマイグレーションを動作させるための詳細設定

ステップ1 — libvirtdをリモート接続に対応させる

デフォルトでは、libvirtdはローカルソケットのみでリッスンしている。2つのホスト間でマイグレーションを機能させるには、TCPトランスポートを有効にする必要がある。

両方のホスト/etc/libvirt/libvirtd.confを編集する:

sudo nano /etc/libvirt/libvirtd.conf

以下の行を追加するか、コメントを解除する:

# TCPトランスポートを有効にする
listen_tcp = 1
listen_addr = "0.0.0.0"
tcp_port = "16509"

# 内部テスト用に認証を無効にする
# (本番環境ではsaslまたはTLSを使用する)
auth_tcp = "none"

デーモンに--listenフラグを有効にする:

# Ubuntu/Debian
sudo sed -i 's/#LIBVIRTD_ARGS=""/LIBVIRTD_ARGS="--listen"/' /etc/default/libvirtd

# RHEL/CentOS
sudo sed -i 's/#LIBVIRTD_ARGS="--listen"/LIBVIRTD_ARGS="--listen"/' /etc/sysconfig/libvirtd

sudo systemctl restart libvirtd

# ポートが開いていることを確認する
ss -tlnp | grep 16509

ステップ2 — SSH公開鍵認証の設定

SSH経由のvirsh migrateには、パスワードなしで接続できる環境が必要だ。ソースホストから実行する:

# マイグレーション専用のSSHキーを作成する
ssh-keygen -t ed25519 -f ~/.ssh/id_migration -N ""

# 宛先ホスト(192.168.1.102)にコピーする
ssh-copy-id -i ~/.ssh/id_migration.pub [email protected]

# 接続をテストし、宛先ホストのVMを確認する
ssh -i ~/.ssh/id_migration [email protected] "virsh list --all"

ステップ3 — NFSシェアドストレージのセットアップ

NFSシェアドストレージはこのセットアップ全体の基盤となる。VMのディスクは、両方のホストで同じパスにマウントされたNFS上に置かなければならない——この点が間違っていると、マイグレーション時にディスクが見つからないというエラーが発生する。

NFSサーバー(第3のホストでも、2つのKVMホストのどちらか一方でも可)でインストールとエクスポートを設定する:

sudo apt install -y nfs-kernel-server
sudo mkdir -p /srv/kvm-shared

# ディレクトリをLANネットワークにエクスポートする
echo "/srv/kvm-shared 192.168.1.0/24(rw,sync,no_root_squash,no_subtree_check)" | \
    sudo tee -a /etc/exports
sudo exportfs -a
sudo systemctl restart nfs-kernel-server

両方のKVMホストにマウントする:

sudo apt install -y nfs-common
sudo mkdir -p /var/lib/libvirt/images/shared

# NFSをマウントする(192.168.1.100はNFSサーバーのIPアドレス)
sudo mount -t nfs 192.168.1.100:/srv/kvm-shared /var/lib/libvirt/images/shared

# 再起動後に自動マウントされるよう/etc/fstabに追加する
echo "192.168.1.100:/srv/kvm-shared /var/lib/libvirt/images/shared nfs defaults,_netdev 0 0" | \
    sudo tee -a /etc/fstab

最初からNFS共有ストレージ上にディスクを配置したVMを新規作成する:

sudo virt-install \
  --name testvm \
  --ram 2048 \
  --vcpus 2 \
  --disk path=/var/lib/libvirt/images/shared/testvm.qcow2,size=20 \
  --os-variant ubuntu22.04 \
  --network bridge=br0 \
  --graphics none \
  --location /var/lib/libvirt/images/ubuntu-22.04-live-server-amd64.iso,kernel=casper/vmlinuz,initrd=casper/initrd

virshによるライブマイグレーションの実行

ソースホストをhost1 (192.168.1.101)、宛先をhost2 (192.168.1.102)とする。testvmという名前のVMがhost1上で稼働しているものとする。

SSH経由のマイグレーション

# 基本的な構文
virsh migrate --live testvm qemu+ssh://[email protected]/system

# --verboseを追加して進捗を確認する
virsh migrate --live --verbose testvm qemu+ssh://[email protected]/system

TCPトランスポート経由のマイグレーション(LAN内では高速)

virsh migrate --live --verbose \
  testvm \
  qemu+tcp://192.168.1.102/system

シェアドストレージがない場合のブロックマイグレーション

--copy-storage-allを使ってディスクも含めて転送する。ギガビットLAN環境では20GBのディスクで約3〜5分かかり、その間VMは動き続けるがI/Oパフォーマンスは低下する:

virsh migrate --live \
  --copy-storage-all \
  --verbose \
  testvm \
  qemu+ssh://[email protected]/system

最終ダウンタイムのチューニング

マイグレーションの最終段階で、VMは残りのダーティページを同期するために数ミリ秒停止しなければならない。デフォルトは300msだ——VMのワークロードが重い場合は、タイムアウトで失敗する代わりにマイグレーションを完了させるため、少し増やすとよい:

# 最終段階で最大500msのダウンタイムを許容する
virsh migrate --live \
  --migrate-setmaxdowntime 500 \
  testvm \
  qemu+ssh://[email protected]/system

確認とモニタリング

VMの移行成功を確認する

# host1上 — testvmはここにはもういない
virsh list --all

# host2上 — testvmが稼働中
virsh list --all
# 期待される出力:
# Id   Name     State
# 1    testvm   running

マイグレーション進捗のリアルタイムモニタリング

# マイグレーション中にソースホストでこのコマンドを実行する
watch -n 1 "virsh domjobinfo testvm"

# 出力内容:
# Job type:         Unbounded
# Time elapsed:     3421 ms
# Data processed:   768 MiB
# Data remaining:   64 MiB
# Memory processed: 768 MiB
# Memory remaining: 64 MiB

ネットワーク切断がないことを確認する

# VMのIPアドレスを取得する
virsh domifaddr testvm

# マイグレーション実行中に外部マシンから継続的にpingを送る
ping -i 0.2 <testvmのIPアドレス>
# マイグレーションが成功した場合、最大でも1〜2パケットロスのみ発生する

よくあるエラーの対処法

# ソースホストのlibvirtログを確認する
sudo tail -f /var/log/libvirt/libvirtd.log

# 特定のVMのQEMUログ
sudo tail -f /var/log/libvirt/qemu/testvm.log

ホームラボでテストしているときによく遭遇するエラーをいくつか紹介する:

  • “Unable to allow access for disk path”:VMのディスクがNFSではなくローカルストレージにある。先にディスクをNFS共有ストレージに移動する必要がある(VMを停止し、ファイルをコピーして、virsh editでパスを更新してから再起動する)。
  • “authentication failed”libvirtd.confauth_tcp = "none"を確認し、デーモンを再起動する。
  • マイグレーションがタイムアウトする:VMのI/Oワークロードが重く、マイグレーションがコピーできる速度よりも速くダーティページが生成されている。--migrate-setmaxdowntimeを増やすか、マイグレーション前にVMの負荷を下げてみる。
  • マイグレーション後にVMがネットワーク接続を失う:2つのホスト間でブリッジ名が異なる。ブリッジネットワークの名前を統一するのが根本的な解決策だ。

Share: