Using Terraform with Proxmox

Using Terraform with Proxmox

As a promised follow-up to my previous post, today I will outline the process necessary to set up and utilize HashiCorp Terraform within Proxmox.

To preface, Terraform is an infrastructure as code tool, letting you spin up, modify, and destroy infrastructure.  Terraform is extremely versatile, utilizing Terraform Providers to interface with with the APIs of everything from cloud providers, such as AWS, to on-prem hypervisors, like Proxmox or VMWare.

Unfortunately, there is no 0fficial provider for Proxmox so we will be making use of Telmate's Proxmox provider, listed here.  While not official, I have found it to be stable and entirely sufficient for someone to familiarize themselves with Terraform in the comfort of their Proxmox lab.

Special thanks to Austin over at his site for providing some valuable info to build on.

💡
Ensure you have already set up a VM template with Proxmox before proceeding. You can find that guide here.

Getting Started

Installation

To start out, you will need to first download and install Terraform onto the client device that you will be running your Terraform commands from.  Commands issued from your client will be used by Terraform to then perform your instructions on your Proxmox host.  

Terraform can be installed on most common OS's, and for my purposes, I will be installing it on my MacBook Pro using Homebrew:

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Additional installation instructions for other OS's can be found here: https://developer.hashicorp.com/terraform/downloads

Preparing Proxmox

Before we can start spinning things up with Terraform, we must first configure Proxmox to be able to communicate with Terraform.

pveum role add terraform-role -privs "VM.Allocate VM.Clone VM.Config.CDROM VM.Config.CPU VM.Config.Cloudinit VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Monitor VM.Audit VM.PowerMgmt Datastore.AllocateSpace Datastore.Audit"

Start by SSH'ing to your Proxmox server and create the terraform-role.  The privs, as you might suspect, appended to the end of this command, limit the scope of what this new terraform-role can and cannot do.

Next, create the terraform user and authentication token.

pveum user add terraform@pve
pveum aclmod / -user terraform@pve -role terraform-role
pveum user token add terraform@pve terraform-token --privsep=0

After running the last command, you will be presented with your token. Please make note of this token now, as you will not be able to retrieve it again in the future.

┌──────────────┬──────────────────────────────────────┐
│ key          │ value                                │
╞══════════════╪══════════════════════════════════════╡
│ full-tokenid │ terraform@pve!terraform-token        │
├──────────────┼──────────────────────────────────────┤
│ info         │ {"privsep":"0"}                      │
├──────────────┼──────────────────────────────────────┤
│ value        │ XXXXX-XXXXX-XXXXX-XXXXX-XXXXX        │
└──────────────┴──────────────────────────────────────┘

Setting up Terraform on your Local Environment

It is time to move back over to the machine you will be running your Terraform commands from.  For my use-case, I will be performing these on my MacBook.

Start by creating a directory that you'd like for your Terraform code to live in.  For the purposes of this post, mine will be living in a directory named blog_demo

Creating a .gitignore file

Next, assuming you will be using some sort of git/source control for your Terraform code, I'd highly suggest creating a .gitignore file.  You can find the one I am using here.

Establishing Secrets

Next, establish a file you'd like for your secrets to live in.  Keeping in mind the .gitignore file we just created, I chose to use a file named terraform.tfvars, as it would be excluded from any sort of git/source control pushes in the future.

My terraform.tfvars file looks as follows:

token_secret = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
token_id = "terraform@pve!terraform-token"

Establishing Variables

Next, you will need to establish variables you'd like to use within your Terraform project.  While these could be hard-coded within your main.tf file (which we will cover next), it is easier for you to establish these separately for convenience in future projects.

To establish your variables, create a file named vars.tf and give it the following variables:

#Set your public SSH key here
variable "ssh_key" {
  default = "your_public_ssh_key_here"
}
#Establish which Proxmox host you'd like to spin a VM up on
variable "proxmox_host" {
    default = "proxmox_host_name"
}
#Specify which template name you'd like to use
variable "template_name" {
    default = "ubuntu-2204-template"
}
#Establish which nic you would like to utilize
variable "nic_name" {
    default = "vmbr<number>"
}
#Establish the VLAN you'd like to use
variable "vlan_num" {
    default = "place_vlan_number_here"
}
#Provide the url of the host you would like the API to communicate on.
#It is safe to default to setting this as the URL for what you used
#as your `proxmox_host`, although they can be different
variable "api_url" {
    default = "https://<proxmox_host_ip>:8006/api2/json"
}
#Blank var for use by terraform.tfvars
variable "token_secret" {
}
#Blank var for use by terraform.tfvars
variable "token_id" {
}

Creating your main.tf file

Lastly, you will need to create your main.tf file.  This is where everything comes together.  Your various variable files will be referenced here and it is also where you will need to establish your provider.

terraform {
  required_providers {
    proxmox = {
      source = "telmate/proxmox"
      #latest version as of Nov 30 2022
      version = "2.9.11"
    }
  }
}

provider "proxmox" {
  # References our vars.tf file to plug in the api_url 
  pm_api_url = var.api_url
  # References our secrets.tfvars file to plug in our token_id
  pm_api_token_id = var.token_id
  # References our secrets.tfvars to plug in our token_secret 
  pm_api_token_secret = var.token_secret
  # Default to `true` unless you have TLS working within your pve setup 
  pm_tls_insecure = true
}

# Creates a proxmox_vm_qemu entity named blog_demo_test
resource "proxmox_vm_qemu" "blog_demo_test" {
  name = "test_vm${count.index + 1}" # count.index starts at 0
  #name = "test-vm-01"
  count = 1 # Establishes how many instances will be created 
  target_node = var.proxmox_host

  # References our vars.tf file to plug in our template name
  clone = var.template_name
  # Creates a full clone, rather than linked clone 
  # https://pve.proxmox.com/wiki/VM_Templates_and_Clones
  full_clone  = "true"

  # VM Settings. `agent = 1` enables qemu-guest-agent
  agent = 1
  os_type = "cloud-init"
  cores = 2
  sockets = 1
  cpu = "host"
  memory = 2048
  scsihw = "virtio-scsi-pci"
  bootdisk = "scsi0"

  disk {
    slot = 0
    size = "50G"
    type = "scsi"
    storage = "intel-storage" # Name of storage local to the host you are spinning the VM up on
    # Enables SSD emulation
    ssd = 1
    # Enables thin-provisioning
    discard = "on"
    #iothread = 1
  }

  network {
    model = "virtio"
    bridge = var.nic_name
    tag = var.vlan_num # This tag can be left off if you are not taking advantage of VLANs
  }


  lifecycle {
    ignore_changes = [
      network,
    ]
  }
  #provisioner "local-exec" {
    # Provisioner commands can be run here.
    # We will use provisioner functionality to kick off ansible
    # playbooks in the future
    #command = "touch /home/tcude/test.txt"
  #}
}

Using Terraform

Now that you have configured all of the various files needed in order to spin up a VM within Proxmox, let's get started with utilizing Terraform to do so.

To start, ensure that all of your previously created files are within the same directory/project:

➜  blog_demo git:(master) ✗ ls -l
total 24
-rw-r--r--  1 tcude  staff  2038 Nov 30 16:37 main.tf
-rw-r--r--  1 tcude  staff   194 Nov 30 15:50 secrets.tfvars
-rw-r--r--  1 tcude  staff   830 Nov 30 16:10 vars.tf

Initialize Terraform

Next, run the command terraform init.  This will initialize a new working directory for use with Terraform.  More can be read about this command here.

Assuming that everything works correctly, you should see output similar to this:

➜  blog_demo git:(master) ✗ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding telmate/proxmox versions matching "2.9.11"...
- Installing telmate/proxmox v2.9.11...
- Installed telmate/proxmox v2.9.11 (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://www.terraform.io/docs/cli/plugins/signing.html

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.

Planning

Now that we have an initialized project, let's run a terraform plan command.  This will give us output indicating what will be created, modified, or destroyed by Terraform.

It is generally a good idea to specify an -out file to save this generated plan to, rather than relying on a speculative plan.

terraform plan -out plan

Once run, you should receive output that looks similar to the following:

Assuming your output looks similar to mine, you should now be ready to move to the final step

Applying the Terraform Plan

After all of your hard work, you are now ready to apply your plan and spin up a VM within your Proxmox server!

Doing so is as simple as running the command terraform apply plan.  This will use our saved plan file and apply it.

Assuming things run successfully, you should see the following output:

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

And here we can see that the new VM shows up within the Proxmox GUI

Deleting a VM with Terraform

Now that you have successfully spun up a VM with Terraform, it is important to know how to delete an existing VM.

Doing so is as simple as ensuring you're cd'd into the corresponding Terraform project directory and then running the command terraform destroy

Doing so will then present you with an output somewhat similar to what you would see when running a terraform apply but this time, shows everything Terraform will be deleting.

Upon successfully deleting your instance with terraform-destroy, you will be presented with the following output:

Conclusion

While it was a bit of lengthy process to get all of this working together, I hope you found it useful.  Terraform is fairly ubiquitous in enterprise environments and being able to utilize it within a homelab setting can be very valuable for learning.  

I hope you found this post helpful.  If you did, please consider subscribing to my RSS feed to see future posts:

https://tcude.net/rss

Resources

terraform-provider-proxmox/index.md at master · Telmate/terraform-provider-proxmox
Terraform provider plugin for proxmox. Contribute to Telmate/terraform-provider-proxmox development by creating an account on GitHub.
How to deploy VMs in Proxmox with Terraform - Austin’s Nerdy Things
Background I’d like to learn Kubernetes and DevOps. A Kubernetes cluster requires at least 3 VMs/bare metal machines. In my last post, I wrote about how to create a Ubuntu cloud-init template for Proxmox. In this post, we’ll take that template and use it to deploy a couple VMs via automation using T…