Skip to content

Terraform Proxmox and Cloud-Init

  • proxmox
  • vm's
  • vm
  • virtualization
  • terraform
  • cloud-init

References:

Terraform with Proxmox

Intro to Cloud-Init

Summary

The final steps in putting everything together I can create the cloud init, and use terraform to send it to the newly created vm. First, setup the ssh keys, set up the cloud-init files, edit the main.tf to use the new things.

SSH Keys

I used my Cloud-Init template Terraform test C&C to generate some ssh keys. As the user I ran

ssh-keygen
From there I ran
ssh-copy-id root@pve.centerionware.com
Root probably isn't the best idea, it should be the terraform user - assuming the terraform user is a system user and not a proxmox only user. can be fixed by moving the key on the pve host into the terraform users /home/terraform_user/.ssh/authorized_keys file and out of /root/.ssh/authorized_keys .

Now that ssh is setup though, the ssh keys can be used to upload the files to hypervisor, and now can fully remotely through scripts define a vm and configure it.

Final Directory Layout

  • /home/user/
  • /home/user/cloud-init/networking.yml
  • /home/user/cloud-init/user.yml
  • /home/user/cloud-init/vendor.yml
  • /home/user/.ssh/id_rsa
  • /home/user/.ssh/id_rsa.pub
  • /home/user/main.tf
  • /home/user/variables.tf
  • /home/user/provider.tf
  • /home/user/output.tf

Directory Layout Note

The reason for the change from just main.tf and vars.tf is because of Gitlab. It expects four files, most of which was already there in the old main.tf.

Final Example

output.tf

(I couldn't get it to work so is commented out)

output "public_ip" {
  value       = proxmox_vm_qemu.proxmox_vm[*].ipconfig0
  description = "Public IP Address of EC2 instance"
}
output "instance_id" {
  value       = proxmox_vm_qemu.proxmox_vm[*].id
  description = "Instance ID"
}

variables.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 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
}
#Blank var for use by terraform.tfvars
variable "token_id" {
    default = "Terraform_Example_API@pve!terraform_api_token"
}

provider.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
}

main.tf

resource "local_file" "networking_yml" {
  content   = file("${path.module}/cloud-init/networking.yml")
  filename  = "${path.module}/local/cloud-init/networking.yml"
}

# Transfer the file to the Proxmox Host
resource "null_resource" "networking_yml_resource" {
  connection {
    type    = "ssh"
    user    = "root"
    private_key = file("${path.module}/.ssh/id_rsa")
    host    = "pve.centerionware.com"
  }

  provisioner "file" {
    source       = local_file.networking_yml.filename
    destination  = "/etc/pve/snippets/snippets/networking.yml"
  }
}

resource "local_file" "user_yml" {
  content   = file("${path.module}/cloud-init/user.yml")
  filename  = "${path.module}/local/cloud-init/user.yml"
}

# Transfer the file to the Proxmox Host
resource "null_resource" "user_yml_resource" {
  connection {
    type    = "ssh"
    user    = "root"
    private_key = file("${path.module}/.ssh/id_rsa")
    host    = "pve.centerionware.com"
  }

  provisioner "file" {
    source       = local_file.user_yml.filename
    destination  = "/etc/pve/snippets/snippets/user.yml"
  }
}

resource "local_file" "vendor_yml" {
  content   = file("${path.module}/cloud-init/vendor.yml")
  filename  = "${path.module}/local/cloud-init/vendor.yml"
}

# Transfer the file to the Proxmox Host
resource "null_resource" "vendor_yml_resource" {
  connection {
    type    = "ssh"
    user    = "root"
    private_key = file("${path.module}/.ssh/id_rsa")
    host    = "pve.centerionware.com"
  }

  provisioner "file" {
    source       = local_file.vendor_yml.filename
    destination  = "/etc/pve/snippets/snippets/vendor.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/user.yml,network=cloud-inits:snippets/networking.yml,vendor=cloud-inits:snippets/vendor.yml"

}