Renommer le repertoire cles en key
Terraform - plan / plan (push) Failing after 16s

This commit is contained in:
hcornet committed 2026-09-14 12:33:39 +02:00
1 parent bed69945ce
commit 442b018f5d
17 files changed
+322 -25

No files matched your search

+19 -4
View File
@@ -3,6 +3,11 @@ name: Terraform - deployer la VM Debian
on:
workflow_dispatch:
inputs:
vm:
description: "Nom de la VM, tel que le fichier vms/<nom>.tfvars"
type: string
required: true
default: centreon
action:
description: "plan pour vérifier, apply pour créer ou mettre à jour la VM"
type: choice
@@ -49,8 +54,18 @@ jobs:
- name: Initialiser le backend S3
run: terraform init
- name: Sélectionner l'espace de travail de la VM
run: |
set -euo pipefail
test -f "vms/${{ inputs.vm }}.tfvars" || {
echo "vms/${{ inputs.vm }}.tfvars n'existe pas. VM déclarées :"
ls vms/*.tfvars | xargs -n1 basename
exit 1
}
terraform workspace select -or-create "${{ inputs.vm }}"
- name: Calculer le plan
run: terraform plan -var-file=lab.tfvars -lock-timeout=120s -out=tfplan
run: terraform plan -var-file="vms/${{ inputs.vm }}.tfvars" -lock-timeout=120s -out=tfplan
- name: Résumé du plan
run: terraform show -no-color tfplan | tail -n 40
@@ -79,7 +94,7 @@ jobs:
continue-on-error: true
uses: actions/upload-artifact@v3
with:
name: mot-de-passe-root
name: mot-de-passe-root-${{ inputs.vm }}
path: mot-de-passe-root.txt
retention-days: 1
@@ -92,6 +107,6 @@ jobs:
continue-on-error: true
uses: actions/upload-artifact@v3
with:
name: inventaire-ansible
path: generated/inventory.yml
name: inventaire-ansible-${{ inputs.vm }}
path: generated/
retention-days: 7
+15 -1
View File
@@ -3,6 +3,11 @@ name: Terraform - detruire la VM Debian
on:
workflow_dispatch:
inputs:
vm:
description: "Nom de la VM à détruire, tel que le fichier vms/<nom>.tfvars"
type: string
required: true
default: centreon
confirmation:
description: "Saisir DETRUIRE pour confirmer la suppression de la VM et de son disque"
type: string
@@ -52,5 +57,14 @@ jobs:
- name: Initialiser le backend S3
run: terraform init
- name: Sélectionner l'espace de travail de la VM
run: |
set -euo pipefail
test -f "vms/${{ inputs.vm }}.tfvars" || {
echo "vms/${{ inputs.vm }}.tfvars n'existe pas."
exit 1
}
terraform workspace select "${{ inputs.vm }}"
- name: Détruire
run: terraform destroy -var-file=lab.tfvars -lock-timeout=120s -auto-approve
run: terraform destroy -var-file="vms/${{ inputs.vm }}.tfvars" -lock-timeout=120s -auto-approve
+90
View File
@@ -0,0 +1,90 @@
name: Terraform - migrer l etat vers les espaces de travail
# À exécuter UNE SEULE FOIS, après le passage aux espaces de travail.
# La VM centreon existante vit dans l'espace « default » : ce workflow recopie
# son état dans un espace nommé « centreon », sans rien détruire ni recréer.
on:
workflow_dispatch:
inputs:
confirmation:
description: "Saisir MIGRER pour confirmer la recopie de l etat"
type: string
required: true
concurrency:
group: terraform-proxmox-debian-12
cancel-in-progress: false
env:
TF_VERSION: "1.9.8"
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
OPENBAO_ROLE_ID: ${{ secrets.OPENBAO_ROLE_ID }}
OPENBAO_SECRET_ID: ${{ secrets.OPENBAO_SECRET_ID }}
jobs:
migrer:
runs-on: ubuntu-latest
steps:
- name: Vérifier la confirmation
run: |
if [ "${{ inputs.confirmation }}" != "MIGRER" ]; then
echo "Confirmation absente ou incorrecte : rien n'a été fait."
exit 1
fi
- name: Récupérer le dépôt
uses: actions/checkout@v4
- name: Installer les outils
run: |
set -euo pipefail
if ! command -v jq >/dev/null || ! command -v unzip >/dev/null; then
apt-get update -qq
apt-get install -y -qq curl jq unzip
fi
curl -fsSL -o /tmp/terraform.zip \
"https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
unzip -oq /tmp/terraform.zip -d /usr/local/bin
terraform version
- name: Charger les secrets depuis OpenBao
run: ./scripts/openbao-env.sh
- name: Initialiser le backend S3
run: terraform init
- name: Recopier l'état de default vers centreon
run: |
set -euo pipefail
terraform workspace select default
terraform state pull > etat-default.json
ressources=$(jq '.resources | length' etat-default.json)
echo "Ressources dans l'état default : $ressources"
if [ "$ressources" -eq 0 ]; then
echo "L'état default est vide, il n'y a rien à migrer."
exit 0
fi
terraform workspace select -or-create centreon
terraform state push -force etat-default.json
terraform workspace select centreon
terraform state list
- name: Vérifier qu'aucune modification n'est proposée
run: |
terraform workspace select centreon
terraform plan -var-file=vms/centreon.tfvars -lock-timeout=120s -detailed-exitcode || {
code=$?
if [ "$code" -eq 2 ]; then
echo "Le plan propose des modifications : à examiner avant tout apply."
fi
exit $code
}
- name: Effacer la copie locale de l'état
if: ${{ always() }}
run: rm -f etat-default.json
+10 -2
View File
@@ -51,5 +51,13 @@ jobs:
- name: Valider la configuration
run: terraform validate
- name: Calculer le plan
run: terraform plan -var-file=lab.tfvars -lock-timeout=120s
- name: Calculer le plan de chaque VM déclarée
run: |
set -euo pipefail
for fichier in vms/*.tfvars; do
vm=$(basename "$fichier" .tfvars)
echo "::group::$vm"
terraform workspace select -or-create "$vm"
terraform plan -var-file="$fichier" -lock-timeout=120s
echo "::endgroup::"
done
+3 -3
View File
@@ -11,6 +11,6 @@ backend.hcl
tfplan
# Seules les cles publiques sont versionnees
cles/*
!cles/*.pub
!cles/README.md
key/*
!key/*.pub
!key/README.md
+1 -1
View File
@@ -85,7 +85,7 @@ Le fichier `.gitattributes` livré fige les fins de ligne en LF, pour la même r
### 4. Clé publique
`lab.tfvars` est versionné et contient la clé publique d'administration : y remplacer
Chaque fichier `vms/<nom>.tfvars` est versionné et référence les clés publiques : vérifier
le `ssh-ed25519 AAAA...` d'exemple avant le premier `apply`. Sans quoi la VM sera
créée avec une clé inutilisable et il faudra la recréer — cloud-init ne rejoue rien.
+5 -5
View File
@@ -20,18 +20,18 @@ humain, ou la console Proxmox quand le réseau est cassé.
## Où vivent les clés
Un fichier `.pub` par identité dans `cles/`, référencé par `lab.tfvars`. Rien n'est
Un fichier `.pub` par identité dans `key/`, référencé par les fichiers `vms/*.tfvars`. Rien n'est
recopié dans le `.tfvars` lui-même.
```
cles/
key/
├── hcornet.pub livré, c'est votre clé
├── invite.pub à déposer pour activer le compte invite
└── ansible.pub à déposer pour l'automatisation
```
Un compte dont `ssh_key_files` est vide **n'est pas créé**. `invite` et `ansible` sont
donc déclarés dans `lab.tfvars` mais inactifs : déposer leur `.pub` et compléter la
donc déclarés dans `vms/centreon.tfvars` mais inactifs : déposer leur `.pub` et compléter la
liste suffit à les activer au prochain déploiement.
Deux garde-fous avant création, faute de quoi une VM inaccessible serait produite et
@@ -45,11 +45,11 @@ C'est ce second contrôle qui manquait : la version précédente vérifiait qu'u
### Votre clé donne aussi l'accès root
`root_ssh_key_files` contient `cles/hcornet.pub` : `ssh [email protected]` fonctionne
`root_ssh_key_files` contient `key/hcornet.pub` : `ssh [email protected]` fonctionne
directement, sans mot de passe. Votre clé privée est donc un identifiant root à part
entière — ce qui rend sa phrase de passe et sa protection d'autant plus importantes.
Le jour où Ansible entre en jeu, ajouter `cles/ansible.pub` à cette même liste.
Le jour où Ansible entre en jeu, ajouter `key/ansible.pub` à cette même liste.
## Le mot de passe root
+9 -2
View File
@@ -55,12 +55,19 @@ export TF_VAR_pve_ssh_password='...'
Si le projet rejoint la CI Gitea, ces deux valeurs se lisent dans OpenBao comme pour
`proxmox-create-vm`, en réutilisant `secret/proxmox`.
## Une VM par fichier
Ce dépôt gère plusieurs VM : un fichier `vms/<nom>.tfvars` et un espace de travail
Terraform par machine, chacune avec son propre état. La version de Debian se choisit
dans ce fichier. Tout est décrit dans **[VMS.md](VMS.md)**.
## Déclenchement habituel : la CI
Le déploiement passe par Gitea Actions (`.gitea/workflows/`), l'état vit sur le backend
S3 et les secrets dans OpenBao. Tout est décrit dans **[CICD.md](CICD.md)**.
Les valeurs non sensibles sont versionnées dans `lab.tfvars` — dont la clé publique
Les valeurs non sensibles sont versionnées dans `vms/<nom>.tfvars`, un fichier par VM
(voir [VMS.md](VMS.md)) — dont la clé publique
d'administration, à remplacer avant le premier déploiement.
## Exécution ponctuelle, sans installer Terraform
@@ -79,7 +86,7 @@ docker run --rm -it -v "$PWD":/work -w /work \
docker run --rm -it -v "$PWD":/work -w /work \
-e TF_VAR_pve_api_token -e TF_VAR_pve_ssh_password \
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
hashicorp/terraform:latest apply -var-file=lab.tfvars
hashicorp/terraform:latest apply -var-file=vms/centreon.tfvars
```
Sous PowerShell, remplacer `"$PWD"` par `${PWD}` et passer les variables avec
+79
View File
@@ -0,0 +1,79 @@
# Créer plusieurs VM avec ce dépôt
Une VM = un fichier `vms/<nom>.tfvars` + un espace de travail Terraform du même nom.
Chaque VM a donc **son propre état** : en détruire une ne touche pas aux autres, et un
plan raté sur l'une n'a aucun effet sur l'autre.
```
vms/
├── centreon.tfvars VM 120, Debian 12, 10.0.5.20
└── exemple-debian13.tfvars VM 121, Debian 13, 10.0.5.21
```
Dans le bucket S3, les états se rangent sous `env:/<nom>/backend/…`, automatiquement.
## Ajouter une VM
Copier un fichier existant, le renommer, puis adapter. Trois valeurs doivent être
uniques sur l'infrastructure, et **rien ne le vérifie** — deux VM avec le même `vm_id`
provoquent un échec à la création, deux avec la même IP un conflit silencieux :
- `vm_id`
- `vm_hostname`
- `vm_ipv4_address`
Un `git push` déclenche le plan de **toutes** les VM déclarées : c'est le filet qui
attrape une erreur de syntaxe ou un fichier de clé manquant avant tout déploiement.
Pour créer la VM, lancer `deploy` en saisissant son nom dans le champ `vm`. Le workflow
refuse un nom sans fichier correspondant et liste les VM connues.
## Changer de version de Debian
Deux lignes, dans le fichier de la VM concernée :
```hcl
debian_image_url = "https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2"
debian_image_file_name = "debian-13-genericcloud-amd64.qcow2"
```
Le reste du code n'en dépend pas : comptes, durcissement SSH, nftables, agent invité et
mises à jour automatiques sont identiques d'une version à l'autre. Le nom du fichier
doit garder une extension `.qcow2`, `.raw` ou `.vmdk`, seules acceptées par le contenu
`import` de Proxmox.
Une limite à connaître : **Centreon 25.10 n'existe que pour Debian 12**. Le dépôt
`ansible-centreon` refuse d'ailleurs de s'exécuter sur autre chose. Une VM Debian 13
convient pour tout le reste.
## Migration de l'existant
La VM `centreon` a été créée avant ce découpage, son état vit donc dans l'espace
`default`. Le workflow **migrer-espaces** le recopie une fois pour toutes dans l'espace
`centreon`, sans rien détruire : confirmation `MIGRER`, puis il vérifie qu'aucune
modification n'est proposée ensuite.
À lancer **avant** tout autre workflow. Sans cette migration, un `deploy` sur la VM
`centreon` la verrait comme inexistante et tenterait de la recréer — avec un VMID déjà
pris, donc un échec, mais autant l'éviter.
L'espace `default` conservera une copie de l'état après migration. Terraform interdit de
le supprimer, et plus aucun workflow ne l'utilise : il devient inerte.
## Commandes hors CI
Pour un essai ponctuel, par conteneur :
```bash
docker run --rm -it -v "$PWD":/work -w /work \
-e TF_VAR_pve_api_token -e TF_VAR_pve_ssh_password \
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
hashicorp/terraform:latest workspace list
```
`workspace list` montre les espaces existants, `workspace select <nom>` bascule, et
chaque commande doit ensuite recevoir `-var-file=vms/<nom>.tfvars`. Se tromper de
couple espace/fichier est la seule vraie façon de se faire mal ici : Terraform
comparerait l'état d'une VM à la définition d'une autre et proposerait de tout recréer.
C'est la raison pour laquelle les workflows sélectionnent toujours les deux ensemble, à
partir du même nom.
+2 -2
View File
@@ -1,7 +1,7 @@
locals {
fqdn = "${var.vm_hostname}.${var.dns_domain}"
# Les clés sont lues dans les fichiers du répertoire cles/. Un fichier
# Les clés sont lues dans les fichiers du répertoire key/. Un fichier
# référencé mais absent fait échouer le plan, ce qui est le comportement
# voulu : mieux vaut un plan rouge qu'une VM inaccessible.
# Un compte sans aucun fichier de clé n'est pas créé.
@@ -68,7 +68,7 @@ resource "proxmox_virtual_environment_file" "user_data" {
for cle in local.toutes_les_cles :
can(regex("^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp[0-9]+|sk-ssh-ed25519) AAAA", cle))
])
error_message = "Un des fichiers de cles/ ne contient pas une clé publique valide."
error_message = "Un des fichiers de key/ ne contient pas une clé publique valide."
}
precondition {
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1 -1
View File
@@ -102,7 +102,7 @@ resource "proxmox_virtual_environment_vm" "centreon" {
}
resource "local_file" "ansible_inventory" {
filename = var.ansible_inventory_path
filename = coalesce(var.ansible_inventory_path, "generated/${var.vm_hostname}-inventory.yml")
file_permission = "0644"
content = yamlencode({
+8 -4
View File
@@ -196,7 +196,7 @@ variable "local_accounts" {
est retirée du système. Pour administrer, on ouvre une session avec son
compte puis on bascule par « su - » avec le mot de passe root.
ssh_key_files référence des fichiers du répertoire cles/, relatifs à la racine
ssh_key_files référence des fichiers du répertoire key/, relatifs à la racine
du dépôt. Un compte sans aucun fichier de clé n'est pas créé : c'est le moyen
de déclarer un compte à l'avance sans l'activer.
EOT
@@ -257,7 +257,11 @@ variable "ssh_allowed_cidrs" {
############################################
variable "ansible_inventory_path" {
description = "Chemin du fichier d'inventaire généré à la fin de l'apply."
type = string
default = "generated/inventory.yml"
description = <<-EOT
Chemin du fichier d'inventaire généré à la fin de l'apply. Laissé à null,
il vaut generated/<nom de la VM>-inventory.yml : chaque VM a le sien.
EOT
type = string
default = null
}
+43
View File
@@ -0,0 +1,43 @@
# Valeurs non sensibles, versionnées : c'est ce fichier que la CI passe en -var-file.
# Les secrets (jeton d'API, mot de passe SSH du nœud) arrivent par TF_VAR_*, depuis OpenBao.
vm_id = 120
vm_hostname = "centreon"
vm_bridge = "vmbr3"
vm_ipv4_address = "10.0.5.20"
vm_ipv4_gateway = "10.0.5.1"
dns_servers = ["10.0.5.1"]
dns_domain = "tips-of-mine.local"
vm_cores = 2
vm_memory = 4096
vm_disk_size = 40
ssh_allowed_cidrs = ["10.0.4.0/24", "10.0.5.0/24"]
# Comptes de la machine. Aucun n'a de privilège : sudo est absent du système.
# Pour administrer, ouvrir une session avec son compte puis « su - ».
# Les clés vivent dans key/, un fichier .pub par identité.
# Une liste ssh_key_files vide signifie : compte déclaré mais non créé.
local_accounts = [
{
name = "hcornet"
gecos = "Hubert Cornet - administration"
ssh_key_files = ["key/hcornet.pub"]
},
{
name = "invite"
gecos = "Compte de consultation - aucun privilege"
ssh_key_files = []
},
{
name = "ansible"
gecos = "Compte de service - sans privilege"
ssh_key_files = []
},
]
# Clés autorisées à ouvrir une session directement en root.
# Y ajouter key/ansible.pub le jour où l'automatisation entre en jeu.
root_ssh_key_files = ["key/hcornet.pub"]
+37
View File
@@ -0,0 +1,37 @@
# Exemple de seconde VM : Debian 13, pour un besoin quelconque.
# Copier ce fichier sous le nom de la VM à créer, puis adapter.
#
# Trois valeurs doivent être uniques sur l'infrastructure, rien ne le vérifie
# automatiquement : vm_id, vm_hostname et vm_ipv4_address.
vm_id = 121
vm_hostname = "bac-a-sable"
vm_bridge = "vmbr3"
vm_ipv4_address = "10.0.5.21"
vm_ipv4_gateway = "10.0.5.1"
dns_servers = ["10.0.5.1"]
dns_domain = "tips-of-mine.local"
vm_cores = 2
vm_memory = 2048
vm_disk_size = 20
# C'est ici, et nulle part ailleurs, que se choisit la version de Debian.
# Le reste du code n'en dépend pas : comptes, durcissement et pare-feu sont
# identiques. Attention, Centreon 25.10 exige Debian 12 : cette VM-ci ne peut
# pas accueillir le dépôt Ansible centreon.
debian_image_url = "https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2"
debian_image_file_name = "debian-13-genericcloud-amd64.qcow2"
ssh_allowed_cidrs = ["10.0.4.0/24", "10.0.5.0/24"]
local_accounts = [
{
name = "hcornet"
gecos = "Hubert Cornet - administration"
ssh_key_files = ["key/hcornet.pub"]
},
]
root_ssh_key_files = ["key/hcornet.pub"]