KVM でネストゲストを動かす

ネストゲストとは、あるゲストの中でさらに別のゲストを動かせることである (KVM ベースでも、別のハイパーバイザでもよい)。最も分かりやすい例は、KVM ゲストの上でさらに KVM ゲストを動かす場合である(以降の説明はこの例を前 提にする):

        .----------------.  .----------------.
        |                |  |                |
        |      L2        |  |      L2        |
        | (Nested Guest) |  | (Nested Guest) |
        |                |  |                |
        |----------------'--'----------------|
        |                                    |
        |       L1 (Guest Hypervisor)        |
        |          KVM (/dev/kvm)            |
        |                                    |
.------------------------------------------------------.
|                 L0 (Host Hypervisor)                 |
|                    KVM (/dev/kvm)                    |
|------------------------------------------------------|
|        Hardware (with virtualization extensions)     |
'------------------------------------------------------'

Terminology: - L0 – レベル 0。ベアメタルのホストで、KVM を動かす。

  • L1 – レベル 1 のゲスト。L0 上で動く VM で、「ゲストハイパーバイザ」とも呼ぶ。それ自体が KVM を動かせるためである。

  • L2 – レベル 2 のゲスト。L1 上で動く VM で、これが「ネストゲスト」である。

注釈

上の図は x86 アーキテクチャを基にしている。s390x や ppc64 など他のアーキテクチャでは、ネストの設計が異なる可能性が高い。

たとえば s390x では、常にベアメタル上で LPAR(LogicalPARtition)ハイパーバイザが動いており、層がもう 1 つ増えるため、ネスト構成は少なくとも 4 つのレベルになる — L0(ベアメタル、LPAR ハイパーバイザを実行)、L1(ホストハイパーバイザ)、L2(ゲストハイパーバイザ)、L3(ネストゲスト)。

本書ではすべてのアーキテクチャについて 3 レベルの用語(L0、L1、L2)で統一し、主に x86 を扱う。

利用例

ネスト KVM が役立つ場面はいくつかある。いくつか挙げると:

  • 開発者として、自分のソフトウェアをさまざまなオペレーティングシステム(OS)でテストしたい場合。クラウドプロバイダから複数の VM を借りる代わりに、ネスト KVM を使えば、十分な大きさの「ゲストハイパーバイザ」(レベル 1 のゲスト)を 1 つ借りられる。その上で、異なる OS を動かす複数のネストゲスト(レベル 2 のゲスト)を作り、そこでソフトウェアを開発・テストできる。

  • 負荷分散や災害復旧などのための、「ゲストハイパーバイザ」とそのネストゲストのライブマイグレーション。

  • VM イメージ作成ツール(virt-install など)はしばしば自前の VM を動かすが、利用者はそれが VM の中で動くことを期待する。

  • 一部の OS は、セキュリティのために内部で仮想化を使う(たとえば、アプリケーションを安全に分離して実行させるため)。

「nested」を有効にする(x86)

Linux カーネル v4.20 以降では、Intel と AMD の ``nested``KVM パラメータ はデフォルトで有効である(ただし、使用している Linux ディストリビューシ ョンがこのデフォルトを上書きしているかもしれない)。

v4.19 より古い Linux カーネルを使っている場合は、ネストを有効にするには ``nested``KVM モジュールパラメータを ``Y``または ``1``に設定する。この設定を再起動後も 保持するには、次のように設定ファイルに書けばよい:

  1. ベアメタルのホスト(L0)でカーネルモジュールを一覧し、KVM モジュールがあることを確認する:

    $ lsmod | grep -i kvm
    kvm_intel             133627  0
    kvm                   435079  1 kvm_intel
    
  2. kvm_intel モジュールの情報を表示する:

    $ modinfo kvm_intel | grep -i nested
    parm:           nested:bool
    
  3. ネスト KVM の設定を再起動後も保持するには、以下を /etc/modprobed/kvm_intel.conf に書く(ファイルがなければ作成する):

    $ cat /etc/modprobe.d/kvm_intel.conf
    options kvm-intel nested=y
    
  4. KVM Intel モジュールをアンロードして再ロードする:

    $ sudo rmmod kvm-intel
    $ sudo modprobe kvm-intel
    
  5. KVM の nested パラメータが有効になっているか確認する:

    $ cat /sys/module/kvm_intel/parameters/nested
    Y
    

AMD ホストでも手順は上記と同じだが、モジュール名が ``kvm-amd``になる。

ネスト関連の追加カーネルパラメータ(x86)

ハードウェアが十分に新しい場合(新しいハードウェア仮想化拡張を持つ Intel Haswell プロセッサ以降)、次の追加機能もデフォルトで有効になる。「Shadow VMCS(Virtual Machine Control Structure)」と、ベアメタルのホスト(L0) での APIC 仮想化である。Intel ホストのパラメータ:

$ cat /sys/module/kvm_intel/parameters/enable_shadow_vmcs
Y

$ cat /sys/module/kvm_intel/parameters/enable_apicv
Y

$ cat /sys/module/kvm_intel/parameters/ept
Y

注釈

L2(すなわちネストゲスト)の動作が遅いと思われる場合は、上記が有効になっていることを確認する(特に enable_shadow_vmcs と ept)。

ネストゲストを起動する(x86)

ベアメタルのホスト(L0)でネストの設定が済めば、次のようにして L1 ゲスト を起動できるはずである:

$ qemu-kvm -cpu host [...]

上記はホスト CPU のケイパビリティをそのままゲストにパススルーする。ライ ブマイグレーションの互換性を高めたい場合は、QEMU が対応する名前付き CPU モデルを使う。例:

$ qemu-kvm -cpu Haswell-noTSX-IBRS,vmx=on

そうすれば、ゲストハイパーバイザは以降、アクセラレーションされた KVM で ネストゲストを動かせるようになる。

「nested」を有効にする(s390x)

  1. ホストハイパーバイザ(L0)で、s390x の nested パラメータを有効にする:

    $ rmmod kvm
    $ modprobe kvm nested=1
    

注釈

s390x では、カーネルパラメータ hpage と nested パラメータは同時に使えない — つまり nested を有効にするには、hpage パラメータを無効に*しなければならない*。

  1. ゲストハイパーバイザ(L1)には sie CPU 機能を渡す必要がある — QEMU では「host passthrough」(コマンドラインの -cpu host)を使えばよい。

  2. これで L1(ゲストハイパーバイザ)で KVM モジュールをロードできる:

    $ modprobe kvm
    

ネスト KVM でのライブマイグレーション

*実行中*のネストゲストを内包する L1 ゲストを別のベアメタルホストへマイグ レーションする機能は、Intel x86 システムでは Linux カーネル 5.3 と QEMU 4.2.0 以降で動作し、s390x ではそれより古いバージョンでも動作する。

AMD システムでは、L1 ゲストが L2 ゲストを起動したあとは、L2 ゲストが停止 するまで L1 ゲストをマイグレーションしたり保存したりすべきではない(QEMU ドキュメントの「savevm」/「loadvm」を参照)。L2 ゲストの実行中に L1 ゲス トをマイグレーションまたは保存・読み込みしようとすると、動作は未定義にな る。``dmesg``に ``kernelBUG!``が出たり、カーネルの oops や、最悪の場合は カーネルパニックが起きたりするかもしれない。そのようにマイグレーションま たは読み込みされた L1 ゲストは、もはや安定とも安全ともみなせないので、再 起動しなければならない。ネストに対応するよう設定しただけで実際には L2 ゲ ストを動かしていない L1 ゲストのマイグレーションは、AMD システムでも通常 どおり動作するはずだが、ゲストを起動すると失敗するかもしれない。

L2 ゲストのマイグレーションは常に成功するはずなので、次のどの場面も AMD システムで動作するはずである:

  • ネストゲスト(L2)を*同じ*ベアメタルホスト上の別の L1 ゲストへマイグレーションする。

  • ネストゲスト(L2)を*別の*ベアメタルホスト上の別の L1 ゲストへマイグレーションする。

  • ネストゲスト(L2)をベアメタルホストへマイグレーションする。

ネスト構成でのバグ報告

「ネスト」の問題をデバッグするには、L0、L1、L2 にまたがるログファイルを 調べる必要がある。そのため、バグの報告者と修正者の間で面倒なやり取りが何 度も起きがちである。

  • 「ネスト」構成で動かしていることを伝える。何らかの「ネスト」を動かしているなら、そう書く。残念ながらこれは明示する必要がある。バグを報告するとき、人はネスト仮想化を使っていることさえ*書くのを*忘れがちだからである。

  • 実際に KVM on KVM で動かしていることを確認する。ゲストハイパーバイザ(L1)で KVM を有効にしておらず、純粋なエミュレーション、いわゆる QEMU でいう「TCG」で動かしているのに、ネスト KVM を動かしていると思い込んでいる人がいる。こうして「nested Virt」(KVM 上の QEMU を指すこともある)と「nested KVM」(KVM on KVM)を混同してしまう。

収集する情報(共通)

以下は完全な一覧ではないが、非常に良い出発点になる:

  • L0 のカーネル、libvirt、QEMU のバージョン

  • L1 のカーネル、libvirt、QEMU のバージョン

  • L1 の QEMU コマンドライン — libvirt を使っている場合はここにある: /var/log/libvirt/qemu/instance.log

  • L2 の QEMU コマンドライン — 上と同様に、libvirt を使っている場合は libvirt が生成した完全な QEMU コマンドラインを取得する

  • L0 の cat /sys/cpuinfo

  • L1 の cat /sys/cpuinfo

  • L0 の lscpu

  • L1 の lscpu

  • L0 の dmesg の全出力

  • L1 の dmesg の全出力

x86 固有の収集情報

以下に挙げる ``x86info``と ``dmidecode``のコマンドは、ほとんどの Linux ディストリビューションで同じ名前で利用できるはずである:

  • L0 での x86info -a の出力

  • L1 での x86info -a の出力

  • L0 での dmidecode の出力

  • L1 での dmidecode の出力

s390x 固有の収集情報

前述の共通の情報に加えて、以下も推奨する:

  • L1 の /proc/sysinfo。これには L0 の情報も含まれる