Qemu小ネタ集

執筆者 : 箕浦 真


前回記事が重かったので、今回は小ネタを集めてみた。

1. Qemuを生で起動

大抵の場合、Qemuを直接起動することはあまりなく、libvirt経由で起動することが多いと思う*1。特に、RHEL系ではQemuが/usr/libexec/qemu-kvmなどというところに置かれていることから分かるように、Qemuを直接起動することは想定されていない。しかしながら、筆者のようにさまざまな特殊な動かし方をしたり、起動時のデバッグをおこなったりといった用途では、直接起動せざるを得ないことがある。

1.1 libvirtからの起動を模擬したい場合

libvirtから起動するのと近い動作をさせたい場合、/var/log/libvirt/qemu/にあるログにコマンドラインが記録されているので、これを参考にする。ところどころ、libvirtが動的に作成したリソースをQemuに渡すような場合があり、注意が必要である。また、KVMを使う場合、Qemuを実行するユーザーがkvmグループ (ディストリビューションによって異なるかもしれない) に属している必要がある。

マスターキー

-object '{"qom-type":"secret",...のような指定がある。これは、マスターキーという暗号化された乱数である。通常このオプションはそのまま削っても良い。

Qemuモニター

libvirtがQemuを制御するのに利用しているのがQemu monitorである。libvirtでは、/var/lib/libvirt/qemu/domain-ID-NAME/monitor.sockにUnixソケットを作成し、そのファイルデスクリプターを渡す形がとられている。

Qemuモニターはデバイスではないが、シリアルポートなどのキャラクターデバイスと同様のバックエンドを接続する形をとっている。id=charmonitorという-chardevオプションを探し、置き換える。たとえば、Qemuの標準入出力をQemuモニターに接続する場合、-chardev socket,id=charmonitor,fd=29,server=on,wait=offのようなオプションを、-chardev stdio,id=charmonitorなどと置き換える。-chardev socket,id=charmonitor,path=/tmp/monitor.sock,server=onなどとして、netcatコマンドなどで接続しても良い。

仮想ネットワーク

libvirtは多様なネットワークが扱えるが、既定のdefaultネットワークに接続していることが多いと思う。この実態は、virbr0というLinuxブリッジであり *2、ここに勝手に接続しても特に問題なく動いてくれるので、筆者の場合以下のようなスクリプトを用意し、

#!/bin/sh
/usr/bin/ip link set "$1" master virbr0

-netdevオプションを置き換えることが多い (-netdev tap,fd=32,id=hostnet0,vhost=on,vhostfd=34-netdev tap,id=hostnet0,script=/tmp/ifupなど)。

ブリッジに組み込むため、管理者特権が必要となる。sudoしたくない場合などは、-netdev user,id=hostnet0を使う。

1.2 とにかくQemuが使いたい場合

libvirtは特に冗長なコマンドラインオプションを使うが、単にQemuを使いたい場合はもっと簡便な書き方もある。例えば、ubuntuで

$ qemu-system-x86_64 -m 1G -accel kvm -cdrom .../ubuntu-22.04-live-server-amd64.iso

のようにすると、ウィンドウが現れてubuntuのインストーラーが起動してくる*3。このウィンドウは、暗黙に指定された-display gtkによるもので*4、ViewメニューからQemuモニターやシリアルポートを選択して操作することもできる*5qemu-imgでディスクイメージを作り、-hda some-disk.imgのようにすると、そこへインストールすることもできるはずである。このとき、仮想マシン (VM) には既定のデバイスが接続されており、たとえばuserバックエンドのIntel i82540EM NIC (いわゆるe1000) が見える。-hdaのディスクとCD-ROMはチップセット内蔵のIDEインターフェイスに接続される。

GUI環境でない場合、

qemu-system-x86_64 -m 1G -accel kvm -nographic -cdrom .../ubuntu-22.04-live-server-amd64.iso

のようにすると、標準入出力がシリアルポートに接続され、シリアルコンソールによる運用ができる。VMにはVGAデバイスが接続されているため、grubの画面でカーネルのコマンドラインにconsole=ttyS0を追加する必要がある*6

なお、このとき標準入出力はQemuモニターとも接続されており、Control-A、続いてcを押すことで互いに切り替えることができる*7

2. Qemuモニター

Qemuモニターは、ユーザーからさまざまな制御コマンドを受け付けたり、逆にユーザーへVMで発生したイベントを送信したりするチャネルである。旧来から人間がコマンドを入力するために利用していたが、後にプログラム (libvirtなど) から制御するのに適したJSONベースのプロトコルが追加されている。前章で、特にオプション指定をしなくても暗黙のうちに起動されたモニターは、人間が操作することが前提となっており、HMP (Human Monitor Interface: Pはどこへ行ったのだろう) と呼ばれる。またlibvirtが起動した時のオプション指定で起動されるモニターは、QMP (Qemu Machine Protocol) と呼ばれ、プログラムが利用する前提となっているほか、VMの起動や終了などのイベントを取得することもできる。

Qemuモニターは、HMP、QMPともQemu起動オプションで幾つでも起動できる。モニターを追加する起動オプションは何種類かあるが、-chardevでキャラクターデバイスバックエンドを定義し、それを利用する場合には-monitor chardev=CHARDEV_IDを利用するやり方が自由度が高い。-monitorにはmodeが指定でき、HMPモニターを追加する場合はmode=readline、QMPモニターを追加したい場合はmode=controlとする。

2.1 HMP (readline)

こちらは人間が操作するものであるため、helpコマンドが使える (helphelp info)。筆者が使うものを例示すると、

  • gdbserver PORT: VMのカーネルをデバッグするために、gdbを接続する端点を作る。gdbからは、target remote localhost:PORTなどとする。

  • c (cont): VMの実行を再開する。起動時に-Sオプションをつけると、一時停止状態で起動するため、この状態でVNCやSPICEのクライアントを接続し、モニターからcと入力することで、VMを起動させるような使い方をする。逆に一時停止するのはs (stop) である。

  • sendkey: 手元の端末が吸い取ってしまうとか、VMと端末とでキーボード配列が違うなどで、入力できないキーを入力するのに利用できる。例えば、Alt-Tabを入力するには、sendkey alt-tabとする。

  • info qtree: VMに接続されているデバイスを列挙する。

2.2 QMP (control)

こちらは機械が操作する前提であるため、info qtreeのような便利なものはない。リファレンスを参照すると、膨大なコマンドが用意されていることがわかる。入出力ともJSON形式で、コマンドの実行は{"execute": "COMMAND", "arguments": ARGUMENTS}のような形式で行う。

まず、お約束としてqmp_capabilitiesコマンドを実行する。

{"execute": "qmp_capabilities"}
{"return": {}}

たとえば、ディスクイメージのミラーを作るには、drive-mirrorコマンドを利用する。まず、query-blockコマンドで、ディスクの内部名を調べる。出力は見やすさのためjqを通した。必要なのは、"device"または"node-name"である。

{"execute":"query-block"}
{
  "return": [
    {
      "io-status": "ok",
      "device": "ide0-hd0",
      "locked": false,
      "removable": false,
      "inserted": {
        "iops_rd": 0,
        "detect_zeroes": "off",
        "image": {
          "virtual-size": 107374182400,
          "filename": "test.qcow2",
          "cluster-size": 65536,
          "format": "qcow2",
[中略]
          "dirty-flag": false
        },
        "iops_wr": 0,
        "ro": false,
        "node-name": "#block161",
[中略]
        "file": "test.qcow2"
      },
      "qdev": "/machine/unattached/device[24]",
      "type": "unknown"
    },
    {
      "io-status": "ok",
      "device": "ide1-cd0",
[以下略]

"device"または"node-name"を指定してdrive-mirrorを実行する。

{"execute":"drive-mirror", "arguments":{"device":"#block161", "target":"mirror.qcow2", "sync":"full", "format":"qcow2"}}
Formatting 'mirror.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=107374182400 lazy_refcounts=off refcount_bits=16
{"timestamp": {"seconds": 1684391400, "microseconds": 195622}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "ide0-hd0"}}
{"timestamp": {"seconds": 1684391400, "microseconds": 196228}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "ide0-hd0"}}
{"return": {}}

"timestamp"で始まる行は、Qemuから送られてくるイベント情報である。どこまでミラーが進んだかは、query-block-jobsで調べられる。

{"execute":"query-block-jobs"}
{"return": [{"auto-finalize": true, "io-status": "ok", "device": "ide0-hd0", "auto-dismiss": true, "busy": true, "len": 7685668864, "offset": 5225054208, "status": "running", "paused": false, "speed": 0, "ready": false, "type": "mirror"}]}

上記なら、5225054208/7685668864で、約68%まできていることがわかる。しばらくすると、終わったというイベントが送られてくる。

{"timestamp": {"seconds": 1684391585, "microseconds": 984923}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "ide0-hd0"}}
{"timestamp": {"seconds": 1684391585, "microseconds": 985061}, "event": "BLOCK_JOB_READY", "data": {"device": "ide0-hd0", "len": 7685668864, "offset": 7685668864, "speed": 0, "type": "mirror"}}

この後、block-job-completeを実行すると、ディスクイメージが新しいファイルに挿し変わり、古いファイルは切り離される。代わりに、block-job-cancelを実行すると、新しいファイルが切り離され、古いファイルのみが使われるようになる。どちらかを実行するまでは、両方のイメージが更新され続ける。

{"execute":"block-job-cancel", "arguments":{"device": "ide0-hd0"}}
{"return": {}}
{"timestamp": {"seconds": 1684391824, "microseconds": 200237}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "ide0-hd0"}}
{"timestamp": {"seconds": 1684391824, "microseconds": 200341}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "ide0-hd0"}}
{"timestamp": {"seconds": 1684391824, "microseconds": 201503}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "ide0-hd0", "len": 7695040512, "offset": 7695040512, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": 1684391824, "microseconds": 201576}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "ide0-hd0"}}
{"timestamp": {"seconds": 1684391824, "microseconds": 201621}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "ide0-hd0"}}

2.3 libvirt配下のQemuに対するモニターコマンド

libvirt配下のQemuは、QMPモニターのUnixソケットを持っており、常にlibvirtdと繋がっている。libvirtにはこのモニターにコマンドを送るAPIがあり、またvirshにも対応するqemu-monitor-commandというコマンドがある。

$ virsh qemu-monitor-command --hmp VM info qtree
bus: main-system-bus
  type System
  dev: kvm-ioapic, id ""
    gpio-in "" 24
    gsi_base = 0 (0x0)
    mmio 00000000fec00000/0000000000001000
[以下略]
$ virsh qemu-monitor-command VM '{"execute":"query-blockstats"}' | jq .
{
  "return": [
    {
      "device": "",
      "parent": {
        "stats": {
          "unmap_operations": 0,
          "unmap_merged": 0,
          "flush_total_time_ns": 0,
[以下略]

qmp_capabilitiesコマンドはすでにlibvirtが実行しているため、あらためて実行する必要はない。

前節で挙げたようなコマンドは、libvirtの知らないところでQemuの状態を変えてしまうので、望ましくない。多くの操作はlibvirt API (対応するvirshコマンド) があるため、これを使うべきである。たとえば前節のディスクイメージのコピーは、virsh blockcopyというコマンドでできる。

また、Qemuから送られてくるイベントは、qemu-monitor-commandでは受け取れず、別途libvirt APIを使って受け取る。

3. リモートからのvirt-manager

リモートマシンのQemu/KVMをGUIで管理するのには、当該リモートマシンでvirt-managerを実行する方法と、リモートマシンで動作するlibvirtdを手元のマシンで動作するvirt-managerで管理する方法とがある。筆者が手元で使っているのはmacbookなので、前者を利用している*8

3.1 リモートでvirt-managerを動かす

virt-managerは、X11アプリケーションで (も) あるので、sshのX11 forwardingの機能で容易に手元に画面を持ってくることができる。リモートマシンのユーザーがlibvirtグループに入っていることを確認した上で、

$ ssh -Xf REMOTE virt-manager

(REMOTEは、リモートマシンのIPアドレス)

これで、(自動的にXQuartzが起動して) macの画面にvirt-managerのウィンドウが表示される。しかし、virt-managerはWaylandアプリケーションでもあるので、注意点がある。

  1. リモートマシンでWaylandセッション (gnome-shellなど) が動いていると、virt-managerはそちらのWaylandへ行ってしまう。これを防ぐには、環境変数GDK_BACKENDをx11にするか、--display localhost:10.0などとしてX11ディスプレイを指定する必要がある*9

  2. virt-managerを複数起動することはできず、2度目以降の起動では、起動済みのvirt-managerがフォアグラウンドへ出るだけである。このため、リモートでWaylandセッションが動いているのを忘れて上記を実行すると、何度実行しても特にエラーを表示することもなく手元には何もこなくてハマることになる。

3.2 リモートvirt-managerを使って、音声を聞く

筆者の仕事ではあまりあることではないが、VMのサウンド機能を使いたいことがあるかもしれない。この場合は、pulseaudioを利用する。macOSでもHomebrewなどでpulseaudioが容易にインストールできるので、これを利用するが、~/.config/pulse/cookieを共有しておく (リモートと手元で同一内容にする) こと、module-native-protocol-tcpモジュールをロードしておくこと、という2点を確認しておきたい。後者は、pactlコマンドを使うか、/usr/local/etc/pulse/default.pa (Homebrewでインストールした場合) を~/.config/pulse/default.paにコピーし、#load-module module-native-protocol-tcpという行のコメントを外した上で、brew services restart pulseaudioなどとしてpulseaudioを再起動する。サウンドを再生する場所は、環境変数PULSE_SERVERで指定する。

$ ssh -Xf REMOTE env PULSE_SERVER=LOCAL virt-manager

(LOCALは、macbookのIPアドレス)

または、TCPポート4713をsshで適切に転送する。なお、遅延が大きかったりするので、間違ってもVMで音声付き動画を再生しようなどと考えてはならない (笑)。

4. ディスクイメージ操作

VMの外から仮想ディスク上のファイルを操作したいことはよくある。ここでは、オフラインの仮想ディスクイメージを操作する方法を2つ紹介する。

4.1 guestfish

guestfishは、Qemuの仮想ディスクイメージに対してさまざまな操作するコマンドである*10。裏でQemuを動かしてLinuxを起動しているため、LVM上のものを含めてLinuxで扱えるあらゆるファイルシステムが扱える。guestfishは、スクリプトから起動するために、すべてをコマンドライン引数で指定することもできるが、ここでは対話的に利用する例を紹介する。

$ sudo guestfish -a test.qcow2

Welcome to guestfish, the guest filesystem shell for
editing virtual machine filesystems and disk images.
[中略]
><fs> run
><fs> list-filesystems
/dev/sda1: unknown
/dev/sda2: ext4
/dev/ubuntu-vg/ubuntu-lv: ext4
><fs>

ubuntuではsudoしないと動作しない。これは、裏で動かすQemuの利用するカーネルとして、ホストのvmlinuzを利用すること、またubuntuではvmlinuzがrootにしか読めないことによる。最初にrunコマンドを実行することにより、Qemuが起動する。起動したLinuxが見ることのできるファイルシステムは、list-filesystemsで列挙することができる。LVMのボリュームも認識されていることがわかる。

><fs> mount /dev/ubuntu-vg/ubuntu-lv /
><fs> ls /root
.bashrc
.profile
.ssh
snap
><fs> cat /root/.bashrc
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples
[中略]

><fs> vi /root/.bashrc
[以下略]

ファイルシステムをmountすると、lsコマンドやcatコマンドを使ってディレクトリやファイルの中身を見たり、viで編集したりすることができる。その他、ファイルをホストへコピーするcopy-out、逆にホストのファイルを仮想ディスクイメージ内にコピーするcopy-in、まとめてtarファイルにしたり、tarファイルを展開するtar-out/tar-inといったコマンドがあり、またパーティションテーブルを操作したりfsckやmkfsしたりすることもできる。

4.2 LIO/tcmu-runner

guestfishはかなり高機能で、たいていの用は済ますことができるが、LIO*11を使うとraw形式やQcow/Qcow2形式の仮想ディスクイメージをホストに接続して、より高度な操作を行なうこともできる。

LinuxをiSCSIやFCなど各種SCSIベースプロトコルのターゲットにすることができるLIOには、ブロックデバイスや通常ファイルをバックエンドのストレージとして利用する機能がある。通常ファイルすなわちQemuのraw仮想ディスクイメージである。この他にTCM Userspaceという機能もあり、ユーザースペースで動くデーモンがデータを供給できる。ユーザースペースデーモンの実装としてtcmu-runnerがあり、これにはCeph RBDやQemuのQcow2イメージを利用する機能がある。tcmu-runnerは、ubuntuではパッケージとして提供されているので、apt install tcmu-runnerで容易に導入できる*12

ここでは、tcmu-runnerを利用して、Qcow2イメージをホストに接続してみよう。まずtcmu-runnerデーモンが動作していることを確認する。

$ systemctl status tcmu-runner
● tcmu-runner.service - LIO Userspace-passthrough daemon
     Loaded: loaded (/lib/systemd/system/tcmu-runner.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2023-05-16 17:25:09 JST; 3 days ago
[以下略]

バックエンドとしてQcow2イメージを追加する。

$ sudo targetcli
targetcli shell version 2.1.53
Copyright 2011-2013 by Datera, Inc and others.
For help on commands, type 'help'.

/> ls
o- / ..................................................................... [...]
  o- backstores .......................................................... [...]
  | o- block .............................................. [Storage Objects: 0]
  | o- fileio ............................................. [Storage Objects: 0]
  | o- pscsi .............................................. [Storage Objects: 0]
  | o- ramdisk ............................................ [Storage Objects: 0]
  | o- user:fbo ........................................... [Storage Objects: 0]
  | o- user:qcow .......................................... [Storage Objects: 0]
  | o- user:rbd ........................................... [Storage Objects: 0]
  | o- user:zbc ........................................... [Storage Objects: 0]
  o- iscsi ........................................................ [Targets: 0]
  o- loopback ..................................................... [Targets: 0]
  o- vhost ........................................................ [Targets: 0]
/> /backstores/user:qcow
/backstores/user:qcow> create test 100g .../test.qcow2
Created user-backed storage object test size 107374182400.
/backstores/user:qcow> /
/> ls
o- / ..................................................................... [...]
  o- backstores .......................................................... [...]
  | o- block .............................................. [Storage Objects: 0]
  | o- fileio ............................................. [Storage Objects: 0]
  | o- pscsi .............................................. [Storage Objects: 0]
  | o- ramdisk ............................................ [Storage Objects: 0]
  | o- user:fbo ........................................... [Storage Objects: 0]
  | o- user:qcow .......................................... [Storage Objects: 1]
  | | o- test .......................... [.../test.qcow2 (100.0GiB) deactivated]
  | |   o- alua ............................................... [ALUA Groups: 1]
  | |     o- default_tg_pt_gp ................... [ALUA state: Active/optimized]
  | o- user:rbd ........................................... [Storage Objects: 0]
  | o- user:zbc ........................................... [Storage Objects: 0]
  o- iscsi ........................................................ [Targets: 0]
  o- loopback ..................................................... [Targets: 0]
  o- vhost ........................................................ [Targets: 0]
/> 

100gはサイズが100GiBであることを示す。正しいサイズを指定しないとエラーになる。

これをiSCSIでエクスポートしてももちろんよいのだが、TCM loopbackという機能を用いれば、その場でsdが生える。

/> /loopback
/loopback> create
Created target naa.500140593fa2b9eb.
/loopback> naa.500140593fa2b9eb/luns
/loopback/naa...3fa2b9eb/luns> create /backstores/user:qcow/test 0
Created LUN 0.
/loopback/naa...3fa2b9eb/luns> /
/> ls
o- / ..................................................................... [...]
  o- backstores .......................................................... [...]
  | o- block .............................................. [Storage Objects: 0]
  | o- fileio ............................................. [Storage Objects: 0]
  | o- pscsi .............................................. [Storage Objects: 0]
  | o- ramdisk ............................................ [Storage Objects: 0]
  | o- user:fbo ........................................... [Storage Objects: 0]
  | o- user:qcow .......................................... [Storage Objects: 1]
  | | o- test ............................ [.../test.qcow2 (100.0GiB) activated]
  | |   o- alua ............................................... [ALUA Groups: 1]
  | |     o- default_tg_pt_gp ................... [ALUA state: Active/optimized]
  | o- user:rbd ........................................... [Storage Objects: 0]
  | o- user:zbc ........................................... [Storage Objects: 0]
  o- iscsi ........................................................ [Targets: 0]
  o- loopback ..................................................... [Targets: 1]
  | o- naa.500140593fa2b9eb ............................. [naa.50014059e135b40b]
  |   o- luns ........................................................ [LUNs: 1]
  |     o- lun0 ................................. [user/test (default_tg_pt_gp)]
  o- vhost ........................................................ [Targets: 0]
/> exit
Global pref auto_save_on_exit=true
Configuration saved to /etc/rtslib-fb-target/saveconfig.json
$ 

これでsdができたはず。

$ journalctl -b 0 -t kernel | tail
[前略]
May 19 17:40:33 c3 kernel: scsi 14:0:1:0: Direct-Access     LIO-ORG  TCMU device      0002 PQ: 0 ANSI: 5
May 19 17:40:33 c3 kernel: sd 14:0:1:0: Attached scsi generic sg6 type 0
May 19 17:40:33 c3 kernel: sd 14:0:1:0: [sdh] 209715200 512-byte logical blocks: (107 GB/100 GiB)
May 19 17:40:33 c3 kernel: sd 14:0:1:0: [sdh] Write Protect is off
May 19 17:40:33 c3 kernel: sd 14:0:1:0: [sdh] Mode Sense: 2f 00 00 00
May 19 17:40:33 c3 kernel: sd 14:0:1:0: [sdh] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
May 19 17:40:33 c3 kernel: sd 14:0:1:0: [sdh] Optimal transfer size 65536 bytes
May 19 17:40:33 c3 kernel:  sdh: sdh1 sdh2 sdh3
May 19 17:40:33 c3 kernel: sd 14:0:1:0: [sdh] Attached SCSI disk
$ lsblk /dev/sdh
NAME                      MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sdh                         8:112  0  100G  0 disk
├─sdh1                      8:113  0    1M  0 part
├─sdh2                      8:114  0    2G  0 part
└─sdh3                      8:115  0   98G  0 part
  └─ubuntu--vg-ubuntu--lv 253:14   0   98G  0 lvm

LVMボリュームも認識されているので、mountするなりmkfsするなり好きなようにいじくり回せばよい。終わったら、targetcliで解除する。

$ sudo targetcli
targetcli shell version 2.1.53
Copyright 2011-2013 by Datera, Inc and others.
For help on commands, type 'help'.

/> /loopback
/loopback> delete naa.50014059e135b40b
Deleted Target naa.50014059e135b40b.
/loopback> /backstores/user:qcow
/backstores/user:qcow> delete test
Deleted storage object test.
/backstores/user:qcow> /
/> ls
o- / ..................................................................... [...]
  o- backstores .......................................................... [...]
  | o- block .............................................. [Storage Objects: 0]
  | o- fileio ............................................. [Storage Objects: 0]
  | o- pscsi .............................................. [Storage Objects: 0]
  | o- ramdisk ............................................ [Storage Objects: 0]
  | o- user:fbo ........................................... [Storage Objects: 0]
  | o- user:qcow .......................................... [Storage Objects: 0]
  | o- user:rbd ........................................... [Storage Objects: 0]
  | o- user:zbc ........................................... [Storage Objects: 0]
  o- iscsi ........................................................ [Targets: 0]
  o- loopback ..................................................... [Targets: 0]
  o- vhost ........................................................ [Targets: 0]
/> 

5. その他小ネタ

5.1 apparmorを解除する

ubuntuホスト上でlibvirt配下のQemuに対して、qemu-monitor-commandなどで特殊な操作を行なおうとすると、すぐにPermission deniedと言われてしまう。誰がdenyしているのかというと、AppArmorである。libvirtは動的にAppArmorプロファイルを生成・修正するので、libvirt API経由で操作している限りPermission deniedは出ないはずである。

libvirtの知らないところで怪しい実験をしたりしたい場合は、AppArmorを解除する。この方法を2つご紹介する。

  • Qemu起動前の場合 -- virsh editなどでドメイン定義のXMLファイルを編集し、<domain>要素直下 (<name><devices>などの並び) に<seclabel type='none'/>という要素を入れる。
  • Qemu起動後の場合 -- virsh domuuidなどでドメインのUUIDを取得し、aa-complainコマンドでcomplainモードにするか、aa-disableでAppArmorを禁止する。例: aa-complain /etc/apparmor.d/libvirt/libvirt-$(virsh domuuid DOMAIN)

5.2 画面解像度を指定する

libvirt配下のQemuのディスプレイ解像度を指定するには、videoデバイスの<model>要素の中に<resolution>要素を追加する。

<domain type='kvm'>
[中略]
  <devices>
[中略]
    <video>
      <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0'/>
    </video>
[以下略]

これを、

<domain type='kvm'>
[中略]
  <devices>
[中略]
    <video>
      <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'>
        <resolution x='1280' y='768'/>
      </model>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0'/>
    </video>
[以下略]

などとする。x='768' y='1024'のように、縦長画面にすることもできる。ファームウェア画面などは特定の解像度になってしまうし、videoデバイス (model type) によっては利用できないこともある。

*1:OpenStack Novaなどもlibvirtを利用している

*2:場合により異なるので、virsh net-dumpxml defaultなどとして確認する

*3:-mを指定しないと、RAMが128MBしか接続されないため、ubuntuのインストーラーは起動できない

*4:ubuntu 22.04の場合

*5:ショートカットキーとして、Alt-Control-1やAlt-Control-2などが用意されており、それで切り替えることも可能

*6:豆知識: ubuntuの場合、---以降に書くことで、インストールされたシステムにもこのオプションが引き継がれる

*7:Control-Aがエスケープキーとなっており、他の機能はControl-A hで表示される

*8:macOSで動作するvirt-manager移植、というものもないわけではないようだ

*9:これは、gtkを利用したアプリケーション共通の動作である

*10:libguestfsという、ディスクイメージ操作のライブラリがあり、そのCLIシェルがguestfishである。libvirtに対するvirshと似ている

*11:LIOのコアである、ターゲットをエミュレートする部分をTCMと呼ぶようだが、TCMをLIOの別名のように使うことも多いようだ

*12:RHEL向けにはRed Hat社ストレージ製品などにバンドルされるようだ