Post

Provision Virtual Machines in Proxmox using Terraform

A comprehensive guide on automating virtual machine deployment in Proxmox with Terraform.

What is Terraform?

Terraform, created by HashiCorp, is an Infrastructure as Code (IaC) tool that lets you manage and set up your infrastructure using code. In the past, setting up or configuring infrastructure meant doing it all manually. But now, with Terraform, you can easily deploy infrastructure through code instead of using a WebUI or SSH. Plus, it allows you to manage configurations across various public and private cloud environments.

Prepare User and API on Proxmox beforehand

Before installing Terraform, we must ensure that the user and API keys are created. This information is essential for deploying our VMs.

Step 1 - Create a User in Proxmox

Proxmox UI

  • Log in to Proxmox.
  • Navigate to the Datacenter.
  • Locate API Keys under Permissions.
  • Click on Add.
  • Provide a random Token ID.
  • Click Add to generate the keys.
  • Save the key securely, such as in a Password Manager or a hidden file.

Step 2 - Cloudinit Template as bash OS

Now, we need to create a cloud-init template that will serve as the base OS configuration. This template will be used when we deploy our code to your instance, ensuring that the machine is set up and configured according to your specifications.

I have already written a detailed blog post outlining the step-by-step process for creating a Cloud-init drive template in Proxmox. You can check it out at the following link.

Step 3 - Installation of Terraform

Next, we need to install Terraform to set up the environment, allowing us to create configuration files for deploying infrastructure. Terraform can be installed on Linux, Windows, or macOS, depending on the operating system of your main device.

Install dependencies
1
2
3
4
sudo apt update
sudo apt install  software-properties-common gnupg2 curl

Add hashicorp repository
1
2
3
4
curl https://apt.releases.hashicorp.com/gpg | gpg --dearmor > hashicorp.gpg
sudo install -o root -g root -m 644 hashicorp.gpg /etc/apt/trusted.gpg.d/

Install Terraform
1
2
3
sudo apt install terraform

Check the current version
1
2
3
terraform --version

Create a Config file on Editor

Now that we have Terraform installed on our machine, it’s time to create configuration files. These files will describe our infrastructure as code, enabling us to deploy virtual machines.

Step 1 - Create a main.tf file

Open your preferred text editor and create a file named main.tf. This file will serve as the primary configuration file where we will define our variables and states.

Step 2 - Add Proxmox provider - Telmate/proxmox

We need to let Terraform know which provider to use. A provider is basically the connector that allows Terraform to interact with different services. Since we’re working with Proxmox, we’ll need to use the Proxmox provider. The most reliable provider I’ve found is Telmate, an open-source project.

1
2
3
4
5
6
7
8
9
10
11
12
# file: main.tf

terraform {
  required_providers {
    proxmox = {
      source = "telmate/proxmox"
      version = "2.9.11"
    }
  }
}

Step 3 - Initialize Terraform to grab provider

We need to initialize Terraform before adding our state to ensure that the provider is downloaded and available for use. Upon initialization, we should receive a message confirming the successful setup, it should installed plugins and have created a lock file.

1
2
3
terraform init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# output message

Initializing the backend...
Initializing provider plugins...
- Reusing previous version of telmate/proxmox from the dependency lock file
- Using previously-installed telmate/proxmox v2.9.11

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.

Step 4 - Now describe your deployment state in main.tf

Once Terraform has been initialized, we need to define our deployment state according to our specifications and requirements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# file: main.tf

terraform {
  required_providers {
    proxmox = {
      source = "telmate/proxmox"
      version = "2.9.11"
    }
  }
}

# Configure the Proxmox provider
provider "proxmox" {
  # The URL of the Proxmox API
  pm_api_url      = "https://your-proxmox-server:8006/api2/json"
  # The user to authenticate as
  pm_user         = "root@pam"
  # The password for the user
  pm_password     = "your-password"
  # Whether to ignore TLS certificate errors
  pm_tls_insecure = true
}

# Define a new VM resource
resource "proxmox_vm_qemu" "example" {
  # The name of the VM
  name        = "terraform-vm"
  # The Proxmox node where the VM will be created
  target_node = "proxmox-node"
  # Clone an existing VM template to create the new VM
  clone {
    # The ID of the VM template to clone
    vm_id     = "100"
    # Whether to perform a full clone
    full_clone = true
  }

  # Specify the OS type, in this case, using cloud-init
  os_type = "cloud-init"
  # Configure the VM's disk
  disks {
    # The ID of the disk
    id           = 0
    # The size of the disk
    size         = "10G"
    # The type of the disk
    type         = "scsi"
    # The storage location for the disk
    storage      = "local-lvm"
    # The storage type
    storage_type = "lvm"
  }

  # Configure the VM's network interface
  network {
    # The ID of the network interface
    id      = 0
    # The model of the network interface
    model   = "virtio"
    # The bridge to connect the network interface to
    bridge  = "vmbr0"
  }

  # Configure the CPU settings
  cpu {
    # The number of sockets
    sockets = 1
    # The number of cores per socket
    cores   = 2
  }

  # Configure the memory settings
  memory {
    # The amount of dedicated memory in MB
    dedicated = 2048
  }
  # Add an SSH public key for access
  sshkeys = <<EOF
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3...
EOF

  # Set the IP configuration for the VM
  ipconfig0 = "ip=192.168.1.100/24,gw=192.168.1.1"
  # Manage lifecycle settings, ignoring changes to network and disks
  lifecycle {
    ignore_changes = [
      network,
      disks,
    ]
  }
}


Step 5 - Run Terraform Plan to review

After defining our desired deployment state according to our specifications, we need to run terraform plan. This command in Terraform enables you to preview the changes that will be made by your Terraform configuration before actually applying them.

1
2
3
terraform plan 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# output message

Terraform will perform the following actions:

  # proxmox_vm_qemu.example will be created
  + resource "proxmox_vm_qemu" "example" {
      + agent         = (known after apply)
      + balloon       = (known after apply)
      + bios          = (known after apply)
      + boot          = (known after apply)
      + bootdisk      = (known after apply)
      + clone         = {
          + full_clone = true
          + vm_id      = "100"
        }
      + cores         = 2
      + cpu           = {
          + cores   = 2
          + sockets = 1
        }
      + disks         = [
          + {
              + id           = 0
              + size         = "10G"
              + storage      = "local-lvm"
              + storage_type = "lvm"
              + type         = "scsi"
            },
        ]
      + id            = (known after apply)
      + ipconfig0     = "ip=192.168.1.100/24,gw=192.168.1.1"
      + memory        = {
          + dedicated = 2048
        }
      + name          = "terraform-vm"
      + network       = [
          + {
              + bridge = "vmbr0"
              + id     = 0
              + model  = "virtio"
            },
        ]
      + os_type       = "cloud-init"
      + sshkeys       = <<-EOF
            ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3...
        EOF
      + target_node   = "proxmox-node"
    }

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

Step 6 - Run Terraform Apply to deploy

Once we have reviewed all the changes that Terraform will implement through the configuration, we need to execute terraform apply. This command in Terraform applies the necessary changes to achieve the desired state as defined in your .tf files. It is the stage where Terraform actually modifies your infrastructure according to the execution plan generated by terraform plan.

1
2
3
terraform apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# output message

Terraform will perform the following actions:

  # proxmox_vm_qemu.example will be created
  + resource "proxmox_vm_qemu" "example" {
      + agent         = (known after apply)
      + balloon       = (known after apply)
      + bios          = (known after apply)
      + boot          = (known after apply)
      + bootdisk      = (known after apply)
      + clone         = {
          + full_clone = true
          + vm_id      = "100"
        }
      + cores         = 2
      + cpu           = {
          + cores   = 2
          + sockets = 1
        }
      + disks         = [
          + {
              + id           = 0
              + size         = "10G"
              + storage      = "local-lvm"
              + storage_type = "lvm"
              + type         = "scsi"
            },
        ]
      + id            = (known after apply)
      + ipconfig0     = "ip=192.168.1.100/24,gw=192.168.1.1"
      + memory        = {
          + dedicated = 2048
        }
      + name          = "terraform-vm"
      + network       = [
          + {
              + bridge = "vmbr0"
              + id     = 0
              + model  = "virtio"
            },
        ]
      + os_type       = "cloud-init"
      + sshkeys       = <<-EOF
            ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3...
        EOF
      + target_node   = "proxmox-node"
    }

Plan: 1 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

proxmox_vm_qemu.example: Creating...
proxmox_vm_qemu.example: Still creating... [10s elapsed]
proxmox_vm_qemu.example: Still creating... [20s elapsed]
proxmox_vm_qemu.example: Still creating... [30s elapsed]
proxmox_vm_qemu.example: Creation complete after 40s [id=terraform-vm]

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


Step 7 - Head back to Proxmox to verify

After running terraform apply to deploy our configuration, we need to return to the Proxmox interface to verify that the VM has been successfully deployed. Once confirmed, check the Cloud-init configuration in the Proxmox UI to ensure that all settings specified in the .tf file have been correctly applied before starting the VM.

Next, verify that you can SSH into the VM using the provided SSH key from the deployment. Once you have successfully accessed the VM via SSH, you should be all set.

Step 8 - Remove deployed VM using Terraform Destroy

By any means, If you ever decide that you no longer need the deployed VM and want to remove it from Proxmox, you can simply accomplish this by running terraform destroy command. This command will take care of tearing down the infrastructure managed by your Terraform setup. Essentially, it reverse everything that terraform apply did, wiping out all the resources defined in your .tf files.

1
2
3
terraform destroy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# output message

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

Terraform will perform the following actions:

  # aws_instance.example will be destroyed
  - resource "aws_instance" "example" {
      - ami           = "ami-0c55b159cbfafe1f0" -> null
      - instance_type = "t2.micro" -> null
      - tags          = {
          - "Name" = "example-instance"
        } -> null
    }

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

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

  Enter a value: yes

aws_instance.example: Destroying... [id=i-0abcd1234efgh5678]
aws_instance.example: Still destroying... [id=i-0abcd1234efgh5678, 10s elapsed]
aws_instance.example: Destruction complete after 15s

Destroy complete! Resources: 1 destroyed.


Ending Points

In summary, Terraform is a game changer for managing infrastructure. It lets you handle everything as code, so you can forget about the hassle of manual setups and configurations through WebUI or SSH. Whether you’re working with public or private clouds, Terraform makes it super easy to manage your infrastructure. By following the steps outlined in the blog posts, you’ll have a solid starting point to use Terraform for deploying your infrastructure, whether it’s on a local on-premise server or a cloud platform.

This post is licensed under CC BY 4.0 by the author.