前置き
Terraform libvirt provider で Q35 type の KVM を作ろうとすると、エラーになる場合がある。同 provider は、Q35 machine type の指定そのものはサポートしている が、CD-ROM をマウントするようなコードが共存する場合、失敗する。原因は、同 provider が CD-ROM に自動的に IDE bus を割り当てるためである。この形式はレガシーであり、Q35 machine にマウントできない。したがって、次のような典型的なユースケースにおいて、VM 作成時にエラーが発生してしまう:
- インストール用の Bootable デバイスを 仮想 CD-ROM としてマウントするケース
- cloudinit でインストールするケース (ISO イメージのマウントが発生)
この問題については Issue #1018 が報告されているが、2025年1月現在まで修正されておらず、現状は XSLT パッチ を用いて IDE bus を SATA bus に編集する対処が勧められている。
本記事では、Terraform Libvirt Provider で Q35 マシンを作成するための XSLT パッチの当て方を紹介する。
準備
Terraform 実行ホストに次のソフトウェアをインストールする(Terraform Libvirt Provider が XSLT を解釈する際に必要となる)。
Ubuntu 系の場合:
$ sudo apt install xsltproc
RHEL 系の場合:
$ sudo dnf install libxslt
テスト用 OS image として、Rocky Linux の qcow2
image をダウンロードしておく:
$ sudo curl -o /var/lib/libvirt/images/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 -LO https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
プロビジョニングファイルの作成
XSLT パッチの用意
次の pathc.xsl
を用意する:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<!-- Identity transform to copy everything as is -->
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<!-- Use SATA CD-ROM for Linux setup -->
<xsl:template match="target[@bus='ide']">
<xsl:copy>
<xsl:apply-templates select="@*[name()!='bus']"/>
<xsl:attribute name="bus">sata</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
<!-- Use SATA CD-ROM for Linux setup -->
のブロック内で、IDE bus の指定を SATA bus に修正する処理を行っている:
-
<target>
要素にマッチする要素を探す - 属性名が
bus
以外の場合、元の属性値をそのままコピーする - 属性名が
bus
の場合、属性値をsata
に変更する。
それ以外の部分は、基本的な宣言や、もとの domain xml の内容をそのままコピーする処理が書かれている、いわゆる「おまじない部分」である。
Terraform file の作成
VM 構築テスト用に、次の main.tf
を作成する。
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.1"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
resource "libvirt_cloudinit_disk" "commoninit" {
name = "commoninit_q35-test.iso"
user_data = templatefile("cloud_init.cfg", {})
pool = "default"
}
resource "libvirt_domain" "vm" {
name = "q35-test"
vcpu = 2
memory = 8000
machine = "q35"
disk {
volume_id = libvirt_volume.volume.id
scsi = true
}
cloudinit = libvirt_cloudinit_disk.commoninit.id
autostart = true
network_interface {
network_name = "default"
addresses = ["192.168.122.100"]
}
cpu {
mode = "host-passthrough"
}
graphics {
type = "vnc"
listen_type = "address"
}
# Makes the tty0 available via `virsh console`
console {
type = "pty"
target_port = "0"
target_type = "serial"
}
xml {
xslt = file("${path.module}/patch.xsl")
}
}
# Download from https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
resource "libvirt_volume" "volume" {
name = "q35-test.qcow2"
pool = "default"
format = "qcow2"
base_volume_id = "/var/lib/libvirt/images/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"
size = 100 * 1024 * 1024 * 1024
}
ポイントは libvirt_domain リソース内の次の2箇所:
-
machine
にq35
を指定する -
xml
ブロックで xslt パッチを渡す
TIPS: 利用可能な qemu machine type の一覧は /usr/libexec/qemu-kvm -machine help
コマンド (RHEL 系以外の OS では /usr/bin/qemu-system-x86_64 -machine help
コマンド) で確認することができる:
# /usr/libexec/qemu-kvm -machine help
Supported machines are:
pc RHEL 7.6.0 PC (i440FX + PIIX, 1996) (alias of pc-i440fx-rhel7.6.0)
pc-i440fx-rhel7.6.0 RHEL 7.6.0 PC (i440FX + PIIX, 1996) (default) (deprecated)
q35 RHEL-9.4.0 PC (Q35 + ICH9, 2009) (alias of pc-q35-rhel9.4.0)
pc-q35-rhel9.4.0 RHEL-9.4.0 PC (Q35 + ICH9, 2009)
pc-q35-rhel9.2.0 RHEL-9.2.0 PC (Q35 + ICH9, 2009)
pc-q35-rhel9.0.0 RHEL-9.0.0 PC (Q35 + ICH9, 2009)
pc-q35-rhel8.6.0 RHEL-8.6.0 PC (Q35 + ICH9, 2009) (deprecated)
pc-q35-rhel8.5.0 RHEL-8.5.0 PC (Q35 + ICH9, 2009) (deprecated)
pc-q35-rhel8.4.0 RHEL-8.4.0 PC (Q35 + ICH9, 2009) (deprecated)
pc-q35-rhel8.3.0 RHEL-8.3.0 PC (Q35 + ICH9, 2009) (deprecated)
pc-q35-rhel8.2.0 RHEL-8.2.0 PC (Q35 + ICH9, 2009) (deprecated)
pc-q35-rhel8.1.0 RHEL-8.1.0 PC (Q35 + ICH9, 2009) (deprecated)
pc-q35-rhel8.0.0 RHEL-8.0.0 PC (Q35 + ICH9, 2009) (deprecated)
pc-q35-rhel7.6.0 RHEL-7.6.0 PC (Q35 + ICH9, 2009) (deprecated)
なお、Terraform libvirt provider は、デフォルトで pc-i440fx
machine type の VM を作成する。このタイプはレガシーであり、例えば PCIe device の passthrough といったモダンな機能をサポートしていない。
cloudinit 設定ファイル cloud_init.cfg
を作成する:
#cloud-config
users:
- name: root
ssh-authorized-keys:
- "<SSH_KEY>"
VM の作成
次のコマンドを実行し、VM を作成する。
$ terraform init
$ terraform apply
...
libvirt_cloudinit_disk.commoninit: Creating...
libvirt_volume.volume: Creating...
libvirt_volume.volume: Creation complete after 0s [id=/var/lib/libvirt/images/q35-test.qcow2]
libvirt_cloudinit_disk.commoninit: Creation complete after 0s [id=/var/lib/libvirt/images/commoninit_q35-test.iso;0a9b23c1-c711-469f-968b-e8adc566ea79]
libvirt_domain.vm: Creating...
libvirt_domain.vm: Creation complete after 1s [id=81cf19ea-54cb-4544-aaae-fbc8222fde98]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
cloudinit を用いながらも、q35
type によるマシン作成が成功した。
VM の作成 (失敗例)
成功例だけ見ても、ああそう、という感じなので、前置きで述べた失敗例も示す。main.tf
XSLT patch の部分だけコメントアウトしてみる:
...
#xml {
# xslt = file("${path.module}/patch.xsl")
#}
...
再度 VM の作成を試みると、今度はエラーになる:
$ terraform destroy # 先程の VM を削除
$ terraform apply
...
libvirt_cloudinit_disk.commoninit: Creating...
libvirt_volume.volume: Creating...
libvirt_volume.volume: Creation complete after 0s [id=/var/lib/libvirt/images/q35-test.qcow2]
libvirt_cloudinit_disk.commoninit: Creation complete after 0s [id=/var/lib/libvirt/images/commoninit_q35-test.iso;548e73d8-1fb5-4141-9a0e-efd49d8e078f]
libvirt_domain.vm: Creating...
╷
│ Error: error defining libvirt domain: unsupported configuration: IDE controllers are unsupported for this QEMU binary or machine type
│
│ with libvirt_domain.vm,
│ on main.tf line 20, in resource "libvirt_domain" "vm":
│ 20: resource "libvirt_domain" "vm" {
│
╵
Q35 type がサポートしない IDE bus が使われたことでエラーになっている。