Linux KVM関係

参考資料

ホスト側設定

カーネル

GentooのWikiに従って設定

KVMサポート

Virtualization  --->
  --- Virtualization
   <*>Kernel-based Virtual Machine (KVM) support
   <*>KVM for Intel processors support
   < >KVM for AMD processors support
   <*>Host kernel accelerator for virtio net (EXPERIMENTAL)

ネットワーク関係(ブリッジ接続用)

Device Drivers  --->
  Network device support  --->
     [*]   Network core driver support
       <*>     Universal TUN/TAP device driver support
Networking support  --->
   Networking options  --->
     <*> 802.1d Ethernet Bridging
      [*]IGMP/MLD snooping

macvtapを利用する(後述)

Device Drivers  ---> 
  [*] Network device support  --->
     [*]     MAC-VLAN support
     [*]       MAC-VLAN based tap driver

KSM (複数のVM間でメモリを共有する)

Processor type and features  --->
  [*] Enable KSM for page merging

関連パッケージ

ネットワーク設定

macvtapを利用する場合

ネットワークに関して当初は以下のようにしていたが、macvtapで物理NICに直接接続する方法もある。NICが複数ある場合は、余計なブリッジを構成しなくてよいので、こちらの方が便利かもしれない。ただし、いまのところGentooではmacvtapを起動スクリプトで扱えないので、仮想マシンを起動する時などにインタフェースを構成する必要がある。また、Qemuからも直接扱えないので、ネットワークデバイスに関するパラメータも少し変更が必要になる。

既にnetifrcパッケージに修正がなされつつあるようなので、Gentooの起動スクリプトでmacvtapが扱えるようになったら設定を見直そう。

macvtapデバイスの構成

以下は、/etc/init.d内のOpenRCの起動スクリプト側に記述。macvtapのデバイスファイル/dev/tapNは、インタフェースを作成した段階ではrootのみしかアクセスできない。そのためownerやpermissionを書き換えるためには、root権限で実行されているスクリプトで処理する必要がある。VMをrootで動かすならば必要ないが、別ユーザで動かしたいため。

# 「macvtap0」という名前でmacvtapデバイスを作成し、物理NICの「enp8s0f0」に接続
# modeはpassthruとする。1つのVMで1つのNICを使うので今回は何でもよい
# 複数VMが物理NICを共有する形で構成される場合、modeによってVM同士の通信の仕方が変わる。
ip link add link enp8s0f0 name macvtap0 type macvtap mode passthru
# 作成したインタフェースを起動
ip link set macvtap0 up
# Qemuは今のところ/dev/tapN経由で利用するが起動直後はデバイスファイルのownerとpermissionがrootのみrwとなっているため修正
# /sys/class/macvtap0/ifindexが/dev/tapNの「N」を示す
chown root:kvm /dev/tap$(cat /sys/class/net/macvtap0/ifindex)
chmod 660 /dev/tap$(cat /sys/class/net/macvtap0/ifindex)

Qemuのネットワークインターフェースのパラメータ

# Qemu側からはインターフェースを直接利用できないのでtapのファイルディスクリプタを指定する
# fd=3は適当な番号(3未満は標準入出力で利用されている?)
# /dev/tapNの番号は/sys/class/net/${MACVTAP}/ifindex)から取得できる
-netdev tap,id=net0,vhost=on,fd=3 3<>/dev/tap$(cat /sys/class/net/${MACVTAP}/ifindex)

# macアドレスをHostのmacvtapインターフェースとゲストで一致させないと通信できない
# たぶん、物理NICを直接VMに接続するイメージなので、macアドレスが一致していないと通信できない
# macvtapを構成する際に指定してもよいが、自動的に付与されたものを利用する
# macvtapに割り当てられたmacアドレスは/sys/class/net/${MACVTAP}/addressで取得できる
-device virtio-net,netdev=net0,mac=$(cat /sys/class/net/${MACVTAP}/address)

tap + bridgeで構成する場合

Guest用に使う物理NICを増設したので、Host側のNICはHost専用で利用する。HostとGuestが別のネットワークに接続されることを前提に、両者の通信用に内部でローカルネットワークを構成する。QEMUでuserモードのネットワークデバイスを使うこともできるが、Guest側からもHostの物理NICが接続しているネットワークに接続する可能性を考えて、Bridgeを用意する(nftablesなどでフォワード、マスカレードすることもできるが、設定が面倒なので使わないことにする)。

/etc/conf.d/net

## Hostのネットワーク設定(通常通り)
config_eth0="192.168.0.1/24"
routes_eth0="default via 192.168.0.254"

## Guestのネットワーク設定
## Bridgeを作成しGuest用物理NICとGuestのtapを接続する

# 物理NICにはIPを割り当てない
config_eth1="null"
# tapデバイスをtap0として構成しIPを空に(IPはGuest OSが設定)
tuntap_tap0="tap"
config_tap0="null"
# bridgeデバイスをbr0として構成しeth1とtap0を参加させる
bridge_br0="eth1 tap0"
# bridgeデバイスそのものにはIPアドレスを設定しない
# HostからGuest用NICがわのネットワークに出ていく場合は適当なIPを設定
config_br0="null"
# bridgeのオプション設定
bridge_forward_delay_br0=0
bridge_hello_time_br0=1000

# bridgeを起動する際の依存関係を記述
# 起動スクリプトが実行される際の依存関係
depend_br0() {
           # /etc/init.dのスクリプト名
           need net.eth1
           need net.tap0
}

以上の設定をしたうえで、/etc/init.dのnet.loからnet.br0、net.tap0など必要なデバイスへのシンボリックリンクを作成し、defaultのrunlevelに追加する。

GuestとHostが内部的に通信するためのBridgeも構成

# tap_local0というtapデバイスを作成しIPは空にしておく
tuntap_tap_local0="tap"
config_tap_local0="null"
# br_local0というbridgeデバイスを作成しtap_local0を参加
bridge_br_local0="tap_local0"
# bridge自体にIPアドレスを設定
# Hostが利用するIPアドレス。適当なローカルアドレスにする
config_br_local0="192.168.1.1/24"
bridge_forward_delay_br_local0=0
bridge_hello_time_br_local0=1000

depend_br_local0() {
           need net.tap_local0
}

br_local0というブリッジデバイスにGuestの2つ目のNICをtap経由で接続しておくことで、HostとGuestしか接続されないネットワークをつくる。ルーティングやIPフォワードの設定をしなければ、HostとGuestだけが参加する内部的なネットワークになる。GuestへのSSHなどは、基本的にHostにログインしてからこのローカルネットワークからアクセスするようにする。

ゲストOS

ディスクイメージの作成

# rawイメージで30GBのディスクイメージを作成
# preallocationでディスク容量分の領域を確保
> qemu-img create -f raw -o preallocation=full Disk01_raw.img 30G                   

Guestを起動するシェルスクリプトを適当に作成

/usr/bin/qemu-system-x86_64 \
  -name "exam_vm" \
  -machine type=q35,accel=kvm \
  -object rng-random,id=rng0,filename=/dev/urandom -device virtio-rng-pci,rng=rng0 \
  -cpu host \
  -smp 8 \
  -m 4G \
  -drive file=exam_vm_raw.img,driver=raw,if=virtio,index=0,media=disk,cache=writeback \
  -boot c \
  -netdev tap,id=net0,ifname=tap0,vhost=on,script=no,downscript=no \
  -device virtio-net,netdev=net0,mac=52:54:00:87:92:31 \
  -netdev tap,id=net1,ifname=tap_local0,vhost=on,script=no,downscript=no \
  -device virtio-net,netdev=net1,mac=52:54:00:87:92:32 \
  -monitor unix:/home/kvm-admin/socket/monitor.sock,server,nowait \
  -serial unix:/home/kvm-admin/socket/console.sock,server,nowait \
  -display none \
  -daemonize \
  -fsdev local,id=share,path=/home/kvm-admin/share_dir,security_model=mapped-xattr -device virtio-9p-pci,fsdev=share,mount_tag=host_share

オプション

 * CD-ROMを接続する場合は「-cdrom install-amd64-minimal-20200531T214503Z.iso」

ゲストOSの設定

従来はエミュレートされたハードウェアに対して通常のドライバを利用していたが、最近はGuestからHostへのアクセスは基本的にVirtio経由で行う。以下を参考に必要なドライバを組み込む。Virtio経由でアクセスするため、SATAやNICのドライバなども無効にしてよい。

時刻

Guestの時刻同期は、仮想ネットワークデバイスがハードウェアタイムスタンプをサポートしていないため、NTPではマイクロ秒レベルの正確な時刻同期ができないらしい。正確にクロックを同期するには、PTPを通してHostのクロックをハードウェアタイムスタンプで同期すればよいらしい。

カーネルのPTPドライバを有効にする

 Device Drivers  --->
    PTP clock support  --->
      [*] PTP clock support
      [*] KVM virtual PTP clock

時刻同期にPTPで時刻を同期できるchronyを利用する。

chrony.confは以下の1行のみを有効にする。server行などはすべてコメントアウト。

refclock PHC /dev/ptp0 poll 2

起動時にhwclockが走らないようにbootランレベルから削除しておく。起動時の時刻同期はカーネル機能で対応。

Device Drivers  --->
  Device Drivers  --->
    [*]   Set system time from RTC on startup and resume
    (rtc0)  RTC used to set the system time
     [*]   /sys/class/rtc/rtcN (sysfs)
     [*]   /proc/driver/rtc (procfs for rtcN)
     [*]   /dev/rtcN (character devices)
     [*]   PC-style 'CMOS'

シリアルポートにコンソールを出力

カーネルにシリアルポートのドライバ等を組み込む。「Console on 8250/16550 and compatible serial port」を有効にしないと、起動からログインプロンプトまでのカーネルメッセージや起動メッセージが表示できないので注意。PNPサポートは、どちらでもよい気がする。

Device Drivers  --->
  Character devices  --->
    Serial drivers  --->
    Serial drivers  --->
      [*] 8250/16550 and compatible serial support
      [ ]   Support 8250_core.* kernel options (DEPRECATED)
      [*]   8250/16550 PNP device support
      [ ]   Support for variants of the 16550A serial port
      [ ]   Support for Fintek F81216A LPC to 4 UART RS485 API
      [*]   Console on 8250/16550 and compatible serial port

起動オプションの変更とGrub自体の出力先をコンソールにも出すようにする。 /etc/default/grubを以下のように編集し、grub-mkconfigで反映する。

# カーネルの「console」コマンドラインオプションを追加し、tty0とttyS0(シリアルポート)の両方に
# コンソールメッセージを出力するようにする
GRUB_CMDLINE_LINUX_DEFAULT="rootfstype=ext4 zswap.enabled=1 zswap.zpool=zbud zswap.compressor=lz4 console=tty0 console=ttyS0,115200n"

# Grub自体の出力もconsoleとシリアルポートの両方に出すようにする
GRUB_TERMINAL="console serial"
# 上記パラメータにserialを追加すると以下がないと警告が出る
# シリアルポートの設定
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"

起動後の仮想コンソール(シリアルコンソール)をシリアルポートに出力する。

この設定がなければ、起動時のカーネルメッセージや起動メッセージはシリアルコンソールに表示されるが、起動してからのログインプロンプトがシリアルポートに出ないので、シリアルコンソールには何も見えなくなる。

/etc/inittab

# シリアルコンソールをttyS0に
s0:12345:respawn:/sbin/agetty -L 115200 ttyS0 vt100

socatコマンドでUNIXドメインソケット経由で仮想マシンのシリアルコンソールに接続

# escape=0x11でCtrl+qをエスケープシーケンスに指定する。socat自体を終了させる際のシーケンス
socat stdin,raw,echo=0,escape=0x11 unix-connect:console.sock