ローカル開発用の VM を構築するための速習 libvirt#
導入#
個人的にローカル開発用の仮想マシン(以下 VM)を構築するのに snap の multipass を利用していましたが、VM を停止できなくなったり、status が unknown になったりとどうも不安定 [1] なので、いっそのこと libvirt を使ってみることにしました。そのため、必要知識を整理しました。
基礎#
libvirt は openvz, kvm, qemu, virtualbox, xen などの仮想化エンジンを統一的に制御するためのAPI群
最近はハイパーバイザーとして Qemu/KVM が用いられる
libvirt の cli として virsh, virt-install がある
接続タイプ#
libvirt の接続タイプとして qemu:///system と qemu:///session がある
qemu:///system
root ユーザあるいは
libvirtユーザグループに属しているユーザはシステム接続になる
qemu:///session
libvirtユーザグループに属していない非 root ユーザはセッション接続になる他のユーザ(root を含む)によって作成された仮想マシンにはアクセスできない
セッション接続ではネットワークの作成が不可、VM の自動起動設定が不可などの制限がある
接続タイプの確認は以下のコマンドで行える
$ virsh uri
qemu:///session
$ sudo virsh uri
qemu:///system
システム接続の方がやれることは多いが、開発目的ではセッション接続で十分だと思われるので、以後その前提で進める。 [2]
環境構築#
libvirt, virt-manager, passt をインストール [3]
# arch系ディストリビューションの場合
$ sudo pacman -S libvirt passt virt-manager
virt-manager は VM を管理するための GUI ツールであり必須ではないが、libvirt では VM やネットワーク等の設定が xml で管理されるため GUI で確認・編集できるのは便利ではある。ただし、virt-manager の GUI がすべての設定項目に対応しているわけではなく、場合により xml を編集する必要はでてくる。
セッション接続の場合、libvirt デーモンは不要だが、一応状況を確認しておく
$ sudo systemctl status libvirtd
$ sudo systemctl status virtqemud
$ sudo systemctl status virtnetworkd
$ sudo systemctl status virtstoraged
libvirtd はレガシーで現在はモジュールに分割された複数のデーモンで構成される
virtqemud, virtnetworkd, virtstoraged など
稼働中の VM がなければ libvirtd より分割されたデーモンを使う方がよいと思われる
VM の作成(1): イメージ#
開発用の VM を作成するたびに毎回対話的に設定を行うのは面倒なのでクラウドイメージ [4] と cloud-init を使う。Ubuntu のクラウドイメージは以下のサイトで配布されている。
cloud-init は二重に実行されないための仕組みがあり、実行結果がファイルとして保存されるようになっている。また、クラウドイメージを直接起動すると、VM 内の変更がイメージファイルに直接書き込まれることになる。
上記の背景により、同じイメージを使って何回か cloud-init を実行しようとしても、2回目移行は実行済みでキャンセルされるという挙動になる。新しい VM を作成する度に、コピーをとったりダウンロードしなおすという手もあるが、qemu には差分イメージというより効率的な機能があるため、それを利用する。
差分イメージ#
qemu の機能でその名前のとおり対象のイメージからの差分情報のみを含んだイメージを作成できる。
ベースイメージへは絶対パスで参照するのでベースイメージの配置を変えてはいけない [5]
ベースイメージに対して変更を加えるのも NG なので、ファイル名にイメージの作成日付などを含めてバージョン管理し、書き込みも禁止しておく
# ディレクトリ構成はお好みでよいが運用の一例としてこんな感じかと
$ mkdir -p ~/libvirt/{base,vms}
$ cd ~/libvirt
$ curl -o base/ubuntu-22.04-20250702.qcow2 https://cloud-images.ubuntu.com/jammy/20250702/jammy-server-cloudimg-amd64-disk-kvm.img
$ chmod 444 base/ubuntu-22.04-20250702.qcow2
# VM 用の差分イメージを作成する。ディスクサイズもここで指定する。
$ qemu-img create -f qcow2 -b $HOME/libvirt/base/ubuntu-22.04-20250702.qcow2 -F qcow2 vms/myvm1.qcow2 16G
# イメージの確認
$ qemu-img info vms/myvm1.qcow2
Tip
qcow2 (QEMU Copy-On-Write version 2) は qemu で使用されるイメージフォーマットであり、その名の通り差分管理(Copy-On-Write)が可能
VM の作成(2): cloud-init#
libvirt で cloud-init を使う際、以前は設定情報を記述した user-data.yaml と meta-data.yaml を含んだ seed.iso を作る必要があったが virt-install の --cloud-init オプションを使うと yaml ファイルを直接指定できる。
しかし環境によっては --cloud-init オプションが期待した通りに動作しないこともあるらしいので、まず最初は最低限の VM を作成し動作検証する。この状態ではネットワーク設定をしていないので VM からインターネットに接続したり外から ssh ができない。
user-data.yaml
#cloud-config
ssh_pwauth: false
users:
- default
chpasswd:
list: |
ubuntu:ubuntu
expire: False
Ubuntu のクラウドイメージでは、default ユーザは "ubuntu" という名前で作成される
明示的に users で作成するユーザを指定する必要がある
デフォルトで
ubuntuユーザのパスワードは設定されていないため、chpasswdでパスワード(ubuntu) を設定する [6]ssh 接続(公開鍵)を設定した場合はパスワードは不要となるが、最初のうちは console で接続できるようにパスワードを設定した方が良いと思われる
Tip
好みが分かれるところだと思われるが、 default でなく明示的に ubuntu ユーザの各属性を指定してもよい
#cloud-config
ssh_pwauth: false
users:
- name: ubuntu
uid: 1001
gecos: Ubuntu
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
lock_passwd: false
plain_text_passwd: ubuntu
meta-data.yaml
instance-id: myvm1
local-hostname: myvm1
instance-id はなんでもよいため上記では myvm1 としているが、
$ uuidgen -tで uuid を生成するのが無難か
user-data.yaml と meta-data.yaml を作成後、以下のコマンドで VM を作成する。
$ virt-install \
--name myvm1 \
--autoconsole text \
--import \
--memory 2048 --vcpus=2 \
--osinfo generic \
--disk bus=virtio,path="$HOME/libvirt/vms/myvm1.qcow2" \
--cloud-init user-data=user-data.yaml,meta-data=meta-data.yaml
--autoconsoleで自動的に$ virsh console myvm1を行いシリアル接続する最初のうちは console で入って cloud-init が期待した設定になっているかは確認したほうがよい。不要な場合は
--noautoconsoleに変える。起動後に
ubuntu/ubuntuでログインできるか確認するcloud-init の状態は
$ cloud-init statusコマンドや/var/log/cloud-init.logのログで確認するconsole は
ctrl+]で抜ける
seed.iso#
--cloud-init が期待した動作にならない場合、以下のコマンドで seed.iso を作成してみる
$ genisoimage -output seed.iso -volid cidata -joliet -rock user-data.yaml meta-data.yaml
virt-install の --cloud-init オプションを以下に変更する
--disk path=seed.iso,device=cdrom
VM の操作#
よく使うコマンドを例に挙げる。
$ virsh start myvm1 # 停止中の VM を起動
$ virsh shutdown myvm1 # 起動中の VM を停止
$ virsh destroy myvm1 # 起動中の VM を強制停止
$ virsh undefine myvm1 # VM を登録解除
$ virsh undefine myvm1 --remove-all-storage # VM を登録解除してディスク( ~/libvirt/vms/myvm1.qcow2 など) を削除
$ virsh console myvm1 # VM にシリアル接続
$ virsh dumpxml myvm1 # VM の設定を出力(xml)
$ virsh edit myvm1 # VM の設定を編集
ネットワーク#
セッション接続でネットワークを利用する場合、 passt が利用できる。 passt ではホストから VM へのアクセスはポートフォワーディングを用いる。
先ほどの VM を削除し、 virt-install にネットワークオプションを追加する。また、 user-data.yaml を編集し、ssh 接続用のユーザを追加する。 [7]
#cloud-config
ssh_pwauth: false
users:
- default
- name: yourname
lock_passwd: true
sudo: ALL=(ALL) NOPASSWD:ALL
uid: 1002
shell: /bin/bash
ssh_authorized_keys:
- ssh-rsa AAAAB3Nz...(略,公開鍵の内容をコピペ)
chpasswd:
list: |
ubuntu:ubuntu
expire: False
$ virsh shutdown myvm1
$ # vm の削除およびディスクイメージの削除
$ virsh undefine myvm1 --remove-all-storage
$ # イメージ再作成
$ qemu-img create -f qcow2 -b $HOME/libvirt/base/ubuntu-22.04-20250702.qcow2 -F qcow2 vms/myvm1.qcow2 16G
$ virt-install \
--name myvm1 \
--autoconsole text \
--import \
--memory 2048 --vcpus=2 \
--osinfo generic \
--disk bus=virtio,path="$HOME/libvirt/vms/myvm1.qcow2" \
--network passt,model=virtio,portForward=20222:22 \
--cloud-init user-data=user-data.yaml,meta-data=meta-data.yaml
これにより、VM からのインターネット接続と、ssh接続 (ローカルの 20222 を 22 にポートフォワーディング ) ができる。
Tip
VM を複数起動する場合、ローカルポートはかぶらないようにする(若干面倒)
VM起動後、以下のコマンドで ssh 接続を行う
$ ssh -i .ssh/your-private-key -p 20222 yourname@localhost
Tip
portForward はコマンドラインからでは複数設定できない(?) ようなので、複数のポートを公開したい場合は xml を編集する必要がある
<!-- 追加で 8000番ポートを開ける例 -->
<interface type="user">
<mac address="52:54:00:62:e2:d6"/>
<portForward proto="tcp">
<range start="20222" to="22"/>
<range start="8000" to="8000"/>
</portForward>
<model type="virtio"/>
<backend type="passt"/>
<alias name="net0"/>
<address type="pci" domain="0x0000" bus="0x00" slot="0x03" function="0x0"/>
</interface>
フォルダ共有#
ホストOSのディレクトリをゲストOSに共有したい場合(典型的には gitリポジトリなど)、ファイルシステムのドライバーとして virtio-9p と virtiofs が選択できる。
virtiofs の方が新しくパフォーマンスもよいらしいが、 virtiofs をセッション接続で用いる場合、ゲストOSの共有対象のディレクトリのオーナーがホストOSの root にマッピングされるため [8] 、ゲストOS側のログインユーザで作業する際に Permisson Error が起きる可能性があり、使い勝手が若干よくない。
この問題は id のマッピングを設定することで解決できるが、 virt-install のオプションで渡せるかが不明(できても記述が長くなりそう)なので、マッピングの指定に関しては xml を編集する必要がある。
まず、以下のコマンドで virtiofs が利用できるか確認する(Linux Kernel 5.4, 2019 以降で対応されているのでまず大丈夫だと思われるが)
$ modinfo virtiofs
上記問題ない場合、 virt-install に以下の --filesystem オプションを追加する。
--memorybacking source.type=memfd,access.mode=shared \
--filesystem $HOME/path/to/host/shared,shared,type=mount,driver.type=virtiofs \
memorybacking は virtiofs の利用時に必要になるオプションになる
--filesystemの2つめの値(shared) はマウントタグと呼ばれ、ゲストOSでマウント対象を識別するのに使う
ゲストOS で、以下のコマンドでマウントする
$ mkdir shared
$ sudo mount -t virtiofs shared shared
この状態では、idmap を設定してないので ls -l するとオーナーは root になっている。VM を一度停止し、 virsh edit コマンド(または virt-manager )で xml を編集し、マッピングを指定する。
$ virsh shutdown myvm1
$ virsh edit myvm1
--filesystem の指定により、devices 要素の子要素として filesystem 要素が追加されている
<devices>
<filesystem type='mount' accessmode='passthrough'>
<driver type='virtiofs'/>
<source dir='/path/to/host/shared'/>
<target dir='shared'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'>
</filesystem>
</devices>
ここに以下のような idmap 要素を追加する。uid/gid の target はホストOS の実行ユーザの uid/gid、start はゲストOS の作業ユーザの uid/gid を指定する。
<devices>
<filesystem type='mount' accessmode='passthrough'>
<driver type='virtiofs'/>
<source dir='/path/to/host/shared'/>
<target dir='shared'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'>
+ <idmap>
+ <uid start='1002' target='1000' count='1'/>
+ <gid start='1002' target='1000' count='1'/>
+ </idmap>
</filesystem>
</devices>
設定がうまくいけば、再起動、マウントした際のオーナーが root から変更されているはずである。
Tip
VSCode では remote-ssh, sftp, rsync などいろいろなファイル同期手段があるので、開発用途VM としてはそれらを使うのもよい
TIP: managed save について#
libvirt には managed save という、ゲストOS の状態を保存して次回起動時に状態を 復元する機能がある。
また、ホストOS のシャットダウン時にゲストOS が起動している場合、セッション接続 のデフォルトの挙動では managed save を試みる設定になっている。
特に問題なければそのままで良いのだが、稀に保存された状態に何らかの不整合があり ゲストOSが起動できなくなることがある。 [9] そうなった場合は以下のコマンドで 状態を削除する(あくまで状態の削除であり、OS自体は削除されない)。
$ virsh managedsave-remove myvm
頻発する場合、以下で設定を無効にできる。
auto_shutdown_try_save = "none"
おまけ: システム接続でのコマンド#
あまり検証してないメモ書きなので、参考程度に
環境設定
$ sudo systemctl status libvirtd
$ sudo systemctl enable --now virtqemud.socket
$ sudo systemctl enable --now virtnetworkd.socket
$ sudo systemctl enable --now virtstoraged.socket
$ sudo systemctl start virtqemud
$ sudo systemctl start virtnetworkd
$ sudo systemctl start virtstoraged
ネットワーク設定
# net-undefine したときに xml ごと消されることがあるのでバックアップしておく
$ sudo cp /etc/libvirt/qemu/networks/default.xml /etc/libvirt/qemu/networks/default.xml.bak
# ネットワークを作成
$ sudo virsh net-define /etc/libvirt/qemu/networks/default.xml
$ sudo virsh net-autostart default
$ sudo virsh net-start default
$ sudo virsh net-list --all
# ネットワークの自動起動解除
$ sudo virsh net-autostart --disable default
# ネットワークの削除(xml も同時に消される模様)
$ sudo virsh net-undefine default
VM でネットワーク利用
--network bridge=virbr0,model=virtio \
# or
--network default,model=virtio \
IP の確認
$ virsh domifaddr myvm1
$ sudo virsh net-dhcp-leases default
$ sudo virsh dumpxml myvm1 | grep interface -A 10
VM 側でのネットワークインターフェイスの確認・有効化
$ ip -br addr show
$ sudo ip link set virbr0 up
(2026.07.22 追記) VirtFS (virtio-9p) について#
初稿では 9p の passthrough を使った共有方法を記載しており、それに合わせてゲストOS 側の uid をホストの VM 実行ユーザの uid と揃えていた。が、そもそも 9p の passthrough は qemu を root ユーザで実行して使うものらしく、libvirt のセッション接続(=非root) で使うことが想定外と思われたため、 virtiofs を使うように修正した。
なお 9p の passthrough は root で qemu を動かす事自体がリスクであるため非推奨とされており、基本的に virtiofs を使った方が良さそうである。
参考#
1.5. 仮想化のユーザー空間接続タイプ | Linux 仮想マシンの設定と管理 | Red Hat Enterprise Linux | 10 | Red Hat Documentation
Launch QCOW images using libvirt - Ubuntu Public Images documentation
Using virtiofs with libvirt/virt-install - A Random Walk Down Tech Street
Updated on 2026-07-22
ファイル共有方式を 9p から virtiofs に変更
Updated on 2026-07-27
managed save について加筆
Footnotes
ローカル開発用の VM を構築するための速習 libvirt — ykrods note
https://www.ykrods.net/posts/2025/07/13/libvirt-local-vm-quickstart/