Skip to content

-proxmox - vm's - vm - virtualization - terraform

Terraform with Proxmox

References

Cloud Init Intro

The complete terraform api reference for proxmox, find every variable you can set for the proxmox_vm_qemu provider: https://github.com/Telmate/terraform-provider-proxmox/blob/master/proxmox/resource_vm_qemu.go

Requirements:

  1. Proxmox
  2. Cloud-Init Optional but EXTREMELY RECOMMENDED
  3. A VM, I'm using a cloud-init Alpine VM for this test.

Set up Proxmox User and API Key

There are many articles on this step already, https://registry.terraform.io/providers/Telmate/proxmox/latest/docs is a good one.

Set up C&C

In the VM you need to install terraform.

Alpine:

apk add terraform --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community

Create some files

You need at bare minimum from my (minimal, cursory) understanding, vars.tf and main.tf files.

  1. main.tf : This file contains the main definition of your vm's
  2. vars.tf : Specify the variables you want to be more easily set without having to dig through the entirety of main.tf to do so.

Examples:

vars.tf

#Set your public SSH key here
variable "ssh_key" {
  default = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBi7A81T7smfUrtyqDjg8kRjiuNu6KmS/CGVBMOn0WAPg/k5D4uAZT3CsO/MrpwFyx5Zx+wFd82Y+e68WRzqV2gsNszCUiG+7BEWD+ArDMUf/zbj7vafR4xzm8f9bPVRmV9PPqjnauZadAcwEP7rGHa8n8Eun8khB/cyfkRU3K/ziE7vhVCku82ECsYr5vsHs9+M+Q6j/IXoKFD9blBdqVgwUR6NjvKmpIo2kqe2f64mKrE0x2F95KWsWKjVu0ugwjYrpwmLmQFJYr4xBa+XAlwL9K99rJQrcKWUskiupbtYs0OgQPEnYamqQjLgB0qe4DD9bB4N/6NZMioVA24oXx deadc0de@deadc0de-PC"
}
variable "hostname" {
  default = "terraform-test"
}
#Establish which Proxmox host you'd like to spin a VM up on
variable "proxmox_host" {
    default = "hpve"
}
#Specify which template name you'd like to use
variable "template_name" {
    default = "10GB-Alpine-Cloud"
}
#Establish which nic you would like to utilize
variable "nic_name" {
    default = "vmbr0"
}
#Establish the VLAN you'd like to use
variable "vlan_num" {
    default = "1"
}
#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://pve.centerionware.com:8006/api2/json"
}
#Blank var for use by terraform.tfvars
variable "token_secret" {
    default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
#Blank var for use by terraform.tfvars
variable "token_id" {
    default = "EXAMPLE@pve!terraform_api_token"
}

main.tf

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
}


#data "template_file" "user_data" {
#  template = file("cloud-init.yml")
#}


resource "proxmox_vm_qemu" "proxmox_vm" {
  count             = 1
  name              = "${var.hostname}-${count.index}"
  target_node       = var.proxmox_host
  clone             = var.template_name
  os_type           = "cloud-init"
  cores             = 1
  sockets           = "1"
  cpu               = "host"
  full_clone        = false
  agent             = 1
  memory            = 2048
  scsihw            = "virtio-scsi-pci"
  bootdisk          = "virtio0"
#  cicustom          = data.template_file.user_data.rendered
  disk {
    slot              = "0"
    size            = "10G"
    type            = "virtio"
    storage         = "local-zfs"
    iothread        = 1
  }
  network {
  #    id              = "0"
    model           = "virtio"
    bridge          = "vmbr0"
  }
  lifecycle {
    ignore_changes  = [
      network,
    ]
  }

  cicustom = "user=cloud-inits:snippets/gitlab-runner-user.yml,network=cloud-inits:snippets/gitlab-runner-network.yml,vendor=cloud-inits:snippets/gitlab-runner-install.yml"

}

WIP

main.tf is a WIP. Currently it uses cloud-init snippets stored in a directory volume named 'cloud-inits' on the proxmox hosts. Future versions will login via ssh to the proxmox server and upload these files using terraforms built in providers to do exactly that. An example (from: https://yetiops.net/posts/proxmox-terraform-cloudinit-saltstack-prometheus/)

(This would be modified to the desired configuration and added to main.tf, probably near the top, for each of the cloud-init files that need to be uploaded.)

# Source the Cloud Init Config file
data "template_file" "cloud_init_deb10_vm-01" {
  template  = "${file("${path.module}/files/cloud_init_deb10.cloud_config")}"

  vars = {
    ssh_key = file("~/.ssh/id_rsa.pub")
    hostname = "vm-01"
    domain = "yetiops.lab"
  }
}

# Create a local copy of the file, to transfer to Proxmox
resource "local_file" "cloud_init_deb10_vm-01" {
  content   = data.template_file.cloud_init_deb10_vm-01.rendered
  filename  = "${path.module}/files/user_data_cloud_init_deb10_vm-01.cfg"
}

# Transfer the file to the Proxmox Host
resource "null_resource" "cloud_init_deb10_vm-01" {
  connection {
    type    = "ssh"
    user    = "root"
    private_key = file("~/.ssh/id_rsa")
    host    = "10.15.31.7"
  }

  provisioner "file" {
    source       = local_file.cloud_init_deb10_vm-01.filename
    destination  = "/var/lib/vz/snippets/cloud_init_deb10_vm-01.yml"
  }
}

Running terraform

Init

Now with vars.tf and main.tf in place, and terraform installed, terraform can be initialized.

terraform init

Plan

Run terraform in plan mode to visualize the changes to be made

terraform plan -out plan

Apply

Once the plan is approved you can apply it.

terraform apply plan

If you need to cancel, control+c twice can leave dirty VM's, let things play out. I tried it to see what would happen while it was creating a vm, and it did still make the vm, but then terraform was unable to destroy it and I had to manually do that. Just be careful here.

Destroy

If you're done with your VM and ready to destroy it

terraform destroy 

Further Reading

Next Chapter