## Introduction

Automatiser Proxmox avec le CI/CD signifie traiter les machines virtuelles comme du code : chaque VM est définie dans un dépôt versionné, chaque modification passe par une revue et des vérifications automatisées, et chaque déploiement laisse une trace d'audit. Ce guide s'adresse aux développeurs, ingénieurs DevOps et équipes techniques de startups qui souhaitent remplacer les clics manuels dans Proxmox par des pipelines reproductibles.

Vous apprendrez à :

- Inventorier votre environnement Proxmox et planifier l'automatisation en toute sécurité.

- Construire un pipeline CI/CD minimal mais prêt pour la production avec GitLab CI, Terraform et Ansible.

- Déployer et configurer une VM depuis zéro jusqu'à servir du trafic.

- Ajouter des contrôles de santé, des plans de retour arrière et des diagnostics de panne.

- Éviter les erreurs qui rendent l'automatisation Proxmox fragile et dangereuse.

Chaque exemple utilise des espaces réservés explicites ( <placeholder> ) et suppose Proxmox VE 8.x. Ne copiez jamais de véritables identifiants, jetons ou identifiants de production dans le contrôle de version.

L'objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, vérifier chaque étape et savoir comment récupérer avant d'en avoir besoin.

## Inventaire des versions et de l'environnement

Avant d'écrire la moindre ligne de code de pipeline, collectez les faits concernant votre installation Proxmox. Cela évite les incompatibilités de versions et les hypothèses erronées sur l'API.

### Ce qu'il faut capturer

Exécutez ces commandes en lecture seule sur un nœud Proxmox via SSH ou le shell web :

# Version de Proxmox
pveversion
# Sortie attendue : pve-manager/8.1.4/7d9c1b5e (running kernel: 6.5.11-7-pve)

# Statut du cluster (si en cluster)
pvecm status
# Sortie attendue : informations sur le quorum, liste des membres et chaque nœud marqué comme en ligne

# Stockage disponible pour les disques VM
pvesm status
# Sortie attendue : un tableau des identifiants de stockage, types et utilisation. Notez le type : dir, lvm, lvmthin, zfs, cephfs, nfs, etc.

# Ponts réseau
ip link show | grep -E '^[0-9]+: (vmbr|tap)'
# Sortie attendue : lignes montrant vmbr0, vmbr1, etc., avec état UP 
 Enregistrez ces informations dans un fichier environment.md dans votre dépôt d'automatisation :

# Instantané de l'environnement Proxmox
Date : 2025-02-20
Nœud : pve-node-1
Version PVE : 8.1.4
Cluster : oui (3 nœuds, quorum OK)
Stockage pour VM : local-lvm (lvmthin), ceph-pool (cephfs)
Ponts réseau : vmbr0 (gestion), vmbr1 (public VM)
Utilisateur d'automatisation : terraform@pve (jeton API uniquement) 

### Automatisation de l'inventaire

 Vous pouvez automatiser cet inventaire avec un script planifié. Exemple avec Ansible :

# playbooks/facts.yml
- name: Collecter les faits Proxmox
 hosts: pve_nodes
 gather_facts: false
 vars:
 ansible_user: root
 ansible_python_interpreter: /usr/bin/python3
 tasks:
 - name: Exécuter pveversion
 command: pveversion
 register: pve_version

 - name: Afficher la version
 debug:
 msg: "{{ pve_version.stdout }}" 
 Lancez-le avec ansible-playbook -i inventory/proxmox.yml playbooks/facts.yml . Planifiez-le chaque semaine ou dans la première étape de votre pipeline pour détecter les dérives.

### Pourquoi c'est important

Si vous écrivez du code Terraform pour la version 2 de l'API Proxmox et que votre serveur est encore en version 1, tout échouera. Si vous supposez que local-lvm est disponible mais que le nœud utilise ZFS, vos définitions de disques seront incorrectes. Inventoriez d'abord, automatisez ensuite.

## Chemin de configuration sécurisé

Les identifiants d'automatisation doivent suivre le principe du moindre privilège. Au lieu d'utiliser le mot de passe root, créez un utilisateur Proxmox dédié avec des autorisations de jeton API.

### Créer un jeton API restreint

Dans l'interface web Proxmox :

- Allez dans Datacenter > Permissions > Utilisateurs et cliquez sur Ajouter .

- Nom d'utilisateur : terraform@pve

- Domaine : Authentification PVE

- Mot de passe : générez-en un fort, stockez-le dans un gestionnaire de secrets.

- Allez dans Datacenter > Permissions > Jetons API et cliquez sur Ajouter .

- Utilisateur : terraform@pve

- Identifiant du jeton : ci-token

- Décochez Séparation des privilèges (afin que le jeton ait les autorisations de l'utilisateur).

- Notez l'identifiant complet du jeton : terraform@pve!ci-token et le secret (il n'est affiché qu'une seule fois).

- Allez dans Datacenter > Permissions > Ajouter > Permission de jeton API .

- Chemin : /

- Jeton : terraform@pve!ci-token

- Rôle : PVEVMAdmin (permet la gestion des VM mais pas les modifications au niveau de l'hôte).

Ce jeton peut créer, démarrer, arrêter et supprimer des VM, mais ne peut pas modifier les paramètres de stockage ou de réseau. Il convient au CI/CD.

### Stocker les secrets en toute sécurité

Ne mettez jamais le secret du jeton dans le fichier de pipeline. Dans GitLab CI, utilisez des variables masquées :

- PROXMOX_URL = https://pve-node-1.example.com:8006/api2/json

- PROXMOX_TOKEN_ID = terraform@pve!ci-token

- PROXMOX_TOKEN_SECRET = (la longue chaîne secrète, masquée)

Vous pouvez également utiliser HashiCorp Vault, AWS Secrets Manager ou les variables de GitLab CI. Le jeton doit être renouvelé tous les 90 jours, et le renouvellement doit faire partie de votre liste de contrôle des opérations.

### Vérifier l'accès au jeton

Testez le jeton avec curl :

curl -k -H "Authorization: PVEAPIToken=terraform@pve!ci-token=<SECRET>" \
 https://pve-node-1.example.com:8006/api2/json/nodes
# Attendu : tableau JSON des nœuds 
 Une erreur 401 ou 403 signifie que le jeton est mal configuré.

## Construction du pipeline CI/CD avec Terraform et GitLab CI

Nous utiliserons Terraform pour définir une VM Proxmox, Ansible pour la configurer et GitLab CI pour tout orchestrer. Le même schéma fonctionne avec GitHub Actions, Jenkins ou tout exécuteur capable d'exécuter des conteneurs.

### Structure du dépôt

proxmox-ci/
├── .gitlab-ci.yml
├── terraform/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── ansible/
│ ├── site.yml
│ └── inventory/
│ └── hosts.yml
└── scripts/
 └── healthcheck.sh 

### Configuration du fournisseur Terraform

 terraform/main.tf :

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

provider "proxmox" {
 endpoint = var.proxmox_url
 insecure = true # uniquement pour les certificats auto-signés ; mettre false avec une CA valide
 api_token = "${var.proxmox_token_id}=${var.proxmox_token_secret}"
} 
 terraform/variables.tf :

variable "proxmox_url" {
 type = string
 sensitive = true
}
variable "proxmox_token_id" {
 type = string
 sensitive = true
}
variable "proxmox_token_secret" {
 type = string
 sensitive = true
}
variable "vm_name" {
 type = string
 default = "web-demo"
}
variable "vm_cpu" {
 type = number
 default = 2
}
variable "vm_memory" {
 type = number
 default = 2048
} 

### Définition de la VM

 Ajoutez une ressource VM à main.tf en utilisant le modèle d'image cloud Ubuntu :

resource "proxmox_virtual_environment_vm" "web" {
 name = var.vm_name
 node_name = "pve-node-1"
 vm_id = 9001

 cpu {
 cores = var.vm_cpu
 }
 memory {
 dedicated = var.vm_memory
 }

 agent {
 enabled = true
 }

 disk {
 datastore_id = "local-lvm"
 file_id = proxmox_virtual_environment_download_file.ubuntu_cloud_image.id
 interface = "scsi0"
 size = 20
 }

 network_device {
 bridge = "vmbr1"
 }

 operating_system {
 type = "l26"
 }

 initialization {
 datastore_id = "local-lvm"
 ip_config {
 ipv4 {
 address = "dhcp"
 }
 }
 user_account {
 username = "ubuntu"
 keys = [file("~/.ssh/id_rsa.pub")]
 }
 }
} 
 Avant de l'utiliser, téléchargez un modèle d'image cloud. Vous pouvez le faire manuellement ou avec Terraform :

resource "proxmox_virtual_environment_download_file" "ubuntu_cloud_image" {
 content_type = "iso"
 datastore_id = "local"
 node_name = "pve-node-1"
 url = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
 file_name = "noble-server-cloudimg-amd64.img"
} 
 Convertissez-le ensuite en modèle de VM. L'automatisation de la création du modèle dépasse le cadre de cet article, mais vous pouvez suivre le wiki Proxmox pour les images cloud-init.

### Pipeline GitLab CI

.gitlab-ci.yml :

stages:
 - validate
 - plan
 - deploy
 - configure
 - verify

variables:
 TF_ROOT: terraform
 ANSIBLE_HOST_KEY_CHECKING: "false"

before_script:
 - apt-get update && apt-get install -y curl jq openssh-client
 - cd $TF_ROOT
 - terraform --version

validate:
 stage: validate
 script:
 - terraform init -backend=false
 - terraform validate
 only:
 - merge_requests

plan:
 stage: plan
 script:
 - terraform init
 - terraform plan -out=plan.tfplan -var-file=vars/prod.tfvars
 artifacts:
 paths:
 - terraform/plan.tfplan
 expire_in: 1 week
 only:
 - main

deploy:
 stage: deploy
 script:
 - terraform init
 - terraform apply -auto-approve plan.tfplan
 - terraform output -json vm_ip > /tmp/vm_ip.json
 - cat /tmp/vm_ip.json
 artifacts:
 paths:
 - /tmp/vm_ip.json
 expire_in: 1 day
 when: manual
 only:
 - main

configure:
 stage: configure
 script:
 - apt-get update && apt-get install -y ansible
 - cd ansible
 - echo "[web]" > inventory/hosts.ini
 - IP=$(jq -r '.value' /tmp/vm_ip.json)
 - echo "web ansible_host=$IP ansible_user=ubuntu ansible_ssh_private_key_file=$SSH_PRIVATE_KEY" >> inventory/hosts.ini
 - ansible-playbook -i inventory/hosts.ini site.yml -e vm_ip=$IP
 only:
 - main

verify:
 stage: verify
 script:
 - IP=$(jq -r '.value' /tmp/vm_ip.json)
 - ./scripts/healthcheck.sh $IP
 only:
 - main 

### Configuration Ansible

 ansible/site.yml :

- name: Configurer le serveur web
 hosts: web
 become: true
 tasks:
 - name: Mettre à jour le cache apt
 apt:
 update_cache: yes

 - name: Installer nginx
 apt:
 name: nginx
 state: present

 - name: Démarrer nginx
 service:
 name: nginx
 state: started
 enabled: yes

 - name: Créer index.html
 copy:
 content: "<html><body><h1>Déployé par CI/CD</h1></body></html>"
 dest: /var/www/html/index.html
 owner: www-data
 group: www-data
 mode: '0644' 

### Script de contrôle de santé

 scripts/healthcheck.sh :

#!/bin/bash
IP=$1
for i in {1..5}; do
 HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://$IP/)
 if [ "$HTTP_CODE" == "200" ]; then
 echo "Contrôle de santé réussi : HTTP 200"
 exit 0
 fi
 echo "Tentative $i : obtenu $HTTP_CODE, nouvelle tentative dans 5s..."
 sleep 5
done
echo "Contrôle de santé échoué"
exit 1 

### Résultats attendus et signaux d'échec

- terraform validate échoue si la syntaxe ou le bloc fournisseur est invalide.

- terraform plan montre les changements souhaités ; si aucun changement, la VM existe déjà.

- L'étape deploy applique le plan et affiche l'IP de la VM.

- L'étape configure installe nginx et le démarre.

- L'étape verify interroge l'IP et attend un code HTTP 200.

 Si une étape échoue, le pipeline s'arrête. Corrigez l'erreur, validez et relancez. L'état Terraform est stocké dans le backend GitLab (vous devez configurer un backend, par exemple HTTP ou S3, ou utiliser l'état Terraform géré par GitLab).

## Vérification et diagnostics

Après le déploiement, vérifiez toujours depuis l'extérieur de la VM, pas seulement depuis l'intérieur. Le script de contrôle de santé le fait déjà, mais voici des vérifications supplémentaires.

### Vérifier le statut de la VM via l'API Proxmox

curl -k -H "Authorization: PVEAPIToken=terraform@pve!ci-token=<SECRET>" \
 https://pve-node-1.example.com:8006/api2/json/nodes/pve-node-1/qemu/9001/status/current
# Attendu : JSON avec "status": "running" 

### Vérifier les journaux système dans la VM

 Dans le playbook Ansible, ajoutez une tâche pour récupérer les journaux si nécessaire :

- name: Obtenir le journal d'erreurs nginx
 command: tail -n 20 /var/log/nginx/error.log
 register: nginx_log
 ignore_errors: true

- name: Afficher le journal
 debug:
 msg: "{{ nginx_log.stdout }}" 

### Diagnostiquer les problèmes courants

 Symptôme Cause probable Commande de diagnostic 
 Terraform apply échoue avec 401 Mauvais jeton ou secret curl -I -H "Authorization: ..." https://pve/api2/json 
 VM créée mais n'obtient jamais d'IP Cloud-init non configuré Vérifiez la sortie console de la VM : qm terminal 9001 
 SSH Ansible échoue Pas de clé SSH injectée ou pare-feu ssh -i key ubuntu@IP depuis l'exécuteur 
 Le contrôle de santé obtient une connexion refusée nginx non démarré ou pare-feu Dans la VM : systemctl status nginx , ufw status 
 API Proxmox lente ou délais d'attente Mauvaise configuration réseau ou DNS ping pve-node-1 , curl -v l'API 

 Capturez toujours la sortie exacte de la commande avant de faire des modifications. Le journal des tâches Proxmox (interface web > Nœud > Historique des tâches) montre les actions et erreurs de l'API.

## Modes de défaillance et récupération

L'automatisation peut échouer de nombreuses façons. Voici les échecs les plus probables et comment les récupérer.

### 1. Corruption de l'état Terraform

Échec : Une exécution de pipeline est interrompue pendant que Terraform applique. Le fichier d'état sur le backend peut être verrouillé ou corrompu.

Récupération :

- Vérifiez si l'état est verrouillé : terraform force-unlock <lock-id> (obtenez l'ID de verrouillage à partir de l'erreur).

- Si l'état est corrompu, restaurez à partir d'une sauvegarde. L'état géré par GitLab a une gestion des versions ; vous pouvez revenir en arrière.

- Relancez terraform plan puis apply .

### 2. VM déployée mais la configuration échoue

Échec : Terraform crée la VM, mais Ansible échoue à mi-chemin (par exemple, apt update échoue en raison du DNS). La VM reste dans un état partiellement configuré.

Récupération :

- Diagnostiquez via les journaux Ansible. Si corrigible, relancez uniquement la tâche en échec : ansible-playbook -i inventory site.yml --start-at-task="Installer nginx"

- Si la VM est inutilisable, détruisez-la avec Terraform : terraform destroy -target=proxmox_virtual_environment_vm.web et redéployez.

- Pour éviter l'état partiel, définissez ignore_errors: false et utilisez block/rescue dans Ansible pour nettoyer.

### 3. Retour arrière vers une version précédente de la VM

Scénario : Un nouveau déploiement introduit une version cassée de l'application. Vous devez revenir à l'instantané précédent de la VM.

Récupération :

Si vous avez pris un instantané Proxmox avant le déploiement, le retour arrière est instantané :

qm rollback 9001 snap_before_update
# Attendu : la VM est ramenée à l'état de l'instantané, redémarrée 
 Vous devez intégrer la création d'instantanés dans votre pipeline avant d'exécuter des modifications de configuration :

qm snapshot 9001 snap_before_update --description "Avant la mise à jour de l'application" 
 Si vous utilisez une infrastructure immuable (traitez les VM comme jetables), vous déployez simplement la balise de version précédente depuis Git :

- Sur GitLab, balisez chaque build réussi avec v1.2.3 .

- Dans votre pipeline, autorisez le déploiement manuel de n'importe quelle balise.

- Pour revenir en arrière, exécutez le job de déploiement pour la balise précédente.

### 4. Suppression accidentelle de la VM de production

Échec : Une modification Terraform supprime la ressource VM parce que quelqu'un a changé le count ou le nom.

Récupération :

- Le plan Terraform aurait montré la suppression. Exigez une approbation manuelle pour l'application.

- Planifiez des sauvegardes Proxmox (par exemple, nocturnes vers PBS ou NFS). Restaurez à partir de la sauvegarde.

- Utilisez le méta-argument de cycle de vie prevent_destroy sur les VM critiques.

lifecycle {
 prevent_destroy = true
} 

### 5. Le jeton API expire ou est révoqué

 Échec : Le pipeline échoue avec 401 parce que le jeton a été renouvelé ou révoqué.

Récupération :

- Conservez le secret du jeton dans un gestionnaire de secrets avec renouvellement automatique.

- Dans GitLab, mettez à jour la variable CI et relancez le pipeline.

- Documentez l'expiration du jeton dans votre runbook.

## Liste de contrôle des opérations

Utilisez cette liste avant et après chaque déploiement en production.

### Liste de contrôle pré-déploiement

<div class="my-stack-md overflow-x-auto">
<table class="min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Élément</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Responsable</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Fréquence</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Versions Proxmox et stockage disponibles</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Ingénieur DevOps</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">À chaque déploiement</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Plan Terraform examiné et approuvé</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Chef d&#39;équipe</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">À chaque déploiement</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Secrets renouvelés et valides</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Ingénieur sécurité</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Mensuel</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Sauvegarde de la VM existante effectuée</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Ingénieur DevOps</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Avant les modifications de configuration</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Script de contrôle de santé mis à jour pour le nouveau service</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Développeur</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">À chaque déploiement</td></tr></tbody>
</table>
</div>

### Liste de contrôle post-déploiement

<div class="my-stack-md overflow-x-auto">
<table class="min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Élément</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Responsable</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Fréquence</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Contrôle de santé réussi</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Pipeline CI</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">À chaque déploiement</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">VM en cours d&#39;exécution et IP correcte</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Ingénieur DevOps</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">À chaque déploiement</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Les journaux ne montrent pas de boucles de crash</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Développeur</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Dans l&#39;heure</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Alertes de surveillance configurées</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">SRE</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Hebdomadaire</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Documentation mise à jour avec la nouvelle version</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Rédacteur technique</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Hebdomadaire</td></tr></tbody>
</table>
</div>
Attribuez un responsable unique pour chaque élément de la liste de contrôle. Le responsable décide quand revoir l'élément et signe l'achèvement. Utilisez un système de tickets (Jira, GitLab issues) pour suivre ces tâches.

## Pièges courants et comment les éviter

### 1. Utiliser le compte root pour l'automatisation

Pourquoi cela arrive : Commodité. Vous devez faire avancer les choses rapidement. Conséquence : Un jeton CI compromis donne un contrôle total de l'hôte. Évitement : Créez un utilisateur dédié avec un jeton API et un rôle minimal (PVEVMAdmin). Renouvelez régulièrement les jetons.

### 2. Coder en dur les secrets dans Git

Pourquoi cela arrive : Les développeurs veulent que le pipeline fonctionne sans configuration supplémentaire. Conséquence : Les secrets se retrouvent dans l'historique des commits, exposés à toute personne ayant accès au dépôt. Évitement : Utilisez les variables CI/CD, Vault ou des secrets externes. Analysez les dépôts à la recherche de secrets avec git-secrets ou la détection de secrets de GitLab.

### 3. Ne pas épingler les versions des fournisseurs

Pourquoi cela arrive : Utiliser latest ou omettre la version dans le fournisseur Terraform. Conséquence : Une nouvelle version du fournisseur peut casser votre configuration. Évitement : Épinglez des versions exactes dans required_providers et utilisez un fichier de verrouillage ( terraform init génère .terraform.lock.hcl ). Validez le fichier de verrouillage.

### 4. Sauter l'approbation du plan Terraform

Pourquoi cela arrive : Pour accélérer les déploiements, vous définissez toujours auto-approve . Conséquence : Un mauvais changement peut détruire la production sans examen humain. Évitement : Utilisez une étape d'approbation manuelle dans le pipeline ( when: manual ) avant l'application. Examinez la sortie du plan.

### 5. Pas de plan de retour arrière

Pourquoi cela arrive : Les équipes supposent que les nouvelles versions fonctionnent toujours. Conséquence : Lorsqu'une mauvaise version sort, la panique et les corrections manuelles provoquent des interruptions. Évitement : Ayez toujours un retour arrière par instantané ou un déploiement de la balise précédente prêt. Testez le retour arrière en staging avant la production.

### 6. Ignorer la configuration cloud-init

Pourquoi cela arrive : Vous créez une VM à partir d'une image disque sans cloud-init et vous vous attendez à ce que les clés SSH fonctionnent. Conséquence : La VM démarre mais vous ne pouvez pas vous connecter, Ansible échoue. Évitement : Utilisez des images cloud-init et un bloc initialization approprié dans Terraform. Testez l'accès SSH avant de continuer.

### 7. Ne pas surveiller après le déploiement

Pourquoi cela arrive : Le pipeline passe, tout le monde passe à autre chose. Conséquence : Le service échoue plus tard sans être remarqué. Évitement : Intégrez la surveillance (Prometheus, Grafana) et configurez des alertes. Incluez une étape de vérification qui s'exécute périodiquement, pas seulement une fois.

## Conclusion

L'automatisation CI/CD avec Proxmox est un moyen puissant de gérer l'infrastructure comme du code, mais elle exige de la discipline. En suivant les étapes de ce guide — inventaire, configuration sécurisée, pipeline avec Terraform et Ansible, vérification et planification de la récupération — vous pouvez déployer des VM de manière fiable et revenir rapidement en arrière lorsque les choses tournent mal.

Commencez avec une VM à faible risque, mettez en œuvre le pipeline et entraînez-vous à un retour arrière. Ensuite, étendez-vous à plus de VM et à des applications complexes. Gardez votre automatisation versionnée, vos secrets en sécurité et vos contrôles de santé significatifs. Un flux de travail fiable rend les échecs visibles et la récupération une routine, pas une crise.