Proxmox VEとTerraformにNetboxを加えて仮想マシンとIPをセットで管理する話

## はじめに

前回 [Proxmox VEとTerraformでインターン生に仮想マシンを払い出す話](https://qiita.com/ymbk990/items/bd3973d2b858eb86e334) という記事を書きました。

最初はインターンシップのために始めてみたけど、案外利用シーンが多かったりして。  
そんなこんなで、簡単にVMが提供できるようになったら、どんどんVMが作っては捨てられていきます。  
すると、今度はIPアドレスの管理が行き届かなくなりました。

VMを作る度にExcelシートを更新していたら、大変です。  
そこで、TerraformでVMを作るついでに、IPAMツールである [Netbox](https://docs.netbox.dev/en/stable/) にもIPアドレスを登録できるようにしてみましょう。

## サンプル用のNetboxの構築

Netboxにはdockerイメージがありますので、この記事用に構築してみます。

https://github.com/netbox-community/netbox-docker のQuickStartにならって、以下のコマンドで起動します。  
手元の環境がDocker compose v2だったので `docker-compose` コマンドは `docker compose` に読み替えています。

```console
git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker
tee docker-compose.override.yml <<EOF
version: '3.4'
services:
  netbox:
    ports:
      - 8000:8080
EOF
echo "VERSION=v3.4.4-2.4.0" > .env
docker compose pull
docker compose up -d
```

`.env` ファイルでバージョンを指定しているのはTerraform Providerのサポートバージョンと合わせるためです。
少し待つと `admin` / `admin` でログインするところまで進みました。  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613240/84f2d378-1b81-973b-9231-6b899fe650a6.png)

特に引っ掛かりは無いと思いますが、netbox-dockerではdocker composeのhealthcheckが動いているため、Netboxの起動時の初期化が遅いと `container netbox-docker-netbox-1 is unhealthy` と出力されることがあります。  

```
$ docker compose up -d
[+] Running 3/4
 ⠿ Container netbox-docker-redis-cache-1  Running                                                                                                                                                                                                                       0.0s
 ⠿ Container netbox-docker-postgres-1     Running                                                                                                                                                                                                                       0.0s
 ⠿ Container netbox-docker-redis-1        Running                                                                                                                                                                                                                       0.0s
 ⠿ Container netbox-docker-netbox-1       Error                                                                                                                                                                                                                         0.5s
dependency failed to start: container netbox-docker-netbox-1 is unhealthy
```

そうなった場合は `docker compose logs -f netbox` でログを拾ってみましょう。  
初期化の途中であれば、少し待ってから再度 `docker compose up -d` 実行することで解決します。

## Terafform Netbox Provider

TerraformでNetboxを扱うためのNetbox Providerは、どうやらCommunityで作成された有力なものが2つあるようです。

* https://registry.terraform.io/providers/e-breuninger/netbox/latest/docs -> [Github](https://github.com/e-breuninger/terraform-provider-netbox)
* https://registry.terraform.io/providers/smutel/netbox/latest/docs -> [Github](https://github.com/smutel/terraform-provider-netbox)

特に選定理由があるわけでもありませんので、今回は1つ目の方(e-breuninger/netbox)を試してみます。

## Netbox用のtfファイルの作成

[Proxmox VEとTerraformでインターン生に仮想マシンを払い出す話](https://qiita.com/ymbk990/items/bd3973d2b858eb86e334) で使用したtfファイルを編集して、VMが使用するIPアドレスをNetboxに登録するようにしてみます。  
変更点は以下の通りです。

* providerに `e-breuninger/netbox 3.1.0` を追加
* netboxの接続情報を追加
    * api_token は https://github.com/netbox-community/netbox-docker#quickstart に書いてある通りです
* netbox_ip_address のリソースを追加

前回作成したファイルに以下の差分を追記します。（全文は記事下部に記載）

```diff
--- a/intern-vm.tf
+++ b/intern-vm.tf
@@ -11,6 +11,10 @@ terraform {
       source  = "telmate/proxmox"
       version = "2.9.11"
     }
+    netbox = {
+      source = "e-breuninger/netbox"
+      version = "3.1.0"
+    }
   }
 }
 
@@ -21,6 +25,11 @@ provider "proxmox" {
   pm_tls_insecure = true
 }
 
+provider "netbox" {
+  server_url = "http://netbox.example.com:8000"
+  api_token  = "0123456789abcdef0123456789abcdef01234567"
+}
+
 resource "proxmox_vm_qemu" "intern-vm" {
   name = "intern-vm-${var.username}"
   target_node = "pve-c01"
@@ -46,3 +55,8 @@ ${var.public_key}
 EOF
 
 }
+
+resource "netbox_ip_address" "intern_vm_eth0_ip" {
+  ip_address   = "${var.ip_address}/24"
+  status       = "active"
+}
```

## Terraformの実行前確認

Providerのインストールをして、planに問題なさそうかを確認します。

```
terraform init -upgrade
terraform plan -var-file sample01.tfvars
```

Netboxのバージョンが新しく、Terraform Providerがサポート範囲外となる場合は以下のように警告が出ます。  

```
╷
│ Warning: Possibly unsupported Netbox version
│ 
│   with provider["registry.terraform.io/e-breuninger/netbox"],
│   on intern-vm.tf line 28, in provider "netbox":
│   28: provider "netbox" {
│ 
│ Your Netbox version is v3.4.5. The provider was successfully tested against the following versions:
│ 
│   3.3.0, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5, 3.3.6, 3.3.7, 3.3.8, 3.3.9, 3.3.10, 3.4.0, 3.4.1, 3.4.2, 3.4.3, 3.4.4
│ 
│ Unexpected errors may occur.
╵
```

この記事の手順通りの場合、冒頭でNetboxのバージョンに `v3.4.4` を明示したので警告は出ないと思います。  
と言っても、v3.4.4とv3.4.5ではAPI仕様にそう大きな変更は無いでしょうから、警告を無視して利用する判断もあるかと思います。

## Terraformの実行とログ

それでは `terraform plan` が成功したら、早速実行してみましょう。  
Terraformで仮想マシンを作成するのと同時に、IPアドレスがNetboxに登録されるはずです。

```
$ terraform apply -var-file sample01.tfvars

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # netbox_ip_address.intern_vm_eth0_ip will be created
  + resource "netbox_ip_address" "intern_vm_eth0_ip" {
      + id          = (known after apply)
      + ip_address  = "192.168.0.11/24"
      + object_type = "virtualization.vminterface"
      + status      = "active"
    }

  # proxmox_vm_qemu.intern-vm will be created
  + resource "proxmox_vm_qemu" "intern-vm" {
      + additional_wait           = 0
      + agent                     = 0
      + automatic_reboot          = true
      + balloon                   = 0
      + bios                      = "seabios"
      + boot                      = "order=virtio0"
      + bootdisk                  = (known after apply)
      + ciuser                    = "sample01"
      + clone                     = "ubuntu-22.04a"
      + clone_wait                = 0
      + cores                     = 4
      + cpu                       = "host"
      + default_ipv4_address      = (known after apply)
      + define_connection_info    = true
      + force_create              = false
      + full_clone                = true
      + guest_agent_ready_timeout = 100
      + hotplug                   = "network,disk,usb"
      + id                        = (known after apply)
      + ipconfig0                 = "ip=192.168.0.11/24,gw=192.168.0.1"
      + kvm                       = true
      + memory                    = 8192
      + name                      = "intern-vm-sample01"
      + nameserver                = (known after apply)
      + numa                      = false
      + onboot                    = false
      + oncreate                  = true
      + os_type                   = "cloud-init"
      + preprovision              = true
      + reboot_required           = (known after apply)
      + scsihw                    = (known after apply)
      + searchdomain              = (known after apply)
      + sockets                   = 1
      + ssh_host                  = (known after apply)
      + ssh_port                  = (known after apply)
      + sshkeys                   = <<-EOT
            ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHZYaK+tKWpOVCxhKeTeoKLW/UoAOaj03QXPGrqY2JQm user@sample
        EOT
      + tablet                    = true
      + target_node               = "pve-c01"
      + unused_disk               = (known after apply)
      + vcpus                     = 0
      + vlan                      = -1
      + vmid                      = (known after apply)

      + disk {
          + backup             = 0
          + cache              = "none"
          + file               = (known after apply)
          + format             = (known after apply)
          + iops               = 0
          + iops_max           = 0
          + iops_max_length    = 0
          + iops_rd            = 0
          + iops_rd_max        = 0
          + iops_rd_max_length = 0
          + iops_wr            = 0
          + iops_wr_max        = 0
          + iops_wr_max_length = 0
          + iothread           = 0
          + mbps               = 0
          + mbps_rd            = 0
          + mbps_rd_max        = 0
          + mbps_wr            = 0
          + mbps_wr_max        = 0
          + media              = (known after apply)
          + replicate          = 0
          + size               = "120G"
          + slot               = (known after apply)
          + ssd                = 0
          + storage            = "local-lvm"
          + storage_type       = (known after apply)
          + type               = "virtio"
          + volume             = (known after apply)
        }

      + network {
          + bridge    = "vmbr0"
          + firewall  = false
          + link_down = false
          + macaddr   = (known after apply)
          + model     = "virtio"
          + queues    = (known after apply)
          + rate      = (known after apply)
          + tag       = -1
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

netbox_ip_address.intern_vm_eth0_ip: Creating...
proxmox_vm_qemu.intern-vm: Creating...
netbox_ip_address.intern_vm_eth0_ip: Creation complete after 0s [id=1]
proxmox_vm_qemu.intern-vm: Still creating... [10s elapsed]
proxmox_vm_qemu.intern-vm: Still creating... [20s elapsed]
proxmox_vm_qemu.intern-vm: Still creating... [30s elapsed]
proxmox_vm_qemu.intern-vm: Still creating... [40s elapsed]
proxmox_vm_qemu.intern-vm: Still creating... [50s elapsed]
proxmox_vm_qemu.intern-vm: Still creating... [1m0s elapsed]
proxmox_vm_qemu.intern-vm: Creation complete after 1m5s [id=pve-c01/qemu/103]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
```

## 実行結果の確認

Proxmox VEでは、仮想マシンが作成されて起動しています。

```
root@pve-c01:~# pvesh get /nodes/pve-c01/qemu/103/status/current
┌─────────────────┬────────────────────┐
│ key             │ value              │
╞═════════════════╪════════════════════╡
│ cpus            │ 4                  │
├─────────────────┼────────────────────┤
│ ha              │ {"managed":0}      │
├─────────────────┼────────────────────┤
│ maxdisk         │ 120.00 GiB         │
├─────────────────┼────────────────────┤
│ maxmem          │ 8.00 GiB           │
├─────────────────┼────────────────────┤
│ name            │ intern-vm-sample01 │
├─────────────────┼────────────────────┤
│ pid             │ 817200             │
├─────────────────┼────────────────────┤
│ qmpstatus       │ running            │
├─────────────────┼────────────────────┤
│ running-machine │ pc-i440fx-7.2+pve0 │
├─────────────────┼────────────────────┤
│ running-qemu    │ 7.2.0              │
├─────────────────┼────────────────────┤
│ status          │ running            │
├─────────────────┼────────────────────┤
│ uptime          │ 6m 17s             │
├─────────────────┼────────────────────┤
│ vmid            │ 103                │
└─────────────────┴────────────────────┘
```

Netboxにも、IPアドレスが登録されていることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613240/8a84c891-3e5c-a8f4-c0ac-eaaf40825cb7.png)

もちろん、削除も `terraform destroy -var-file sample01.tfvars` で簡単です。

## おしまい

今回はProxmox VEとTerraformにNetboxを追加で組み合わせて、VMを作る時にIPアドレスをNetboxに登録する方法を書いてみました。  
手元の機材を使うと、どうしても管理しなければならない点が増えてしまいます。しかし、ネットワークサービスを始めとした自社設備の管理が重要となる業界では、現在でもこういった情報の同期が必要なケースもありますので、上手く使ってみてください。


## 付録

差分ではなく、使用したtfファイル全文もこちらに掲載しておきます。

<details><summary>intern-vm.tf</summary>

```terraform
variable username {}
variable public_key {}
variable cores {}
variable memory {}
variable disk_size {}
variable ip_address {}

terraform {
  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = "2.9.11"
    }
    netbox = {
      source = "e-breuninger/netbox"
      version = "3.1.0"
    }
  }
}

provider "proxmox" {
  pm_api_token_id = "root@pam!qiita-sample"
  pm_api_token_secret = "d2f33ee3-bf19-4155-bd7c-e23a3f14522d"
  pm_api_url = "https://pve-sample:8006/api2/json"
  pm_tls_insecure = true
}

provider "netbox" {
  server_url = "http://netbox.example.com:8000"
  api_token  = "0123456789abcdef0123456789abcdef01234567"
}

resource "proxmox_vm_qemu" "intern-vm" {
  name = "intern-vm-${var.username}"
  target_node = "pve-c01"
  clone = "ubuntu-22.04a"
  os_type = "cloud-init"
  boot = "order=virtio0"
  cores   = "${var.cores}"
  memory  = "${var.memory}"
  disk {
    storage = "local-lvm"
    type = "virtio"
    size = "${var.disk_size}G"
  }
  network {
    model = "virtio"
    bridge = "vmbr0"
    firewall = false
  }
  ipconfig0 = "ip=${var.ip_address}/24,gw=192.168.0.1"
  ciuser = "${var.username}"
  sshkeys = <<EOF
${var.public_key}
EOF

}

resource "netbox_ip_address" "intern_vm_eth0_ip" {
  ip_address   = "${var.ip_address}/24"
  status       = "active"
}
```

</details>