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
関連パッケージ
- app-emulation/qemu – KVMの基本ユーティリティ
- QEMU_SOFTMMU_TARGETS=“x86_64” – フルモードでエミュレートするアーキテクチャ
- QEMU_USER_TARGETS=“” – ユーザランドモードでエミュレートするアーキテクチャ
- net-misc/bridge-utils – ブリッジネットワーク関連のユーティリティ
- sys-apps/usermode-utilities – tunctlコマンドをインストールするため
ネットワーク設定
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の物理NICの最近の命名規則は違っているが、とりあえずeth+Nとする。
- eth0はHost用のNIC(マザーボード)
- eth1はGuest用に増設したNIC
## 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
オプション
- -name “VM01”
- 仮想マシンの名称
- -machine type=q35,accel=kvm
- 仮想マシンのタイプ:マザーボードの基本構成のようなもの
- KVMを有効にする。QEMUは汎用的な仮想マシンエミュレータでKVMでカーネルの支援が有効になる
- -object rng-random,id=rng0,filename=/dev/urandom -device virtio-rng-pci,rng=rng0
- 乱数生成器などを仮想マシンから見えるようにする
- -cpu host
- cpuのタイプ。他の種類のCPUもエミュレートできるがHostをそのまま使うのが一番効率がよい
- -smp 8
- 割り当てるCPUの数。NUMA構成などもできるようだが単純にコア数だけ指定
- -m 4G
- 割り当てるメモリ
- -drive file=exam_vm_raw.img,driver=raw,if=virtio,index=0,media=disk,cache=writeback
- 仮想ハードディスクの設定。作成した仮想ディスクのイメージとフォーマットを指定。インターフェイスはvirtio
- cacheやaioなどの設定によってディスクのパフォーマンスをが変わる。HostがSSDかHDDか、メモリの量などで調整が必要
- boot c
- 起動デバイスの指定。cはハードディスク。dはCD-ROM。インストール時は起動ディスクのisoをCD-ROMとして設定してdから起動。
* CD-ROMを接続する場合は「-cdrom install-amd64-minimal-20200531T214503Z.iso」
- -netdev tap,id=net0,ifname=tap0,vhost=on,script=no,downscript=no
- ネットワークのバックエンドの設定。tapで先ほど作成したtap0に接続する。idは仮想マシン側に見せるデバイスの設定に利用。
- scriptは仮想マシン起動時にtapデバイスを作成する場合に利用できる。ここでは静的に設定しているのでスクリプトは利用なし。
- -device virtio-net,netdev=net0,mac=52:54:00:87:92:31
- Guest側に見えるNICの設定。virtio-net経由でidで指定したバックエンド(net0)に接続。
- -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
- 同様に、Hostと通信するためだけに作成したBridgeに接続しているNICもGuestに作成
- -monitor unix:/foo/var/vm01.sock,server,nowait
- QEMUモニタに接続するためのソケット。telnetでモニタにアクセスできるようにもできるが、telnetの場合、複数の仮想マシンを運用すると、各仮想マシンのモニタの区別はポート番号のみになり、管理しにくい。ソケットであれば、ソケット名をわかりやすく設定できるので便利。
- ソケットへのアクセスはsocatコマンドで可能。
- socat - UNIX-CONNECT:/foo/bar/vm01.sock
- 上記コマンドにパイプでモニタコマンドを与えてやると直接モニタを操作できる。例えば「echo “system_powerdown” |socat - UNIX-CONNECT:/foo/bar/vm01.sock」とすれば、仮想マシンに電源ボタンを押下したことを伝達できる。仮想マシン上のカーネルで電源ボタンの押下を検知できるようにし、acpidなどを設定しておくとHostから手軽にシャットダウンできる。
- telnetの場合は「-monitor telnet:127.0.0.1:10100,server,nowait」。10100番ポートにtelnetするとモニタにアクセスできる。
- -serial unix:/foo/var/console.sock,server,nowait
- LinuxやGrubのコンソールを出すためのシリアルポートを仮想マシン上に作成し、その出力をUNIXドメインソケットとして/foo/var/console.sockに接続する。シリアルコンソールに関する他の設定は後述。
- -display none
- 本番でサーバ運用の場合はディスプレイは不要なのでnone。
- インストール時はcursesとすれば、sshなどのコマンドラインに直接Guestのコンソールを接続できる。ただし、テキストモードの場合のみ。グラフィックモードの場合はvncなどが必要。
- -daemonize
- 本番時はターミナルから切り離して起動。
- -fsdev local,id=share,path=/foo/bar/share_dir,security_model=mapped-xattr
- -device virtio-9p-pci,fsdev=share,mount_tag=v_share
- HostのディレクトリをGuestと共有。Guest側カーネルで9pのオプションを有効にする必要がある。
- fsdevで共有するディレクトリのバックエンドを設定し、deviceでGuestから見えるデバイスを生成する。mount_tagはGuestからmountする場合のデバイス名となる。
ゲスト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