Aller au contenu principal

Provisionner des VM avec Terraform

En tant que consultant DevOps, j'ai besoin de disposer d'outils puissants et flexibles pour configurer des environnements de veille technologique. Pour cela, j'utilise Terraform, un outil d'automatisation d'infrastructure as code, qui se distingue par sa capacité à gérer le cycle de vie complet des instances : de la création jusqu'à la destruction.

Pré-requis

Avant de plonger dans l'intégration spécifique de Terraform avec Proxmox, il est essentiel de posséder une compréhension solide de la création de configurations Terraform. Terraform utilise un langage de configuration déclaratif pour définir l'infrastructure souhaitée. Cela implique la connaissance des bases de Terraform, y compris la syntaxe, les modules, les providers et les états.

Si vous êtes nouveau dans l'univers de Terraform ou souhaitez rafraîchir vos connaissances, je vous recommande vivement de consulter le guide d'introduction suivant : Introduction à Terraform. Ce guide couvre les concepts fondamentaux et fournit une base solide pour comprendre comment Terraform peut être utilisé pour automatiser la gestion de l'infrastructure.

Configuration de proxmox

info

Attention : pour réaliser ce tutoriel, j'ai installé Proxmox sur une VM virtualisé avec Vagrant. J'ai détaillée cette procédure ici. Certains paramètres devront être modifiés.

L'adoption du principe de moindre privilège est essentielle pour renforcer la sécurité de votre infrastructure. Cela implique de créer un utilisateur et un rôle spécifique dans Proxmox, qui sera utilisé par Terraform. Cette approche garantit que Terraform n'aura que les permissions nécessaires pour effectuer ses tâches, sans accès excessif pouvant compromettre la sécurité du système. Voici comment procéder en ligne de commande :

Création d'un Rôle

L'adoption du principe de moindre privilège est essentielle pour renforcer la sécurité de votre homelab. Cela implique de créer un utilisateur et un rôle spécifique dans Proxmox, qui sera utilisé par Terraform. Cette approche garantit que Terraform n'aura que les permissions nécessaires pour effectuer ses tâches, sans accès excessif pouvant compromettre la sécurité du système. Voici comment procéder en ligne de commande :

Connectez-vous à votre serveur Proxmox via SSH ou accédez à son terminal si vous y avez un accès physique.

Proxmox permet de créer des rôles personnalisés avec des permissions spécifiques. Utilisez la commande suivante, en vous connectant en ssh auparavant, pour créer un nouveau rôle (par exemple, TerraformRole) avec des permissions adaptées à Terraform :

pveum role add TerraformProv -privs "Datastore.Allocate Datastore.AllocateSpace Datastore.Audit Pool.Allocate Sys.Audit Sys.Console Sys.Modify VM.Allocate VM.Audit VM.Clone VM.Config.CDROM VM.Config.Cloudinit VM.Config.CPU VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Console VM.Migrate VM.Monitor VM.PowerMgmt SDN.Use"

Cette commande crée un rôle TerraformRole avec des privilèges adaptés pour gérer les machines virtuelles.

Création d'un Utilisateur

Créez un utilisateur spécifique pour Terraform. Par exemple, pour créer un utilisateur terraform-prov dans le domaine @pve avec un mot de passe, utilisez :

pveum user add terraform-prov@pve --password <password>

Après avoir créé l'utilisateur, attribuez-lui le rôle créé précédemment. Vous pouvez le faire pour l'ensemble du datacenter :

pveum aclmod / -user terraform-prov@pve -role TerraformProv

Cette commande assigne le rôle TerraformRole à terraform-prov@pve pour toutes les ressources du datacenter.

Test de Connexion

Testez la connexion avec le nouvel utilisateur pour vous assurer que tout fonctionne comme prévu. Vous pouvez le faire en vous connectant à l'interface web de Proxmox avec les identifiants du nouvel utilisateur ou en testant une action simple via Terraform.

Génération d'un token

Pour une intégration sécurisée et efficace entre Terraform et Proxmox, l'utilisation d'un token API est recommandée. Cela permet à Terraform de se connecter et d'interagir avec Proxmox sans nécessiter des informations d'identification utilisateur classiques. Voici la procédure pour créer un token API dans Proxmox et le configurer dans Terraform.

pveum user token add terraform-prov@pve terraform -expire 0 -privsep 0 -comment "Terraform token"
┌──────────────┬──────────────────────────────────────────────────────────┐
│ key          │ value                                                    │
╞══════════════╪══════════════════════════════════════════════════════════╡
│ full-tokenid │ terraform-prov@pve!terraform                             │
├──────────────┼──────────────────────────────────────────────────────────┤
│ info         │ {"comment":"Terraform token","expire":"0","privsep":"0"} │
├──────────────┼──────────────────────────────────────────────────────────┤
│ value        │ 85b12793-73c5-4d72-87b8-54d8dbba6d62                     │
└──────────────┴──────────────────────────────────────────────────────────┘

Création du template

cd /var/lib/vz/template/iso
wget https://cloud-images.ubuntu.com/releases/20.04/release/ubuntu-20.04-server-cloudimg-amd64.img
sudo virt-customize -a ubuntu-20.04-server-cloudimg-amd64.img --install qemu-guest-agent
sudo virt-customize -a ubuntu-20.04-server-cloudimg-amd64.img --root-password password:stephane
sudo qm create 9999 --name "ubuntu.robert.local" --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
sudo sudo qm importdisk 9999 ubuntu-20.04-server-cloudimg-amd64.img local
qm set 9999 --tags "template,test"
sudo qm set 9999 --scsihw virtio-scsi-pci --scsi0 local:9999/vm-9999-disk-0.raw
sudo qm set 9999 --boot c --bootdisk scsi0
sudo qm set 9999 --ide2 local:cloudinit
sudo qm set 9999 --serial0 socket --vga serial0
sudo qm set 9999 --agent enabled=1
sudo qm set 9999 --ipconfig0 ip=dhcp
sudo qm template 9999

Configuration SSH

Le provider Proxmox BPG demande à ce que l'on puisse se connecter au cluster proxmox en SSH avec l'agent.

eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519
ssh root@proxmox2.robert.local

Ecriture du Code Terrafom

Bon, coté configuration tout est prêt passons au code.

Configuration du Provider Proxmox

Le cœur de l'intégration entre Terraform et Proxmox réside dans la configuration du provider Proxmox. Ce provider permet à Terraform de communiquer et de gérer les ressources sur votre serveur Proxmox.

Commencez par créer un fichier nommé main.tf. Dans ce fichier, ajoutez les deux blocs de configuration pour le provider Proxmox BPG. Voici un exemple :

terraform {
  required_providers {
    proxmox = {
      source = "bpg/proxmox"
      version = "0.42.0"
    }
  }
}

provider "proxmox" {
  endpoint = "https://proxmox.robert.local:8006/"
  api_token = var.api_token
  insecure = false
  ssh {
    agent    = true
    username = "root"
  }
}

Créez un fichier variables.tf :

variable "api_token" {
  description = "Token to connect Proxmox API"
  type = string
}

Pour éviter de mettre les informations de connexion dans un fichier, nous allons définir une variable d'environnement :

export TF_VAR_api_token="terraform-prov@pve\!terraform=159ae6b8-8d5e-4be0-af1f-7490b70989e"

Ouvrez un terminal dans le répertoire contenant votre fichier main.tf et exécutez terraform init. Cette commande initialise le projet Terraform et télécharge le provider Proxmox.

Validation de la Configuration

Une fois le provider configuré, il est important de valider que Terraform peut communiquer correctement avec votre serveur Proxmox :

Utilisez la commande terraform plan pour planifier une action simple. Cette étape ne modifiera rien, mais vous permettra de vérifier que Terraform peut interagir avec Proxmox sans erreurs.

Examinez les sorties de la commande. Si tout est configuré correctement, Terraform devrait pouvoir se connecter à Proxmox et afficher le plan d'exécution sans erreurs.

Vous êtes maintenant prêt à utiliser Terraform pour automatiser la gestion de votre infrastructure Proxmox.

Création de ressources Proxmox

Après avoir configuré Terraform et Proxmox pour une intégration sécurisée, le prochain pas consiste à automatiser la création et la gestion de machines virtuelles (VMs). Terraform permet de définir des VMs dans Proxmox de manière déclarative, rendant ce processus à la fois fiable et reproductible.

Nous allons utiliser le template que nous avons créé précédement.

Définition d'une Ressource VM dans Terraform

Dans votre fichier main.tf ajoutez ces lignes



data "proxmox_virtual_environment_vms" "template" {
  node_name = var.target_node
  tags      = ["template", var.template_tag]
}


resource "proxmox_virtual_environment_file" "cloud_user_config" {
  content_type = "snippets"
  datastore_id = "local"
  node_name    = var.target_node

  source_raw {
    data = file("cloud-init/user_data")

    file_name = "${var.vm_hostname}.${var.domain}-ci-user.yml"
  }
}

resource "proxmox_virtual_environment_file" "cloud_meta_config" {
  content_type = "snippets"
  datastore_id = "local"
  node_name    = var.target_node

  source_raw {
    data = templatefile("cloud-init/meta_data",
      {
        instance_id    = sha1(var.vm_hostname)
        local_hostname = var.vm_hostname
      }
    )

    file_name = "${var.vm_hostname}.${var.domain}-ci-meta_data.yml"
  }
}

resource "proxmox_virtual_environment_vm" "vm" {
  name      = "${var.vm_hostname}.${var.domain}"
  node_name = var.target_node

  on_boot = var.onboot


  agent {
    enabled = true
  }

  tags = var.vm_tags

  cpu {
    type    = "x86-64-v2-AES"
    cores   = var.cores
    sockets = var.sockets
    flags   = []
  }

  memory {
    dedicated = var.memory
  }

  network_device {
    bridge  = "vmbr0"
    model   = "virtio"
  }

  # Ignore changes to the network
  ## MAC address is generated on every apply, causing
  ## TF to think this needs to be rebuilt on every apply
  lifecycle {
    ignore_changes = [
      network_device,
    ]
  }

  boot_order    = ["scsi0"]
  scsi_hardware = "virtio-scsi-single"

  disk {
    interface    = "scsi0"
    iothread     = true
    datastore_id = "${var.disk.storage}"
    size         = var.disk.size
    discard      = "ignore"
  }

  dynamic "disk" {
    for_each = var.additionnal_disks
    content {
      interface    = "scsi${1 + disk.key}"
      iothread     = true
      datastore_id = "${disk.value.storage}"
      size         = disk.value.size
      discard      = "ignore"
      file_format  = "raw"
    }
  }

  clone {
    vm_id = data.proxmox_virtual_environment_vms.template.vms[0].vm_id
  }

  initialization {
    # ip_config {
    #   ipv4 {
    #     address = "dhcp"
    #   }
    # }

    datastore_id         = "local"
    interface            = "ide2"
    user_data_file_id    = proxmox_virtual_environment_file.cloud_user_config.id
    meta_data_file_id    = proxmox_virtual_environment_file.cloud_meta_config.id
  }


}

Quelques explications :

La première section définit une source de données Terraform (data) pour rechercher une VM dans Proxmox qui correspond à certaines balises (tags). Cette VM sera utilisée comme template pour cloner de nouvelles VMs.

data "proxmox_virtual_environment_vms" "template" {
  node_name = var.target_node
  tags      = ["template", var.template_tag]
}

Les deux ressources suivantes proxmox_virtual_environment_file créent des fichiers de configuration Cloud-Init dans Proxmox, qui seront utilisés pour configurer la VM lors de son premier démarrage.

resource "proxmox_virtual_environment_file" "cloud_user_config" {
  ...
}

resource "proxmox_virtual_environment_file" "cloud_meta_config" {
  ...
}

La dernière ressource définit la VM à créer :

resource "proxmox_virtual_environment_vm" "vm" {
  ...
}

Dans votre fichier variables.tf ajoutez ceci :

variable "api_token" {
  description = "Token to connect Proxmox API"
  type = string
}


variable "target_node" {
  description = "Proxmox node"
  type        = string
  default = "devbox1"
}

variable "onboot" {
  description = "Auto start VM when node is start"
  type        = bool
  default     = true
}

variable "target_node_domain" {
  description = "Proxmox node domain"
  type        = string
  default = ""
}

variable "vm_hostname" {
  description = "VM hostname"
  type        = string
  default = "test"
}

variable "domain" {
  description = "VM domain"
  type        = string
  default = "robert.local"
}

variable "vm_tags" {
  description = "VM tags"
  type        = list(string)
  default = [ "ubuntu" ]
}

variable "template_tag" {
  description = "Template tag"
  type        = string
  default = "test"
}

variable "sockets" {
  description = "Number of sockets"
  type        = number
  default     = 1
}

variable "cores" {
  description = "Number of cores"
  type        = number
  default     = 1
}

variable "memory" {
  description = "Number of memory in MB"
  type        = number
  default     = 2048
}

variable "vm_user" {
  description = "User"
  type        = string
  sensitive   = true
  default = "sysadmin"
}

variable "disk" {
  description = "Disk (size in Gb)"
  type = object({
    storage = string
    size    = number
  })
  default = {
    storage = "local"
    size = 10
  }
}

variable "additionnal_disks" {
  description = "Additionnal disks"
  type = list(object({
    storage = string
    size    = number
  }))
  default = []
}

Il faut aussi créer les fichiers de templates. Créez un dossier cloud-init dans lequel vous placez ces deux fichiers :

  • user_data :

    #cloud-config
    hostname: ${hostname}
    local-hostname: ${hostname}
    fqdn: ${hostname}.${domain}
    manage_etc_hosts: true
    package_upgrade: true
    users:
      - default
      - name: ubuntu
        sudo: ALL=(ALL) NOPASSWD:ALL
        ssh_authorized_keys:
          - ssh-ed25519 AAAAB3NzaC1yc2...
    ssh_pwauth: True ## This line enables ssh password authentication
    
  • meta-data : vide

Validation de la Configuration

Comme auparavant, utilisez la commande terraform plan pour vérifier que le code source ne contient pas d'erreurs.

Création de la VM

Dans votre terminal, exécutez terraform plan pour voir les changements que Terraform s'apprête à effectuer. Cette étape vous permet de vérifier la configuration avant son application.

terraform apply

...

Plan: 4 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_virtual_environment_file.cloud_meta_config: Creating...
proxmox_virtual_environment_file.cloud_user_config: Creating...
proxmox_virtual_environment_file.cloud_user_config: Creation complete after 0s [id=local:snippets/test.robert.local-ci-user.yml]
proxmox_virtual_environment_file.cloud_meta_config: Creation complete after 0s [id=local:snippets/test.robert.local-ci-meta_data.yml]
proxmox_virtual_environment_vm.vm: Creating...
proxmox_virtual_environment_vm.vm: Still creating... [10s elapsed]
proxmox_virtual_environment_vm.vm: Creation complete after 10s [id=100]
╷
│ Warning: the VM startup task finished with a warning, task log:
│
│       | trying to acquire lock...
│       |  OK
│       | generating cloud-init ISO
│       | WARN: no efidisk configured! Using temporary efivars disk.
│       | TASK WARNINGS: 1
│
│   with proxmox_virtual_environment_vm.vm,
│   on main.tf line 72, in resource "proxmox_virtual_environment_vm" "vm":
│   72: resource "proxmox_virtual_environment_vm" "vm" {
│
╵

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

Malgré le message d'erreur la VM fonctionne. On peut se connecter avec l'adresse IP fournit par l'agent :

ssh ubuntu@192.168.132

...

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

-bash: warning: setlocale: LC_ALL: cannot change locale (fr_FR.UTF-8)
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@test:~$

On fait le ménage

Rien de plus simple :

terraform destroy -auto-approve                                   06:50:25

data.proxmox_virtual_environment_vms.template: Reading...
proxmox_virtual_environment_file.cloud_user_config: Refreshing state... [id=local:snippets/test.robert.local-ci-user.yml]
proxmox_virtual_environment_file.cloud_meta_config: Refreshing state... [id=local:snippets/test.robert.local-ci-meta_data.yml]
data.proxmox_virtual_environment_vms.template: Read complete after 0s [id=116257b3-85cc-4ad7-b60f-61fa71699dab]
proxmox_virtual_environment_vm.vm: Refreshing state... [id=100]

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:

  # proxmox_virtual_environment_file.cloud_meta_config will be destroyed

Plan: 0 to add, 0 to change, 3 to destroy.
proxmox_virtual_environment_vm.vm: Destroying... [id=100]
proxmox_virtual_environment_vm.vm: Destruction complete after 7s
proxmox_virtual_environment_file.cloud_meta_config: Destroying... [id=local:snippets/test.robert.local-ci-meta_data.yml]
proxmox_virtual_environment_file.cloud_user_config: Destroying... [id=local:snippets/test.robert.local-ci-user.yml]
proxmox_virtual_environment_file.cloud_meta_config: Destruction complete after 0s
proxmox_virtual_environment_file.cloud_user_config: Destruction complete after 0s

Conclusion

À travers ce guide, nous avons exploré comment provisionner des VM avec Terraform sur un cluster Proxmox. Cela vous offre une flexibilité et une puissance considérables pour gérer des machines virtuelles et des conteneurs.

Cela n'a pas été si simple. En effet, il n'existe pas de provider officiel, mais deux maintenus par la communauté. Celui qui est le plus documenté n'est plus mis à jour.

Je tiens à remercier toutes les personnes qui m'ont aidé :

Nous pouvons passer à la suite avec l'utilisation de l'inventaire dynamique Ansible avec Proxmox. Ansible est un outil puissant pour l'automatisation de la configuration et la gestion de l'infrastructure et l'inventaire dynamique est un moyen efficace de gérer les ressources virtualisées dans notre cluster Proxmox de manière flexible et scalable.