2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Terraform + Proxmox】Cloud-initでVM自動構築してみた

Posted at

1. はじめに

なぜこの記事を書いたのか

自宅でProxmox VEを使ってホームラボを運用していますが、検証用VMを作成するたびに手動で30分程度かかってしまい、「これは自動化できないか?」と思ったのがきっかけです。

最近学習を始めたTerraformと組み合わせることで、VM作成を完全自動化できないかチャレンジしてみました。

この記事で分かること

  • ProxmoxとTerraformを組み合わせたVM自動構築の方法
  • Cloud-initを使ったユーザー作成・SSH設定の自動化
  • 手動30分→自動2分への時短効果

想定読者

  • Proxmox環境でVM作成の効率化を図りたい方
  • Terraformの基礎的な使い方を知りたい方
  • 自宅ラボの運用を改善したい方

試行錯誤の過程で学んだポイントも含めて、実践的な内容をお届けします。

2. 動作環境

  • PC

    • HW: Apple M2 MacBook Air
    • OS: macOS Sequoia 15.0
    • Terraform: v1.5.7 on darwin_arm64
      • Telmate/proxmox: v3.0.2-rc01
  • 自宅サーバ

    • HW: GMKtec G5 12+512 x2台
    • OS: Proxmox VE 8.4.0
    • VM: Red Hat Enterprise Linux release 9.6 (Plow)
      • cloud-init: 24.4-4.el9_6.3

構成図

image.png

3. 事前準備

3-1. Proxmoxでの作業

前提:OSイメージのダウンロードについては各自で実施、VMの作成までは完了している状態
今回使用したOS: rhel-9.4-x86_64-boot.iso

上記のイメージはcloud-init非対応のため、まずはcloud-initのインストールを行います。
※cloud-initの利用目的:仮想マシン(VM)の初回起動時にネットワーク設定、ユーザー作成も含めて自動化するため

dnf install cloud-init
systemctl status cloud-init
○ cloud-init.service - Cloud-init: Network Stage
     Loaded: loaded (/usr/lib/systemd/system/cloud-init.service; enabled; preset: disabled)
     Active: inactive (dead)

3-2. テンプレート作成

テンプレートの作成は対象のVMを右クリック -> テンプレートに変換 -> はい を選択することでVMからテンプレートに変換することができます。
スクリーンショット 2025-07-19 17.29.45.png
スクリーンショット 2025-07-19 17.29.56.png
※便宜上テンプレート名は rhel9.4-cloudinit に変更してから使用しています。

3-3. cloud-init-user.ymlファイルの配置

Proxmoxサーバー上で以下を実行:

# snippetsディレクトリを作成(存在しない場合)
sudo mkdir -p /var/lib/vz/snippets

# ローカルからファイルをコピー
scp cloud-init-user.yml root@192.168.0.150:/var/lib/vz/snippets/

# 権限設定
sudo chmod 644 /var/lib/vz/snippets/cloud-init-user.yml

4. tfファイル作成

ファイル階層

.
├── main.tf
└── cloud-init-user.yml

main.tf

main.tf
# プロバイダ設定
terraform {
  required_providers {
    proxmox = {
      source  = "Telmate/proxmox"
      version = "3.0.2-rc01"
    }
  }
}

provider "proxmox" {
  pm_api_url      = "https://192.168.0.150:8006/api2/json" # URLは環境ごとに差し替え
  pm_user         = "root@pam"
  pm_password     = "PASSWORD" # は環境ごとに差し替え
  pm_tls_insecure = true # SSL証明書の検証を無効にする場合(必要な場合)
}

# VM リソース定義
resource "proxmox_vm_qemu" "rhel94-cloudinit" {
  vmid        = 999                    # VM ID番号
  name        = "rhel94-cloudinit"     # VM名
  target_node = "pve02"                # 作成先ノード
  agent       = 1                      # QEMU Guestエージェント有効
  memory      = 1024                   # メモリ1GB
  boot        = "order=scsi0"          # ブート順序
  clone       = "rhel9.4-cloudinit"    # クローン元テンプレート
  scsihw      = "virtio-scsi-single"   # SCSIコントローラー
  vm_state    = "running"              # VM状態(起動状態)
  automatic_reboot = true              # 自動再起動有効
  
  cpu {
    cores = 1    # 1コア
  }

  # Cloud-Init configuration
  # カスタムcloud-init設定でhandsonユーザー作成とSSH設定を変更
  cicustom   = "user=local:snippets/cloud-init-user.yml"  # カスタム設定ファイル
  ciupgrade  = true                                       # パッケージ更新有効
  nameserver = "1.1.1.1 8.8.8.8"                        # DNS設定
  ipconfig0  = "ip=192.168.0.101/24,gw=192.168.0.1,ip6=dhcp"  # IP設定
  skip_ipv6  = true                                       # IPv6無効

  # cicustomを使用する場合、ciuserとcipasswordは無効になるのでコメントアウト
  # cicustomのcloud-init-user.ymlで管理
  # ciuser     = "root"
  # cipassword = "PASSWORD!"

  # Most cloud-init images require a serial device for their display
  serial {
    id = 0    # コンソール表示用
  }

  disks {
    scsi {
      scsi0 {
        # We have to specify the disk from our template, else Terraform will think it's not supposed to be there
        disk {
          storage = "local-lvm"    # ストレージ
          size    = "20G"          # 20GB
        }
      }
    }
    ide {
      # Some images require a cloud-init disk on the IDE controller, others on the SCSI or SATA controller
      ide2 {
        cloudinit {
          storage = "local-lvm"    # cloud-init用ドライブ
        }
      }
    }
  }

  network {
    id     = 0          # ネットワークID
    bridge = "vmbr0"    # ブリッジ
    model  = "virtio"   # ネットワークモデル
  }
}

cloud-init-user.yml

cloud-init-user.yml
#cloud-config

# ホスト名を明示的に設定(複数の方法で確実に)
hostname: rhel94-cloudinit
fqdn: rhel94-cloudinit.hands-on.lab
prefer_fqdn_over_hostname: true
manage_etc_hosts: true

users:
  # rootユーザーの設定(Terraformのciuser、cipasswordを反映)
  - name: root
    plain_text_passwd: password
    lock_passwd: false
    ssh_authorized_keys: []
  # handsonユーザーの設定
  - name: handson
    # ハッシュ化されたパスワードを使う場合:
    # passwd: $6$rounds=4096$xyz$abcdefghijklmnopqrstuvwxyz
    # 平文パスワードを使う場合:
    plain_text_passwd: password
    lock_passwd: false
    shell: /bin/bash
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: [wheel, sudo]
    ssh_authorized_keys: []
    home: /home/handson

# パッケージのアップデート
package_upgrade: true

# 必要なパッケージをインストール
packages:
  - git

# SSH設定を明示的に行う
ssh_pwauth: true
disable_root: false

# サービスの有効化
runcmd:
  # SSH設定でrootログインとパスワード認証を有効にする
  - sed -i 's/#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config
  - sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
  - sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
  - systemctl restart sshd
  - echo "Cloud-init setup completed" > /var/log/cloud-init-custom.log

cloud-init-user.yml はproxmox(pve02)の /var/lib/vz/snippets に格納しておく必要があります。

5. 実行結果

terraform plan
・・・略・・・
Plan: 1 to add, 0 to change, 0 to destroy.

エラーがないことを確認

terraform apply --auto-approve
Plan: 1 to add, 0 to change, 0 to destroy.
proxmox_vm_qemu.rhel94-cloudinit: Creating...
proxmox_vm_qemu.rhel94-cloudinit: Still creating... [10s elapsed]
・・・略・・・
proxmox_vm_qemu.rhel94-cloudinit: Creation complete after 1m38s [id=pve02/qemu/999]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

今回の環境では合計でcloud-initの環境まで2分程度かかりました。
VM作成: 1m30s
VM作成~cloud-init実行完了まで: 30s

変更内容の確認

handsonユーザーが作成されておりSSHできるか等を確認

❯ ssh 192.168.0.101 -l root
root@192.168.0.101's password:
Last failed login: Tue Jul 22 07:40:10 JST 2025 from 192.168.0.12 on ssh:notty
There was 1 failed login attempt since the last successful login.
Last login: Sun Jul 20 14:50:55 2025

[root@rhel94-cloudinit home]# ll /home/
合計 0
drwx------ 3 handson handson 95  7月 22 07:45 handson

問題なく変更できていることを確認できました。

cloud-init関連の切り分け

cloud-init周りでエラーが出ている際は下記のコマンドで状況を確認し適宜切り分けを行う必要があります。

# 1. cloud-initのクリーンアップ
sudo cloud-init clean

# 2. cloud-initの再実行
sudo cloud-init init

# 3. cloud-configの再実行
sudo cloud-init modules --mode config

# 4. final段階の再実行
sudo cloud-init modules --mode final

# 5. ステータス確認
sudo cloud-init status

# cloud-initの状態確認
sudo cloud-init status
sudo cloud-init status --long
sudo cloud-init analyze show

# journalログ確認
sudo journalctl -u cloud-init
sudo journalctl -u cloud-final

6. 注意事項・改善点

セキュリティ面

  • 本記事のパスワード設定は検証用: 本番環境では適切なパスワードを設定
  • SSH鍵認証の推奨: パスワード認証より安全
  • rootログインの制限: 必要に応じて無効化を検討

7. まとめ

今回の検証を通じて学んだポイント:

技術的な学び

  • cicustomとTerraformの設定の関係: cicustomを使用する場合、ciuser/cipasswordは無効になる
  • cloud-initの実行順序: bootcmd → write_files → runcmd の順で実行される
  • SSH設定の重要性: RHEL系ではデフォルトでrootログインが制限されている

自動化の効果

これまで手動で30分程度かかっていた作業が、コマンド1つで2分程度に短縮できました。特にハンズオン環境のような繰り返し作成する環境では大幅な効率化が期待できそうです。

8. 参考資料

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?