zaki work log

作業ログやら生活ログやらなんやら

[Proxmox VE] Terraformを使った設定済みVMプロビジョニング (telmate/proxmoxプロバイダ)

  • 2025.11.08:再実行時の変更について追記
  • 2025.11.24:proxmox_cloud_init_diskリソースを使ったCloud-init設定と、bpg/proxmoxプロバイダについて追記

クラウドイメージのテンプレートとcloud-initを使ったProxmoxでの設定済みVMのプロビジョニングや、それをAnsibleで自動化するというのは以前にやってたけど、それをより宣言型になるようTerraform対応ってのはやるやる詐欺のまま放置したまま…
ただ最近仕事でこのパターンの組み合わせをやりそうな流れになってきたので、手元の環境で試してみた。

前提として、クラウドイメージを使ったVMのテンプレートがすでにある状態。
本エントリでは、Ubuntu 24.04をクローン可能な ubuntu2404-template を用意してある。

pve.proxmox.com

なお、テンプレート作成もAnsible対応可能

接続設定

認証と権限

あるべき状態としては「Terraform実行用に適切な権限を持たせたユーザーのトークンを使用」が理想だが、ひとまずコードが動くことの確認をしたかったので、rootのパスワード認証で実施。

パスワード認証の場合は以下の環境変数をセットしておけばOK

  • PM_USER=root@pam
  • PM_PASS=rootのパスワード

トークンの場合は以下

  • PM_API_TOKEN_ID
  • PM_API_TOKEN_SECRET

Docs overview | Telmate/proxmox | Terraform | Terraform Registry

※ 同様の理由でバックエンド設定も省略

required_providers/provider設定

installation | Guides | Telmate/proxmox | Terraform | Terraform Registry

2025.09.14時点で最新が3.0.2-rc04なのでそれを指定。
pm_api_urlにPVEのURLを https://<pveのアドレス>:8006/api2/json の形式で記述する。

※ URLは環境変数 PM_API_URL でも指定可能

terraform {
  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = "3.0.2-rc04"
    }
  }
}

provider "proxmox" {
  pm_api_url      = "https://<pveのアドレス>:8006/api2/json"
  pm_tls_insecure = true
}

いったんここまででinitを実行してプロバイダをインストール。

$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding telmate/proxmox versions matching "3.0.2-rc04"...
- Installing telmate/proxmox v3.0.2-rc04...
- Installed telmate/proxmox v3.0.2-rc04 (self-signed, key ID A9EBBE091B35AFCE)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://developer.hashicorp.com/terraform/cli/plugins/signing
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

VMの作成 (テンプレートからのクローンとcloud-init設定)

前述のterraformproviderに加えてこんな感じ。

TerraformのPVEにおけるVM作成はシンプルで proxmox_vm_qemu リソースのみで実現する。
また、Ansibleの場合(クローンするタスクと、設定更新のタスクと複数必要)と異なり一発で行ける。

proxmox_vm_qemu | Resources | Telmate/proxmox | Terraform | Terraform Registry

resource "proxmox_vm_qemu" "pve-vm-example" {
  name = "tf-vm-example"
  target_node = "pve01"
  vmid = 2002
  agent = 1
  scsihw = "virtio-scsi-pci"
  tags = "dev"

  clone = "ubuntu2404-template"

  cpu {
    cores = 2
    type = "x86-64-v2-AES"
  }
  memory = 8192
  network {
    id = 0
    bridge = "vmbr0"
    model = "e1000"
  }
  network {
    id = 1
    bridge = "vmbr1"
    model = "e1000"
  }
  disk {
    slot = "ide0"
    type = "cloudinit"
    storage = "local-lvm"
  }
  disk {
    slot = "scsi0"
    type = "disk"
    storage = "local-lvm"
    size = "15G"
    format = "raw"
  }

  # serial
  serial {
    id = "0"
    type = "socket"
  }

  # cloud-init
  os_type = "cloud-init"

  ciuser = "zaki"
  cipassword = "curry_tabetai"
  sshkeys = "ssh-ed25519 AAA...[snip]"
  nameserver = "192.168.0.4"
  ipconfig0 = "ip=192.168.0.78/24,gw=192.168.0.1"
  ipconfig1 = "ip=172.16.0.78/23"
}

VMのテンプレートはクラウドイメージを使って事前に作成してあるものを使用する。
上記この場合、テンプレート名は"ubuntu2404-template"を使用。

※ 2025.11.08更新

当初は以下のように「scsi0/disk → ide0/cloudinit」の順に記述していが、これだと再実行時にno changesとならずに「cloudinit → disk」の順に変更が走るような動作となったので、入れ替えている。
入れ替えればno changesになる。
(telmate/proxmox v3.0.2-rc04で確認)

  disk {
    slot = "scsi0"
    type = "disk"
    storage = "local-lvm"
    size = "15G"
  }
  disk {
    slot = "ide0"
    type = "cloudinit"
    storage = "local-lvm"
  }

VM設定

主なパラメタと設定例は以下の通り

パラメタ 内容
name VM
target_node クラスタ内のVMを作成するPVEノード
vmid VM ID
os_type cloud-initを設定(後述)
agent QEMU Guest Agentの有効化
memory メモリサイズ(MB)
clone クローン元テンプレート名
scsihw virtio-scsi-pci

scsihwの設定はドキュメントには特に言及はないが、手元の環境では未設定の場合はVMが動作しなかった。
正常に動作している既存VMの設定を参考にvirtio-scsi-pciを指定すれば動作した。

※ 2025.11.08追記:tagsでタグを指定できるが、未設定あるいは空文字("")やnull指定だと、再実行時にno changesにならない。実害はないが…仕様??

  # proxmox_vm_qemu.pve-vm-example will be updated in-place
  ~ resource "proxmox_vm_qemu" "fpve-vm-example" {
        id                     = "pve01/qemu/2002"
        name                   = "pve-vm-example"
      - tags                   = " " -> null
        # (52 unchanged attributes hidden)

CPU設定 / cpu { ... }

パラメタ 内容
cores CPUコア
type CPU種別

ネットデバイス設定 / network { ... }

idはUIで確認できるnet0などの数字部分で、0から順に使っていけば良い。
複数のNICを使う場合はidの値を増やして複数記述する。

パラメタ 内容
id バイスID
bridge ネットワークブリッジ(vmbr0など)
model NICのモデル

ストレージ / disk { ... }

ネットワークと同様、使用する分だけ複数記述する。

パラメタ 内容
slot scsi0ide0などの種別と番号
type diskcloudinitなどストレージ種別
storage PVEにおけるストレージ名
size type=diskの場合のディスクサイズ

テンプレートからクローンする際に重要なのは、ディスクサイズを元のテンプレートのディスクサイズより小さい値を指定すると「Boot failed: not a bootable disk」「No bootable device.」と出力され起動に失敗するので注意

シリアルポート / serial { ... }

テンプレートからクローンする際はこれを指定しないと「コンソール」で接続に失敗するため設定しておく。

パラメタ 内容
id 0
type socket

cloud-init関連

cloud-initで与えられるパラメタを設定する。
Ansibleと同じくciがprefixにつくものが多いためわかりやすいはず。

パラメタ 内容
ciuser ユーザー名
cipassword パスワード
sshkeys 公開鍵
nameserver ネームサーバー設定
ipconfig0 nic0(network.id=0)の設定
ipconfig1 nic1(network.id=1)の設定

NICの設定は必要なだけセットする。
なお、前述のos_typeにはcloud-initを設定しておくのが無難だと思うが、手元の環境では未設定でも影響しなかった。ドキュメントの記載は以下の通りなので、ip=の設定があれば未設定でも良さそうな気はする。

When os_type is cloud-init not setting ip= is equivalent to skip_ipv4 == true and ip6= to skip_ipv6 == true .

proxmox_cloud_init_disk

※ 2025.11.24追記
Proxmoxが標準で用意しているCloud-initの項目以外のカスタマイズについては、proxmox_cloud_init_diskリソースがあり、これを使うことでパッケージインストール他Cloud-initの機能をフルに(たぶん。未確認)活用できる。
が、この機能は、以前動作確認したPVEのスニペット領域に配置するカスタムCloud-initファイルを使用した構成と異なり、上記のciuserなどのPVE提供の機能と併用が(少なくとも試した限りでは)不可のため、「設定済みVMをプロビジョニング」する場合はユーザーやネットワークその他もろもろ完全マニュアルで指定する必要があるので注意。

このリソースを使用する場合は、ざっと以下の通りで、proxmox_cloud_init_diskでCloud-init定義を記述し、proxmox_vm_qemuVM定義でCloud-init定義を参照する。
前述の通りこのCloud-init定義にすべてを記述する必要があるため、下記の内容のみだとユーザー定義は書かれた通り「ユーザーが作成されるだけ」になり、デフォルトシェルやsudoers設定が無かったりで、率直なところ使い勝手はあまりよくない。

resource "proxmox_cloud_init_disk" "ci" {
  name = local.vm_name
  pve_node = local.pve_node
  storage = "fileserver"

  meta_data = yamlencode({
    instance_id = sha1(local.vm_name)
    local-hostname = local.vm_name
  })

  user_data = <<-EOT
  #cloud-config
  users:
    - ${var.username}
  chpasswd:
    expire: false
    users:
    - name: ${var.username}
      password: ${var.password}$
      type: text
  ssh_authorized_keys:
    - ${var.ssh_pub_key}
  packages:
    - podman
    - qemu-guest-agent
  runcmd:
    - systemctl start qemu-guest-agent
  EOT

  network_config = <<-EOT
  version: 1
  config:
  - type: physical
    name: ens18
    subnets:
    - type: dhcp
  EOT
}

resource "proxmox_vm_qemu" "tf-vm-ci-sample" {
  name        = local.vm_name
  target_node = local.pve_node
  vmid        = 2003

  agent       = 1
  scsihw      = "virtio-scsi-pci"
  tags        = "dev"
  os_type = "cloud-init"

  cpu {
    cores = 2
    type  = "x86-64-v2-AES"
  }
  memory = 4096

  disks {
    scsi {
      scsi0 {
        disk {
          size    = "15G"
          storage = "local-lvm"
          format  = "raw"
        }
      }
      scsi1 {
        cdrom {
          iso = proxmox_cloud_init_disk.ci.id
        }
      }
    }
  }
  network {
    id     = 0
    bridge = "vmbr0"
    model  = "e1000"
  }
  serial {
    id   = 0
    type = "socket"
  }

  # [...] 以下省略

これについては、telmate/proxmoxでなくbpg/proxmoxプロバイダを使うといい感じに定義できるのを確認。以下参照。

zaki-hmkc.hatenablog.com

関連

zaki-hmkc.hatenablog.com

zaki-hmkc.hatenablog.com

zaki-hmkc.hatenablog.com

雑感

Ansibleの場合は(明示的にブートしなければ)VMを作るところまでだが、Terraformだとbootしてcloud-initによる初期設定まで全部行われるため(※ 2025.11.08追記:これは vm_state オプションによる動作。デフォルトがrunningのため起動する)、terraform applyを実行すれば手元の環境だと約60秒後にはpingの応答があるくらいのスピード感でVMを生成できて非常によかった。

あとAWSでしかTerraform使ったことなかったため、ローカルネットワーク上にあるPVEにterraform planすると秒で結果が出力されるのはビビった。笑