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で物理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)
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にログインしてからこのローカルネットワークからアクセスするようにする。
ディスクイメージの作成
# 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」
従来はエミュレートされたハードウェアに対して通常のドライバを利用していたが、最近は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