TerraformとProxmox VE:IaCモデルで仮想マシンインフラを自動化する

Virtualization tutorial - IT technology blog
Virtualization tutorial - IT technology blog

クイックスタート:5分でTerraform + Proxmoxを動かす

私はProxmox VEで12台のVMとコンテナを管理するhomelabを運用しています。本番環境に上げる前にあらゆるものをテストするためのplaygroundです。6ヶ月間Terraformを使ってインフラ全体を管理してきた結果、手動でVMをクリックして作成したいとは思わなくなりました。すべてがコードになり、レビュー、ロールバック、数分での再構築が可能です。

詳細に入る前に、まず試してみて体感してください:

ステップ1:Terraformをインストール

# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
terraform version

ステップ2:ProxmoxでAPIトークンを作成

Proxmox Web UIにアクセスし、Datacenter → Permissions → API Tokens → Addの順に進みます。user root@pamのトークンを作成し、「Privilege Separation」のチェックを外してトークンに十分な権限を付与します。トークン文字列をコピーしてください — 表示されるのは1回だけです。

ステップ3:最小限の設定ファイル

terraform {
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = "~> 0.60"
    }
  }
}

provider "proxmox" {
  endpoint  = "https://192.168.1.100:8006/"
  api_token = "root@pam!terraform=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  insecure  = true  # 有効なSSL証明書がある場合は削除
}

resource "proxmox_virtual_environment_vm" "ubuntu_vm" {
  name      = "ubuntu-test"
  node_name = "pve"
  vm_id     = 200

  cpu {
    cores = 2
  }

  memory {
    dedicated = 2048
  }

  disk {
    datastore_id = "local-lvm"
    file_id      = "local:iso/ubuntu-22.04-live-server-amd64.iso"
    interface    = "virtio0"
    size         = 20
  }

  network_device {
    bridge = "vmbr0"
  }
}
terraform init
terraform plan
terraform apply

実行が完了すると、VM ubuntu-testがProxmoxのダッシュボードにすぐ表示されます — クリックは一切不要です。

詳細解説:bpg/proxmoxプロバイダーの仕組み

現在、Proxmox向けのプロバイダーとして主に2つが使われています:Telmate/proxmox(古く、メンテナンスが少ない)とbpg/proxmox(より新しく、アクティブで、より多くのリソースをサポート)。両方試した結果、Ubuntu 22.04でTelmateがCloud-Initを正しく処理しなかったため、bpgに移行しました。

主要なリソースタイプ

  • proxmox_virtual_environment_vm — VMの作成と管理
  • proxmox_virtual_environment_container — LXCコンテナ
  • proxmox_virtual_environment_download_file — ISOまたはクラウドイメージのダウンロード
  • proxmox_virtual_environment_pool — リソースプールの作成
  • proxmox_virtual_environment_user — Proxmoxユーザーの管理

Cloud-Initを使った実際のワークフロー

Cloud-Initが設定されたテンプレートからクローンする方法を毎日使っています。VMが起動したらすぐにSSH接続できます — 手動設定は不要です。

resource "proxmox_virtual_environment_vm" "web_server" {
  name      = "web-prod-01"
  node_name = "pve"
  vm_id     = 201

  clone {
    vm_id = 9000  # Ubuntu Cloud-InitテンプレートのID
    full  = true  # フルクローン、テンプレートに依存しない
  }

  cpu {
    cores = 4
    type  = "x86-64-v2-AES"
  }

  memory {
    dedicated = 4096
  }

  network_device {
    bridge = "vmbr0"
    model  = "virtio"
  }

  initialization {
    ip_config {
      ipv4 {
        address = "192.168.1.201/24"
        gateway = "192.168.1.1"
      }
    }

    user_account {
      username = "ubuntu"
      keys     = [file("~/.ssh/id_ed25519.pub")]
    }

    dns {
      servers = ["1.1.1.1", "8.8.8.8"]
    }
  }

  lifecycle {
    ignore_changes = [
      initialization,  # planの度に再適用されるのを防ぐ
    ]
  }
}

応用:for_eachとモジュールで複数VMを管理

VMの数が増えると — 私の場合は12台 — リソースブロックのコピーペーストではメンテナンスが困難になります。for_eachとlocalsを組み合わせることでスッキリ解決できます:

locals {
  vms = {
    "web-01" = { cores = 2, memory = 2048, ip = "192.168.1.201", vm_id = 201 }
    "web-02" = { cores = 2, memory = 2048, ip = "192.168.1.202", vm_id = 202 }
    "db-01"  = { cores = 4, memory = 8192, ip = "192.168.1.210", vm_id = 210 }
    "cache"  = { cores = 2, memory = 4096, ip = "192.168.1.220", vm_id = 220 }
  }
}

resource "proxmox_virtual_environment_vm" "vms" {
  for_each  = local.vms
  name      = each.key
  node_name = "pve"
  vm_id     = each.value.vm_id

  clone {
    vm_id = 9000
    full  = true
  }

  cpu {
    cores = each.value.cores
  }

  memory {
    dedicated = each.value.memory
  }

  initialization {
    ip_config {
      ipv4 {
        address = "${each.value.ip}/24"
        gateway = "192.168.1.1"
      }
    }
    user_account {
      username = "ubuntu"
      keys     = [file("~/.ssh/id_ed25519.pub")]
    }
  }
}

新しいVMを追加するには、locals.vmsに1行追加するだけです。削除したい場合は、その行を削除してterraform applyを実行します。

AnsibleとのOutput連携

TerraformがVMを作成した後、Ansibleが内部のプロビジョニングを担当します。この2つのツールを組み合わせるのが、私が本番環境で使っているワークフローです:

output "vm_ips" {
  value = {
    for k, v in proxmox_virtual_environment_vm.vms :
    k => v.initialization[0].ip_config[0].ipv4[0].address
  }
}
# AnsibleのダイナミックインベントリのIPを取得
terraform output -json vm_ips | jq -r 'to_entries[] | "\(.key) ansible_host=\(.value | split("/")[0])"'

6ヶ月の本番運用で得た実践的なTips

1. Cloud-Initテンプレートを正しく作成する — 最重要ステップ

テンプレートは手動インストールのISOからではなく、クラウドイメージから作成する必要があります。これが最初に最も時間を費やした部分です:

# Proxmoxホスト上で実行
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img

qm create 9000 --name "ubuntu-22.04-cloud" --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
qm importdisk 9000 jammy-server-cloudimg-amd64.img local-lvm
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
qm set 9000 --boot c --bootdisk scsi0
qm set 9000 --ide2 local-lvm:cloudinit
qm set 9000 --serial0 socket --vga serial0
qm set 9000 --agent enabled=1
qm template 9000

2. リモートバックエンドでstateファイルを安全に保存

stateファイル(terraform.tfstate)は、TerraformがどのVMが存在するかを把握するために使用されます。このファイルを失うと、すべてのトラッキングが失われます。セルフホストのMinIOをS3バックエンドとして使用するのは、homelabに適した解決策です:

terraform {
  backend "s3" {
    bucket                      = "terraform-state"
    key                         = "proxmox/homelab.tfstate"
    region                      = "main"
    endpoint                    = "http://192.168.1.50:9000"
    skip_credentials_validation = true
    skip_metadata_api_check     = true
    skip_region_validation      = true
    force_path_style            = true
  }
}

3. applyの前に必ずplanを確認する

# planをファイルに保存し、十分に確認してからapply
terraform plan -out=tfplan
terraform show tfplan
terraform apply tfplan

# 特定のVMを削除(全削除しない)
terraform destroy -target='proxmox_virtual_environment_vm.vms["web-01"]'

よくあるエラーと対処法

  • Cloud-Initが適用されない:テンプレートにcloudinit IDEドライブがあるか確認 — qm set 9000 --ide2 local-lvm:cloudinitのステップが不足している
  • applyでPermission denied:APIトークンにVM.Allocate、VM.Clone、Datastore.AllocateSpace、VM.Config.Networkの権限が必要
  • Terraformが”waiting for VM to start”で止まる:テンプレートにQEMU Guest Agentがインストールされていない — qm set 9000 --agent enabled=1のステップが不足している
  • destroyが成功しない:VMにスナップショットがある場合、Terraformは自動削除できない — 先にProxmoxで手動でスナップショットを削除する必要があります。このエラーのデバッグに2時間かかったことがあります

Terraformでhomelab全体のインフラをセットアップし終えると、必要に応じて10分もかからず最初から再構築できます。それがProxmoxとIaCに初期設定の時間を費やす価値がある理由です。

Share: