ヒスねこTechBlog

日々の気になる技術をまとめてます。

cloud-initとlibvirtでVMに自動OSインストール

 Ansibleほど大規模にVMを構築する気はないけど、OSのインストールは対話的にでなく自動で行いたい、という場合はcloud-initが選択肢に入るらしい。ちょっと試してみましょう。

セットアップ

 今回はUbuntu20.04(x86_64)でやってみました。

$ sudo apt install qemu-kvm libvirt-daemon-system virtinst
# libvirtグループにユーザを追加する
# (apt install時に追加されているはずだが念の為)
$ sudo adduser $USER libvirt
virt-installでインストール

 ゲストOSがUbuntuの場合、公式docに従えばインストールできてしまいます。

ubuntu.com

ただそれだと味気ないので、virt-installを使用してインストールにトライしてみます。

# 前半は公式docのProviding the autoinstall data over the networkと同じ
$ sudo mount -r ~/Downloads/ubuntu-20.04-live-server-amd64.iso /mnt
$ mkdir -p ~/www
cd ~/www
cat > user-data << 'EOF'
#cloud-config
autoinstall:
  version: 1
  identity:
    hostname: ubuntu-server
    password: "$6$exDY1mhS4KUYCE/2$zmn9ToZwTKLhCw.b4/b.ZRTIZM30JZ4QrOQ2aOXJ8yk96xpcCof0kxKwuX1kqLG/ygbJ1f8wxED22bTL4F46P0"
    username: ubuntu
EOF
touch meta-data
$ python3 -m http.server 3003

# 以下は別端末で操作(場所は任意)
$ truncate -s 10G image.img 
$ cd /mnt
$ virt-install \
--name=test \
--os-variant=ubuntu20.04 \
--memory=2048 \
--vcpus=1 \
--network network=default \
--disk=<image.imgへのパス>,cache=none,format=raw,bus=virtio \
--location=<ubuntu-20.04.4-live-server-amd64.isoへのパス>,kernel=casper/vmlinuz,initrd=casper/initrd \
--extra-args "autoinstall ds=nocloud-net;s=http://_gateway:3003/" \
--noreboot

 ちなみに--locationオプションのkernel, intridが/mnt/casper/vmlinuzや/mnt/casper/initrdだと以下のエラーが出たので、上記のように/mntに移動して実行しました。kvmコマンドの場合はフルパスでアクセスできたんですが...

Starting install...
ERROR    Couldn't find kernel for install tree.
Domain installation does not appear to have been successful.
If it was, you can restart your domain by running:
  virsh --connect qemu:///system start test
otherwise, please restart your installation.

 さらに補足ですが、passwordは公式docにならって「ubuntu」のハッシュ値としています。実際に使用する際は変更するべきでしょう。ハッシュ生成は「Linux パスワード ハッシュ」などで検索していただくと、SHA-512でハッシュ値計算するワンライナーがひっかかると思います。

Python+libvirtでインストール

 ついでにPythonでインストールする場合も試行してみました。

 インストールに必要なパラメータを記載したXMLが必要ですが、自分で書くとそれなりに大変です。virt-installのオプションを使って生成・流用するのがよいでしょう。

 ただし、virt-installの時とは異なり、Python+libvirtでは、/mnt配下のvmlinuzやinitrdを参照しようとすると読み取り専用で開けないと怒られます。なにか適切な設定があるのかもしれませんが、今回はとりあえず適当な場所にコピーしておきます。

# /home以下のような任意の場所にvmlinuzとinitrdをコピーしておく
$ mkdir casper
$ cp /mnt/casper/initrd ./casper/
$ cp /mnt/casper/vmlinuz ./casper/

# virt-installで実際にインストールをせずXMLを表示するには
# --print-xmlと--dry-runオプションを付加する
$ truncate -s 10G xmlimage.img
$ virt-install \
--name=xmltest \
--os-variant=ubuntu20.04 \
--memory=2048 \
--vcpus=1 \
--network network=default \
--disk=<xmlimage.imgへのパス>,cache=none,format=raw,bus=virtio \
--location=<ubuntu-20.04.4-live-server-amd64.isoへのパス>,kernel=casper/vmlinuz,initrd=casper/initrd \
--extra-args "autoinstall ds=nocloud-net;s=http://_gateway:3003/" \
--noreboot \
--print-xml \
--dry-run

 これで標準出力にXMLが出力されるのでコピーすればよいのですが、<domain type="kvm"> ... </domain>と囲まれたものが2つ出力されているかと思います。ここで使用するのは、先に出力された方です。後のものはdefine時に使用します。これも別でどこかに控えておいてください。

 コピーしたXMLは、以下のkernelタグ、initrdタグの内容を、先程コピーした先のものに変更する必要があります。元はvirt-install実行時に/var/lib/libvirt/boot/配下に作成した一時的なコピーを参照しているようで、ここの記載を変更しないと「そんなファイルはない」と怒られます。

  <os>
    <type arch="x86_64" machine="q35">hvm</type>
    <kernel>コピー先/casper/vmlinuz</kernel>
    <initrd>コピー先/casper/initrd</initrd>
    <cmdline>autoinstall ds=nocloud-net;s=http://_gateway:3003/</cmdline>
  </os>

 PythonでOSインストールするには、libvirtのPythonバインディングされたAPIがあるため、これを叩くことになります。なお本記事投稿時点のバージョンは6.1.0でした。

$ sudo apt install python3-libvirt

 XMLはファイルとして読み込んでもいいですし、ちょっと試すだけならPythonに直書きでもOKです。とりあえず今回は後者で。

import sys
import libvirt

xmlcreate = """
XMLをここにペースト
"""

conn = libvirt.open('qemu:///system')
if conn == None:
  print('[ERROR]open connection to qemu:///system')
  exit(1)

# defineしない一時的なVMを作成する
# (.imgにはインストールされているがlibvirt管理下にない状態になる)
dom_create = conn.createXML(xmlcreate, 0)
if dom_create == None:
  print('[ERROR]createXML')
  exit(1)


print('all success:'+dom_create.name())

conn.close()
exit(0)

 バックエンドで動作を始めるので、インストールしている様子を見届けたければvirt-viewerをGUI環境で実行するなどします。

$ python3 install.py
$ virt-viewer
VMをdefineする

 defineしていないので、virsh list --allなどしてもインストールしたVMが見えません。次のPythonスクリプトでdefineすることでlibvirtの管理が可能になり、virshでも表示されます。先程のvirt-installであとに出ていた方のXMLをペーストします。こちらは改変する必要は特にありません。

import sys
import libvirt

xmldefine = """
XMLをここにペースト
"""

conn = libvirt.open('qemu:///system')
if conn == None:
  print('[ERROR]open connection to qemu:///system')
  exit(1)

# VMをdefineする
dom_define = conn.defineXML(xmldefine)
if dom_define == None:
  print('[ERROR]defineXML')
  exit(1)

print('all success:'+dom_define.name())

conn.close()
exit(0)

 スクリプト実行はインストールが完了した後にしたほうがよいでしょう。実行後はvirshでdefineされているかの確認、virsh startとvirt-viewerで動作確認ができます。virt-viewer上でVMの操作が可能なので、pingなど試してみてもいいかもしれませんね。

$ python3 define.py
$ virsh list --all
 Id   Name      State
--------------------------
 -    xmltest   shut off

$ virsh start xmltest
Domain xmltest started

$ virt-viewer

今回はこれまで。