Suite à un article portant sur l'utilisation de Terraform pour créer des LXC pour Proxmox, je souhaite approfondir le sujet en abordant cette fois la création de machines virtuelles.

Prérequis : Une connaissance préalable de Terraform, des droits d'administration sur Proxmox, et la capacité à effectuer des manipulations sans contraintes majeures. J'utilise également OpenTofu, compatible avec Terraform sans modification de code au moment de la rédaction de cet article.

Précédemment, je créais des modèles de machines virtuelles à l'aide de Packer. Cependant, être dépendant d'un outil de plus ne me convient plus et viennent alourdir le processus. Désormais, j'utilise les images "cloud" créées et proposées par les éditeurs comme Debian, Ubuntu ou encore Arch Linux, Fedora, etc. Ce type d'image a pour avantage la légèreté et l'intégrité. En contrepartie, vous devrez tester un peu plus vos créations, puisque les images ne comportent que très peu de paquets et de configuration pré-définies par défaut.

L'environnement de travail pour cet article est le suivant :

  • un serveur Proxmox 8.1.x
  • un éditeur de code
  • Terraform 1.9.x ou Open Tofu 1.9.x

Préparation de l'environnement de travail

Avant de commencer le déploiement automatisé avec Terraform ou OpenTofu, assurez-vous que votre environnement est correctement configuré en suivant ces étapes :

  1. Installation de Terraform ou OpenTofu :
    1. Dans un terminal, procédez à l'installation de Terraform en suivant les indications de la documentation officielle d'HashiCorp, adaptées à votre système d'exploitation. De même, pour OpenTofu, référez-vous à ce lien de documentation pour obtenir toutes les informations nécessaires. Assurez-vous que Terraform ou OpenTofu est correctement installé en vérifiant la version : $ terraform version.
  2. Accès à Proxmox :
    1. Assurez-vous d'avoir un accès à votre hôte Proxmox via un navigateur web.
    2. Vérifiez aussi l'accès via SSH pour des opérations en ligne de commande.
    3. Des identifiants pour Proxmox : disposez des identifiants de l'utilisateur "root" ou d'un autre utilisateur ayant des privilèges élevés sur Proxmox pour créer des machines virtuelles et gérer le stockage. Vous pouvez utiliser des "API Token" (lisez le début de cette page pour en savoir plus).

Avec ces prérequis en place, vous êtes prêt à automatiser le déploiement de machines virtuelles avec Terraform ou OpenTofu.

Préparation de Proxmox

Pour simplifier les opérations dans vos environnements de test et raccourcir ce document, vous pouvez utiliser le compte root@pam créé lors de l'installation de Proxmox. Ce compte possède tous les privilèges. Pour une gestion plus fine des droits et des accès, la création d'un utilisateur particulier et de son token est conseillée.

Le code Terraform complet

Enfin, voici le code complet que j'utilise pour créer des VM sur Proxmox avec Terraform ou OpenTofu. Le code est assez complet, j'aime bien avoir le maximum d'informations et d'attributs pour préciser au maximum mes ressources.

Fichier "variables.tf" :

### variables.tf

# The DNS domain used for cloud-init configuration.
variable "cloudinit_dns_domain" {
  type        = string
  default     = "home.arpa"
  description = "The DNS domain to be configured by cloud-init."
}

# A list of DNS servers used for cloud-init configuration.
variable "cloudinit_dns_servers" {
  type        = list(string)
  description = "A list of DNS server addresses for cloud-init configuration. Provide the IP addresses as a list of strings, e.g., [\"9.9.9.9\", \"149.112.112.112\"] (without backslashes)."
}

# SSH keys to be added to the authorized_keys file during cloud-init setup.
variable "cloudinit_ssh_keys" {
  type        = list(string)
  description = "SSH public keys to be included in the user's authorized_keys file."
}

# The username and password for the initial user account set up by cloud-init.
variable "cloudinit_user_account" {
  type        = string
  description = "Username for the initial user account created by cloud-init."
}

# ID of the datastore where the VM will be stored.
variable "vm_datastore_id" {
  type        = string
  description = "ID of the datastore to store the virtual machine disks."
}

# Format for the VM disk file (e.g., qcow2, vmdk).
variable "vm_disk_file_format" {
  type        = string
  description = "File format of the virtual machine's disk image."
}

# Name or identifier for the node.
variable "node_name" {
  type        = string
  description = "Name or identifier of the Proxmox node."
}

# API token for authenticating with the Proxmox Virtual Environment (PVE) host.
variable "pve_api_token" {
  type        = string
  description = "API token used to authenticate with the PVE server. Used too into a PVE cluster"
}

# IP address or hostname of the PVE host.
variable "pve_host_address" {
  type        = string
  description = "IP address or hostname of a PVE host."
}

variable "tags" {
  type        = list(string)
  description = "A list of tags to label the VM."
}

# Directory path for temporary files during VM setup.
variable "tmp_dir" {
  type        = string
  description = "Path to a directory used for storing temporary files like ISO uploads."
}

# Network bridge interface for connecting the VM's LAN.
variable "vm_bridge_lan" {
  type        = string
  description = "Network bridge name for the virtual machine's LAN connection."
}

# Number of CPU cores assigned to the VM.
variable "vm_cpu_cores_number" {
  type        = number
  description = "Number of CPU cores allocated to the virtual machine."
}

# Type of CPUs used in the VM (e.g., host, kvm).
variable "vm_cpu_type" {
  type        = string
  description = "Type of CPU model assigned to the virtual machine."
}

# Description for the VM.
variable "vm_description" {
  type        = string
  description = "Human-readable description or notes about the virtual machine. Markdown compatible."
}

# Size of the VM's disk in GB.
variable "vm_disk_size" {
  type        = number
  description = "Size of the disk for the virtual machine, specified in gigabytes. E.g. If you want a 2 Gb disk, type '1863'."
}

variable "vm_gateway_ipv4" {
  description = "The gateway IP address for the VM's network interface"
  type        = string
}

# Unique identifier for the VM instance (e.g., ID).
variable "vm_id" {
  type        = number
  description = "Unique numerical identifier for the virtual machine. Limited to 9 digits"
}

variable "vm_ipv4_address" {
  description = "The IPv4 address assigned to the VM, without its network mask."
  type        = string
}

# Maximum amount of memory allocated to the VM in MB.
variable "vm_memory_max" {
  type        = number
  description = "Maximum RAM allocation for the virtual machine, specified in megabytes. E.g. for 4 Gb, type '4096'."
}

# Minimum amount of memory allocated to the VM in MB.
variable "vm_memory_min" {
  type        = number
  description = "Minimum RAM allocation for the virtual machine, specified in megabytes. E.g. for 4 Gb, type '4096'."
}

# Name or label for identifying the VM.
variable "vm_name" {
  type        = string
  description = "Name used to identify the virtual machine."
}

# Order to start the VM
variable "vm_startup_order" {
  type        = string
  description = "A number to set the order of the VM starting."
}

# Number of CPU sockets allocated to the VM.
variable "vm_socket_number" {
  type        = number
  description = "Number of CPU sockets assigned to the virtual machine. Don't assign more virtual sockets than you have physically."
}

Fichier "variables.auto.tfvars", c'est ce fichier qu'il faut modifier pour que vous puissiez avoir un conteneur correspondant à votre besoin :

### variables.auto.tfvars
cloudinit_dns_domain   = "your.domain.net"
cloudinit_dns_servers  = ["9.9.9.9"]
cloudinit_ssh_keys     = ["ssh-ed25519 changeme"]
cloudinit_user_account = "jho"
node_name              = "pve"
pve_api_token          = "terrabot@pve!token_name=token_secret"
pve_host_address       = "https://pve:8006"
tags                   = ["linux", "opentofu"]
tmp_dir                = "/tmp"
vm_bridge_lan          = "vmbr0"
vm_cpu_cores_number    = 2
vm_cpu_type            = "x86-64-v2-AES"
vm_datastore_id        = "local"
vm_description         = "Managed by terraform."
vm_disk_file_format    = "raw"
vm_disk_size           = 64
vm_gateway_ipv4        = "172.16.0.254"
vm_id                  = 9993
vm_ipv4_address        = "172.16.241.10"
vm_memory_max          = 8192
vm_memory_min          = 4096
vm_name                = "tf_example"
vm_startup_order       = "1"
vm_socket_number       = 1

Fichier "vm.tf" :

J'utilise cloud-init pour faciliter la configuration de la machine au niveau réseau. À terme, cette configuration permettra aussi d'appliquer des configurations via des fichiers au format YAML.

### vm.tf
terraform {
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = "~> 0.70"
    }
    random = {
      source = "hashicorp/random"
      version = "3.6.3"
    }
  }
}

provider "proxmox" {
  endpoint  = var.pve_host_address
  api_token = var.pve_api_token
  insecure  = true
  ssh {
    agent    = true
    username = var.pve_api_user
  }
  tmp_dir = var.tmp_dir
}

# cloud-image Ubuntu 24 avec cloud-init
resource "proxmox_virtual_environment_download_file" "ubuntu24_cloudimg_20250117" {
  checksum           = "63f5e103195545a429aec2bf38330e28ab9c6d487e66b7c4b0060aa327983628"
  checksum_algorithm = "sha256"
  content_type       = "iso"
  datastore_id       = var.datastore_id
  file_name          = "ubuntu-24.04-server-20250117-cloudimg-amd64.img"
  node_name          = var.node_name
  upload_timeout     = 360
  url                = "https://cloud-images.ubuntu.com/noble/20250117/noble-server-cloudimg-amd64.img"
}

resource "proxmox_virtual_environment_vm" "vm1_example" {
  depends_on = [proxmox_virtual_environment_download_file.ubuntu24_cloudimg_20250117]

  description         = var.vm_description
  keyboard_layout     = "fr"
  machine             = "q35"
  migrate             = true
  name                = var.vm_name
  node_name           = var.node_name
  scsi_hardware       = "virtio-scsi-single"
  started             = true
  stop_on_destroy     = true # Force stop when destroying
  tablet_device       = false # VM without GUI
  tags                = var.tags
  timeout_create      = 180
  timeout_shutdown_vm = 30
  timeout_stop_vm     = 30
  vm_id               = var.vm_id

  agent {
    enabled = true
    timeout = "5m"
    trim    = true
  }

  cpu {
    cores   = var.vm_cpu_cores_number
    flags   = []
    numa    = true
    sockets = var.vm_socket_number
    type    = var.vm_cpu_type
  }

  disk {
    aio          = "native"
    cache        = "none"
    datastore_id = var.datastore_id
    discard      = "on"
    file_format  = var.disk_file_format
    file_id      = "${proxmox_virtual_environment_download_file.ubuntu24_cloudimg_20250117.datastore_id}:iso/${proxmox_virtual_environment_download_file.ubuntu24_cloudimg_20250117.file_name}"
    interface    = "scsi0"
    iothread     = true
    replicate    = false
    size         = var.vm_disk_size
  }

  efi_disk {
    datastore_id      = var.datastore_id
    pre_enrolled_keys = false
    type              = "4m"
  }

  initialization {
    datastore_id = var.datastore_id
    dns {
      domain  = var.cloudinit_dns_domain
      servers = var.cloudinit_dns_servers
    }
    ip_config {
      ipv4 {
        address = "${var.vm_ipv4_address}/16"
        gateway = var.vm_gateway_ipv4
      }
    }
    user_account {
      keys     = var.cloudinit_ssh_keys
      username = var.cloudinit_user_account
    }
  }

  memory {
    dedicated = var.vm_memory_max
    floating  = var.vm_memory_min
  }

  network_device {
    bridge = var.vm_bridge_lan
    model  = "virtio"
  }

  operating_system {
    type = "l26"
  }

  startup {
    order      = var.vm_startup_order
    up_delay   = 15
    down_delay = 30
  }

  serial_device {}

  tpm_state {
    datastore_id = var.datastore_id
    version      = "v2.0"
  }

  vga {
    type = "virtio"
  }
}

Dans ces fichiers, notamment le "variables.auto.tfvars", les valeurs sont à modifier et doivent correspondre à vos besoins. Relisez bien chaque fichier et adaptez-les selon votre infrastructure. J'ai maximisé l'utilisation de variables pour faciliter les ajustements.

Retenez aussi que la création d'une machine virtuelle par Terraform/OpenTofu peut être destructive lors de la mise à jour des paramètres des fichiers .tf. Si vous mettez à jour la ressource "proxmox_virtual_environment_download_file" "ubuntu24_cloudimg_20250117", la VM sera détruite et reconstruite avec le nouveau fichier. Pensez donc à sauvegarder au maximum vos données.

Share this post