vagrant-libvirt を使ってみる

ずいぶん前に Docker Desktop (Docker for Windows) のために Hyper-V を有効にしたため VirtualBox が使えなくなりました。それに伴って Vagrant も使わなくなりました。

最初は Vagrant が使えなくなるのは辛いかな・・と思っていたのですが、開発系の作業は Docker で事足りているし、サーバ系の作業でどうしても Docker では足りないときは KVM (virt-install) とか EC2 にインスタンスを作るなどすればいいので、Vagrant が使えなくなってもそれほど困りませんでした。

ただ、Vagrant でローカルの VirtualBox に環境を作るのと比べると KVM に virt-install でセットアップしたり EC2 インスタンスをマネジメントコンソールや CLI で作るのは作業のステップが多くて煩雑な気もしています。

そこで、KVM ホストにゲストをサクサク作ったり消したりできるよう vagrant-libvirt を試してみました。vagrant-libvirt なら Hyper-V 有効なままでも使用できます。

libvirtd のリモートアクセス

KVM は Vagrant を実行するホストとは別のホストに構築しているので(というか Vagrant を実行するホストは Windows の WSL の Fedora 31 なので)、まずは KVM ホストの libvirtd にリモート接続できるようにする必要があります。

下記によると TCP や TLS で接続できるようですが、素のままでも SSH で接続できます。

https://libvirt.org/remote.html

次の手順で SSH 経由で接続できることを確認しました。

# libvirt-client パッケージを入れる
sudo dnf install libvirt-client

# 設定ファイルのディレクトリを作成
mkdir -pv ~/.config/libvirt/

# デフォルトの接続先を指定
echo 'uri_default = "qemu+ssh://root@192.0.2.123/system"' > ~/.config/libvirt/libvirt.conf

# 接続確認
virsh list --all

vagrant-libvirt をインストール

Vagrant に vagrant-libvirt プラグインを入れます。

Fedora 31 だと普通にはインストールできず CONFIGURE_ARGS を指定する必要がありました。

sudo dnf install -y libvirt-devel
env CONFIGURE_ARGS="with-libvirt-include=/usr/include/libvirt with-libvirt-lib=/usr/lib64" \
  vagrant plugin install vagrant-libvirt

この手順で vagrant-libvirt はインストールできたものの vagrant up がエラーになりました。

vagrant up
:
/usr/lib64/libssh.so.4: undefined symbol: EVP_KDF_ctrl, version OPENSSL_1_1_1b - ...

簡単には解決出来なさそうです。

https://github.com/hashicorp/vagrant/issues/11020

dnf で Fedora のリポジトリから Vagrant とプラグインを一緒にインストール出来るようなので、オフィシャルのパッケージは使わずに Fedora のリポジトリからインストールしました。

sudo dnf install vagrant vagrant-libvirt

ゲストを作る

Vagrantfile を次の内容で作成します。

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"

  config.vm.provider :libvirt do |libvirt|
    libvirt.driver = "qemu"
    libvirt.host = "192.0.2.123"
    libvirt.username = "root"
    libvirt.connect_via_ssh = true
  end

  config.vm.define :sv01 do |sv01|
    sv01.vm.network :private_network, :ip => "10.99.0.101"
    sv01.vm.network :forwarded_port, guest: 80, host: 8080
  end

  config.vm.define :sv02 do |sv02|
    sv02.vm.network :private_network, :ip => "10.99.0.102"
    sv02.vm.network :public_network, :dev  => "br0"
  end

  config.vm.provision "shell", inline: <<-SHELL
    set -eux
    yum -y install httpd
    systemctl start httpd
  SHELL

end

ゲストを作成します。

vagrant up --provider=libvirt

ストレージ

デフォルトでは default というストレージプールにディスクイメージが作成されます。普通に libvirtd をセットアップすると /var/lib/libvirt/images になっていると思います。

ls -1 /var/lib/libvirt/images/
# centos-VAGRANTSLASH-7_vagrant_box_image_2004.01.img
# hogehoge_sv01.img
# hogehoge_sv02.img

Vagrant Box に含まれる qcow2 のイメージをベースとして差分のディスクイメージが作成されます。

virsh dumpxml hogehoge_sv01
# <disk type='file' device='disk'>
#   <driver name='qemu' type='qcow2'/>
#   <source file='/var/lib/libvirt/images/hogehoge_sv01.img'/>
#   <backingStore type='file' index='1'>
#     <format type='qcow2'/>
#     <source file='/var/lib/libvirt/images/centos-VAGRANTSLASH-7_vagrant_box_image_2004.01.img'/>
#     <backingStore/>
#   </backingStore>
#   <target dev='vda' bus='virtio'/>
#   <alias name='virtio-disk0'/>
# </disk>

Vagrantfile の storage_pool_name で別のストレージプールを指定できます。

virsh pool-create-as --name data --type dir --target /data
# ...Vagrantfile snippet...
config.vm.provider :libvirt do |libvirt|
  libvirt.storage_pool_name = "data"
end

この場合も Box イメージは default ストレージプールのみに保存されていました。

ls -1 /var/lib/libvirt/images/
# centos-VAGRANTSLASH-7_vagrant_box_image_2004.01.img

ls -1 /data/
# hogehoge_sv01.img
# hogehoge_sv02.img

Box イメージはこの指定とは無関係に全てのストレージプールから検索されるようです。見つからなかったときのダウンロードの保存先は指定したストレージプールになります。

試しに default ストレージプールから Box イメージを削除して vagrant up すると、指定したストレージプールに Box イメージがダウンロードされました。

ls -1 /data/
# centos-VAGRANTSLASH-7_vagrant_box_image_2004.01.img
# hogehoge_sv01.img
# hogehoge_sv02.img

なお、LVM のストレージプールを指定すると次のエラーになりました。

unexpected exit status 5: Snapshots of snapshots are not supported. (Libvirt::Error)

lvcreate でボリュームを作ろうとはするようなのですが・・・下記によると LVM は対応してなさそうです。

Management Network

vagrant up すると vagrant-libvirt というネットワークが自動的に作成され、ゲストのインタフェースに接続されます。 このインタフェースを通じて Vagrant とゲストの通信や、ゲストから外向き通信の NAT が行われます。

virsh net-dumpxml vagrant-libvirt
# <network connections='2' ipv6='yes'>
#   <name>vagrant-libvirt</name>
#   <forward mode='nat'>
#     <nat>
#       <port start='1024' end='65535'/>
#     </nat>
#   </forward>
#   <bridge name='virbr1' stp='on' delay='0'/>
#   <ip address='192.168.121.1' netmask='255.255.255.0'>
#     <dhcp>
#       <range start='192.168.121.1' end='192.168.121.254'/>
#     </dhcp>
#   </ip>
# </network>

virsh dumpxml hogehoge_sv01
# <interface type='network'>
#   <source network='vagrant-libvirt' bridge='virbr1'/>
#   <target dev='vnet2'/>
#   <model type='virtio'/>
#   <alias name='net0'/>
# </interface>

ネットワーク名やアドレス範囲は変更もできます。

# ...Vagrantfile snippet...
config.vm.provider :libvirt do |libvirt|
  libvirt.management_network_name = "vagrant-management-network"
  libvirt.management_network_address = "10.255.0.0/24"
end

Private Network

private_network は libvirt のネットワークとして作られます。

# ...Vagrantfile snippet...
config.vm.define :sv01 do |sv01|
  sv01.vm.network :private_network, :ip => "10.99.0.101"
end
virsh net-dumpxml hogehoge0
# <network connections='2' ipv6='yes'>
#   <name>hogehoge0</name>
#   <forward mode='nat'>
#     <nat>
#       <port start='1024' end='65535'/>
#     </nat>
#   </forward>
#   <bridge name='virbr2' stp='on' delay='0'/>
#   <ip address='10.99.0.1' netmask='255.255.255.0'>
#     <dhcp>
#       <range start='10.99.0.1' end='10.99.0.254'/>
#     </dhcp>
#   </ip>
# </network>

virsh dumpxml hogehoge_sv01
# <interface type='network'>
#   <source network='hogehoge0' bridge='virbr2'/>
#   <target dev='vnet3'/>
#   <model type='virtio'/>
#   <alias name='net1'/>
# </interface>

Forwarded Port

forwarded_port は ssh ポートフォワードを用いて実現されています。

# ...Vagrantfile snippet...
config.vm.define :sv01 do |sv01|
  sv01.vm.network :forwarded_port, guest: 80, host: 8080
end

forwarded_port が指定された Vagrantfile で vagrant up するとポートフォワードのための ssh がバックグラウンドで立ち上がります。

このポートフォワードは Vagrant を実行しているローカルの PC から Vagrant のゲストに対して行われます。なのでローカルのポート、Vagrant を WSL で実行しているなら Windows 上のポートからゲストのポートに転送されます。KVM ホストのポートからではありません。

Public Network

public_network は macvtap なるもので実現されています。macvtap はだいぶ前にちょっと試したことありました。

https://ngyuki.hatenablog.com/entry/2016/04/27/213205

KVM ホストのブリッジのデバイス名を指定する必要があります。

# ...Vagrantfile snippet...
config.vm.define :sv01 do |sv01|
  sv01.vm.network :public_network, :dev  => "br0"
end
virsh dumpxml hogehoge_sv02
# <interface type='direct'>
#   <source dev='br0' mode='bridge'/>
#   <target dev='macvtap0'/>
#   <model type='virtio'/>
#   <alias name='net2'/>
# </interface>

デフォルトが eth0 なので、ホストでブリッジを構成しておらず eth0 がそのままIPアドレスを持っているような構成なら :dev は指定しなくても良いです。

ドメイン名(libvirt のゲスト名)

libvirt のドメイン名(ゲスト名)は、素のままならカレントディレクトリのベース名がプレフィックスに付与されます。

例えば /path/to/hogehogevagrant up すれば hogehoge_sv01 などとなります。

プレフィックスは default_prefix で変更できます。また random_hostname でランダムなサフィックスが付与できます。

# ...Vagrantfile snippet...
config.vm.provider :libvirt do |libvirt|
  libvirt.default_prefix = "oreore-"
  libvirt.random_hostname = true
end

この設定で oreore-sv01_1596269544_a38c163196854065a16f のようなドメイン名になります。 ディスクイメージも同様に oreore-sv01_1596269544_a38c163196854065a16f.img のような名前で作成されます。

プライベートネットワークの名前もカレントディレクトリのベース名がプレフィックスに付与されますが(hogehoge0 など)、default_prefix はプライベートネットワークの名前には影響しません。

プライベートネットワークの名前は個別に指定する必要があります。

# ...Vagrantfile snippet...
config.vm.define :sv01 do |sv01|
  sv01.vm.network :private_network, :ip => "10.99.0.101",
    :libvirt__network_name => "oreore-private"
end
config.vm.define :sv01 do |sv02|
  sv01.vm.network :private_network, :ip => "10.99.0.102",
    :libvirt__network_name => "oreore-private"
end

さいごに

前回試してみた vagrant-aws はちょっとどうかなと思いましたがvagrant-libvirt はまだメンテされてそうなので、検証用などで KVM にサクッと環境つくりたいときには便利かもしれません。