# Terraform + Proxmox

Cette doc permet le déploiement automatisé de machines virtuelles (VM) sur un cluster Proxmox VE à l’aide de Terraform et du provider [bpg/proxmox](https://registry.terraform.io/providers/bpg/proxmox/latest). \
Bonus : [Petite configuration Ansible](/tutoriels/infra-as-code/ansible.md)

### Prérequis <a href="#prrequis" id="prrequis"></a>

* Un serveur **Proxmox VE** en version 8.x
* Une machine Linux avec **Terraform** (>= 1.5) ou **OpenTofu** (>= 1.6) installé
* Un utilisateur dédié sur Proxmox avec un **API Token** configuré
* Un templaye de vm
* Les accès SSH configurés si nécessaire pour la provision des VM

### Création du rôle et de l'utilisateur Proxmox pour Terraform (via CLI) <a href="#cration-du-rle-et-de-lutilisateur-proxmox-pour-ter" id="cration-du-rle-et-de-lutilisateur-proxmox-pour-ter"></a>

Docs officielle : <https://registry.terraform.io/providers/bpg/proxmox/latest/docs#api-token-authentication>

#### **Créer un rôle Terraform avec les droits nécessaires**

```bash
pveum role add TerraformProv -privs "Datastore.AllocateSpace Datastore.AllocateTemplate 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.Migrate VM.Monitor VM.PowerMgmt SDN.Use"
```

Ce rôle regroupe toutes les permissions recommandées pour piloter des VM, disques, réseaux, cloud-init, migration, etc.

#### **Créer l'utilisateur Terraform**

```bash
pveum user add terraform-prov@pve --password <votre_mot_de_passe>
```

Remplacer `<votre_mot_de_passe>` par un mot de passe fort.

#### **Associer le rôle à l'utilisateur sur tout le cluster**

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

{% hint style="success" %}
Le `/` cible l’ensemble du cluster, ajustez si besoin pour limiter à un nœud ou un pool spécifique.
{% endhint %}

**Créer un token API pour l’utilisateur**

```bash
pveum user token add terraform-prov@pve terraform -expire 0 -privsep 0 -comment "Terraform token"
```

{% hint style="success" %}

* `-expire 0` : pas d’expiration automatique
* `-privsep 0` : désactive la séparation de privilèges, nécessaire pour Terraform
* Notez immédiatement le secret du token affiché : il ne sera plus visible ensuite.
  {% endhint %}

#### Génération du Token API

Créer un token API pour l’utilisateur (ici nommé `terraform`) et accorder les roles necessaire au token :

```bash
pveum user token add terraform-prov@pve terraform -comment "Token Terraform"
#pveum aclmod / --roles TerraformProv --token 'terraform@pve!terraform' --propagate 1
```

{% hint style="danger" %}

* Notez bien le **token secret** affiché : il ne sera plus visible ensuite.
  {% endhint %}

### Configuration des variables d’environnement

Ajouter ces variables à votre shell avant d’utiliser Terraform  : &#x20;

{% hint style="warning" %}
A faire apres chaque fermerture de la session
{% endhint %}

```bash
export PROXMOX_VE_ENDPOINT="https://proxmox.lab.prod:8006/"
export PROXMOX_VE_INSECURE=true
export PROXMOX_VE_USERNAME="terraform-uer-proxmox@pve"
export PROXMOX_VE_PASSWORD="user-proxmox-x"
export PROXMOX_VE_API_TOKEN="API_Token"
```

### **Création du template cloudinit:**

* ID : 9000
* nom de la VM : debian-cloudinit
* RAM : 2Go
* Interface réseau : vmbr1

#### **✅1. Télécharger une ISO Debian Cloud-Init Ready**

Utiliser une ISO minimale, par exemple via `netinst`, ou directement une image cloud-init-ready comme :

```bash
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
```

#### **✅ 2. Créer une VM dans Proxmox (sans ISO) en CLI**

```bash
qm create 9000 --name debian-cloudinit --memory 2048 --net0 virtio,bridge=vmbr1
```

#### **✅ 3. Importer l’image disque téléchargée**

```bash
qm importdisk 9000 debian-12-genericcloud-amd64.qcow2 local
```

Où ***local*** représente le nom du Datastore. Ensuite, attacher le disque au contrôleur :

<pre class="language-bash"><code class="lang-bash"><strong>qm set 9000 --scsihw virtio-scsi-pci --scsi0 local:9000/vm-9000-disk-0.raw
</strong></code></pre>

#### **✅ 4. Ajouter le disque Cloud-Init**

{% hint style="info" %}
Sur Proxmox, le disque Cloud-Init est un volume virtuel ajouté à une machine virtuelle pour automatiser sa configuration lors du premier démarrage. Ce disque contient les paramètres essentiels définis par l’administrateur, tels que le nom d’hôte, la configuration réseau, les utilisateurs, les mots de passe ou encore les clés SSH. Lors du boot initial, l’agent Cloud-Init présent dans l’image de la VM détecte ce disque et applique automatiquement toutes les configurations fournies, ce qui permet de déployer rapidement et de façon standardisée des machines virtuelles personnalisées sans intervention manuelle répétitive. Cette méthode est particulièrement utile pour le clonage de templates, le déploiement en masse ou l’intégration à des infrastructures cloud, car elle garantit que chaque VM démarre avec les bons paramètres dès la première utilisation.&#x20;
{% endhint %}

```bash
qm set 9000 --ide2 local:cloudinit
```

#### **✅ 5. Configurer le boot & autres options**

```bash
qm set 9000 --boot c --bootdisk scsi0
qm set 9000 --serial0 socket --vga serial0
```

#### **✅ 6. Convertir en template**

<pre class="language-bash"><code class="lang-bash"><strong>qm template 9000
</strong></code></pre>

#### 🔁 Ensuite dans Terraform

On pourra directement l’utiliser comme `vm_id = 9000` dans ta config `clone`.

#### 🧼 Bonus : nettoyage au besoin

```bash
rm debian-12-genericcloud-amd64.qcow2
```

### Création d'une clé ssh&#x20;

Cette clé ssh sera déployée sur les vms qu'on créera

```bash
ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa
```

Cela génèrera deux fichiers :

* `id_rsa` → Clé **privée** (à garder secrète).
* `id_rsa.pub` → Clé **publique** (à injecter dans les VMs).

**Place la clé publique dans le dossier Terraform** :\
Le fichier `id_rsa.pub` doit se trouver dans le dossier contenant ton projet Terraform.

### Création des fichiers de configs terraform

proxmox-bpg-terraform/\
├── README.md\
├── main.tf\
├── data.tf\
├── variables.tf\
├── outputs.tf\
├── vm.tf\
Tout ça est disponible sur mon [repo Github](https://github.com/imadyr1gk/proxmox-bpg-terraform)

#### main.tf

{% hint style="success" %}
Ce fichier initialise le projet Terraform et définit le provider principal utilisé :\
bpg/proxmox (version 0.66.2).\
Cette configuration est nécessaire pour permettre à Terraform d’interagir avec l’API Proxmox.
{% endhint %}

```hcl
terraform {
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = "0.66.2"
    }
  }
}
```

#### data.tf

{% hint style="success" %}
Ce fichier permet à terrafom de récupérer une clé publique en data source et de lire le contenu du fichier `id_rsa.pub` et de l’utiliser dans la configuration via `data.local_file.ssh_public_key.content`\
C’est la méthode recommandée pour importer une clé publique locale dans Terraform
{% endhint %}

```hcl
data "local_file" "ssh_public_key" {
  filename = "${path.module}/id_rsa.pub"
}
```

* `local_file` est un **data source** qui lit le contenu d’un fichier local.
* `trimspace(...)` est utilisé ensuite dans le `vm.tf` pour s'assurer qu'aucun saut de ligne inutile ne soit injecté dans le cloud-init.

{% hint style="warning" %}
Vous pouvez remplacer ${path.module} par le chemin où se trouve la clé. Moi je l'ai dans le répertoire de mon projet. donc mon data.tf devient : &#x20;

```hcl
data "local_file" "ssh_public_key" {
filename = ".id_rsa_pub_deploy"
}
```

{% endhint %}

#### variables.tf

{% hint style="success" %}
Ce fichier définit l’ensemble des variables d’entrée utilisées dans le projet Terraform\
pour la création de machines virtuelles sur Proxmox.\
Modifiez ces variables selon votre environnement et vos besoins.
{% endhint %}

```hcl
variable "node_name" {
  description = "proxmox "
  default     = "prox"
}

variable "template_debian_id" {
  description = " template debian cloudinit"
  default     = "9000"
}

variable "nb_vms" {
  description = "nombre de vms à créer"
}

variable "vm_names" {
  description = "nom des vm"
  type        = list(string)
  default     = ["vm1", "vm2", "vm3"]
}
```

{% hint style="success" %}
Le bloc suivant :

```hcl
variable "vm_names" {
  description = "nom des vm"
  type        = list(string)
  default     = ["vm1", "vm2", "vm3"]
}
```

définit une variable appelée `vm_names`, qui est une **liste de chaînes de caractères** (donc un tableau de noms). Chaque élément de cette liste représente le **nom d'une VM à créer avec Terraform**.

Ce format est **essentiel** car il permet d'utiliser `for_each` dans le fichier [vm.tf](#vm.tf), ce qui offre un **meilleur contrôle** sur chaque ressource individuellement (contrairement à `count`, qui fonctionne avec des index numériques sans lien direct avec le nom).

Par exemple :

```hcl
for_each = toset(var.vm_names)
name     = each.key
```

Cela permet de créer une VM pour **chaque nom fourni dans la liste**, avec des paramètres propres (nom, IP, ID, etc.).
{% endhint %}

{% hint style="danger" %}
⚠️ **Important** : cette liste reflète l’état réel des VMs gérées par Terraform.\
Si on supprime un nom de la liste, Terraform considérera que la VM correspondante doit être détruite.\
Donc, pour **ajouter de nouvelles VMs sans toucher aux existantes**, il faut **ajouter les nouveaux noms à la liste sans retirer les anciens**
{% endhint %}

#### outputs.tf

{% hint style="success" %}
Ce fichier définit les sorties (outputs) de Terraform pour ce projet.\
Ici, on expose les adresses IPv4 de toutes les VMs créées, ce qui permet de les retrouver facilement après un apply.
{% endhint %}

```hcl
output "vms_ipv4" {
  description = "IPv4 des VMs créées"
  value = {
    for vm_name, vm in proxmox_virtual_environment_vm.vm :
    vm_name => vm.ipv4_addresses
  }
}
```

#### vm.tf

{% hint style="success" %}
Ce fichier définit la ressource principale de machine virtuelle (VM) à déployer sur Proxmox via Terraform.\
Il s’appuie sur les variables du projet et la clé publique SSH importée en data source.
{% endhint %}

Dans Terraform, le mot de passe fourni à `user_account` doit être **hashé** .

* Génération du mot de passe chiffré

```bash
sudo apt install openssl
sudo dnf install openssl # pour le gestionnaire de paquet dnf
sudo yum install openssl # pour le gestionnaire de paquet yum
openssl passwd -6 le-mot-de-passe-à-chiffré
```

* Exemple de résultat

```bash
imad@:/mnt/c/Users/imad$ openssl passwd -6 password
$6$tHVVDVCWxyr0H0kf$AXm7J6O/nGKMpKK1qzx/uMMRKw7IawWPHWbhu0lYIkql/I/chCaX9nAdisUJ65tuY1RBb4WqqqNMErQiE725n0
```

Le mot de passe haché est à utiliser dans :&#x20;

```hcl
user_account {
  username = "imad"
  password = "<mot_de_passe_hashé>"
  keys     = [trimspace(data.local_file.ssh_public_key.content)]
}
```

{% hint style="info" %}
Avec `keys     = [trimspace(data.local_file.ssh_public_key.content)]` qui représente la clé public récupéré en data source dans [data.tf](#data.tf) et `username` le nom d'utilisateur des vms qui seront déployés
{% endhint %}

Le fichier vm.tf devrait ressembler à ca

```hcl
resource "proxmox_virtual_environment_vm" "vm" {
  for_each  = toset(var.vm_names)
  name      = each.key
  node_name = var.node_name
  vm_id     = 900 + index(var.vm_names, each.key)
  clone {
    vm_id = 9000
    full  = true
  }
  initialization {
    datastore_id = "local"

    user_account {
      username = "imad"
      password = "mot-de-passe-haché"
      keys     = [trimspace(data.local_file.ssh_public_key.content)]
    }
    dns {
      domain  = "local"
      servers = ["1.1.1.1"]
    }
    ip_config {
      ipv4 {
        address = "10.10.10.${50 + index(var.vm_names, each.key)}/24"
        gateway = "10.10.10.1"
      }
    }
  }
  lifecycle {
    ignore_changes = [
      # ignore toute mise à jour sur ces attributs pour les VMs existantes
      initialization[0].ip_config[0].ipv4[0].address,
      initialization[0].ip_config[0].ipv4[0].gateway,
      network_device,
      disk,
      memory,
      cpu
    ]
  }
  #  prevent_destroy = true
}
```

{% hint style="success" %}
Pourquoi `for_each` ?

Contrairement à `count`, `for_each` permet de :

* Nommer chaque ressource individuellement (via une clé explicite),
* Ajouter/retirer des ressources **sans destruction** des autres,
* Gérer une configuration plus flexible.

#### Exemple :

```hcl
variable "vm_names" {
  default = ["vm1", "vm2", "vm3"]
}

resource "proxmox_virtual_environment_vm" "vm" {
  for_each  = toset(var.vm_names)
  name      = each.key
```

**Ce que ça fait** :

* Crée une VM nommée `vm1`, une autre `vm2`, etc.
* Chaque VM est traitée indépendamment.

***

### &#x20;Génération automatique des `vm_id` et adresses IP

#### 🔢 vm\_id unique

```hcl
vm_id = 900 + index(var.vm_names, each.key)
```

→ Cela génère :

* `vm1` → `900`
* `vm2` → `901`
* `vm3` → `902`

#### 🌐 IPs fixes

```hcl
address = "10.10.10.${50 + index(var.vm_names, each.key)}/24"
```

→ IPs attribuées :

* `bastion` → `10.10.10.50`
* `kube-master` → `10.10.10.51`
* `kube-worker` → `10.10.10.52`

***

#### 🔄 Utilisation de `lifecycle.ignore_changes`

```hcl
lifecycle {
  ignore_changes = [
    initialization[0].ip_config[0].ipv4[0].address,
    initialization[0].ip_config[0].ipv4[0].gateway,
    network_device,
    memory,
    cpu,
    disk
  ]
}
```

Pouvoir modifier manuellement ces paramètres (RAM, CPU, IP, etc.) via l’interface Proxmox sans que Terraform essaie de les remettre à leur état initial à chaque `apply`.

#### Prevent destroy

Le champ `prevent_destroy` dans Terraform est une **protection** qui empêche **la suppression accidentelle** d'une ressource, même si elle n’existe plus dans le code ou si tu fais un `terraform destroy`.

```hcl
 prevent_destroy = true
```

{% endhint %}

### Déploiement

* `terraform init` → Prépare l’environnement
* `terraform fmt` → Formate le code
* `terraform validate` → Vérifie la validité de la configuration
* `terraform plan` → Prévisualise les changements
* `terraform apply` → Applique les changements sur l’infrastructure

Pour ajouter de nouvelles VMs à défaut de modifier le fichier variables.tf :

```bash
terraform apply -var='vm_names=["vm1", "vm2", "vm3", "dns"]'
```

Une  fois les Vms créées on peut leur appliquer des configs spécifiques avec ansible. [ICI](/tutoriels/infra-as-code/ansible.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wiki-tech.fikara.io/tutoriels/infra-as-code/terraform-+-proxmox.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
