test 001
Terraform - plan / plan (push) Failing after 6s

This commit is contained in:
hcornet committed 2026-09-13 13:02:19 +02:00
1 parent b046f4054e
commit a918ebec8e
10 files changed
+471 -11

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
* text=auto eol=lf
*.sh text eol=lf
*.tf text eol=lf
*.tftpl text eol=lf
*.yml text eol=lf
+81
View File
@@ -0,0 +1,81 @@
name: Terraform - deployer la VM Debian
on:
workflow_dispatch:
inputs:
action:
description: "plan pour vérifier, apply pour créer ou mettre à jour la VM"
type: choice
required: true
default: plan
options:
- plan
- apply
concurrency:
group: terraform-proxmox-debian-12
cancel-in-progress: false
env:
TF_VERSION: ${{ vars.TF_VERSION || '1.9.8' }}
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
OPENBAO_ADDR: ${{ vars.OPENBAO_ADDR }}
OPENBAO_ROLE_ID: ${{ secrets.OPENBAO_ROLE_ID }}
OPENBAO_SECRET_ID: ${{ secrets.OPENBAO_SECRET_ID }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- 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 \
-backend-config="bucket=${{ vars.TF_BACKEND_BUCKET }}" \
-backend-config="key=${{ vars.TF_BACKEND_KEY }}" \
-backend-config="region=${{ vars.TF_BACKEND_REGION }}"
- name: Calculer le plan
run: terraform plan -var-file=lab.tfvars -lock-timeout=120s -out=tfplan
- name: Résumé du plan
run: terraform show -no-color tfplan | tail -n 40
- name: Appliquer le plan
if: ${{ inputs.action == 'apply' }}
run: terraform apply -lock-timeout=120s -auto-approve tfplan
- name: Afficher le point d'entrée
if: ${{ inputs.action == 'apply' }}
run: |
echo "VM : $(terraform output -raw vm_fqdn) ($(terraform output -raw vm_ipv4_address))"
echo "SSH : $(terraform output -raw ssh_command)"
echo "cloud-init tourne encore 3 à 6 minutes après la fin de ce job."
- name: Publier l'inventaire Ansible
if: ${{ inputs.action == 'apply' }}
continue-on-error: true
uses: actions/upload-artifact@v3
with:
name: inventaire-ansible
path: generated/inventory.yml
retention-days: 7
+61
View File
@@ -0,0 +1,61 @@
name: Terraform - detruire la VM Debian
on:
workflow_dispatch:
inputs:
confirmation:
description: "Saisir DETRUIRE pour confirmer la suppression de la VM et de son disque"
type: string
required: true
concurrency:
group: terraform-proxmox-debian-12
cancel-in-progress: false
env:
TF_VERSION: ${{ vars.TF_VERSION || '1.9.8' }}
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
OPENBAO_ADDR: ${{ vars.OPENBAO_ADDR }}
OPENBAO_ROLE_ID: ${{ secrets.OPENBAO_ROLE_ID }}
OPENBAO_SECRET_ID: ${{ secrets.OPENBAO_SECRET_ID }}
jobs:
destroy:
runs-on: ubuntu-latest
steps:
- name: Vérifier la confirmation
run: |
if [ "${{ inputs.confirmation }}" != "DETRUIRE" ]; then
echo "Confirmation absente ou incorrecte : rien n'a été détruit."
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 \
-backend-config="bucket=${{ vars.TF_BACKEND_BUCKET }}" \
-backend-config="key=${{ vars.TF_BACKEND_KEY }}" \
-backend-config="region=${{ vars.TF_BACKEND_REGION }}"
- name: Détruire
run: terraform destroy -var-file=lab.tfvars -lock-timeout=120s -auto-approve
+60
View File
@@ -0,0 +1,60 @@
name: Terraform - plan
on:
pull_request:
branches: [main]
push:
branches: [main]
# Un seul job Terraform à la fois : l'état est partagé, autant ne pas se battre
# avec le verrou du backend.
concurrency:
group: terraform-proxmox-debian-12
cancel-in-progress: false
env:
TF_VERSION: ${{ vars.TF_VERSION || '1.9.8' }}
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
OPENBAO_ADDR: ${{ vars.OPENBAO_ADDR }}
OPENBAO_ROLE_ID: ${{ secrets.OPENBAO_ROLE_ID }}
OPENBAO_SECRET_ID: ${{ secrets.OPENBAO_SECRET_ID }}
jobs:
plan:
runs-on: ubuntu-latest
steps:
- 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: Vérifier le formatage
run: terraform fmt -check -recursive
- name: Initialiser le backend S3
run: |
terraform init \
-backend-config="bucket=${{ vars.TF_BACKEND_BUCKET }}" \
-backend-config="key=${{ vars.TF_BACKEND_KEY }}" \
-backend-config="region=${{ vars.TF_BACKEND_REGION }}"
- name: Valider la configuration
run: terraform validate
- name: Calculer le plan
run: terraform plan -var-file=lab.tfvars -lock-timeout=120s
+2
View File
@@ -7,3 +7,5 @@ terraform.tfvars
generated/
override.tf
override.tf.json
backend.hcl
tfplan
+124
View File
@@ -0,0 +1,124 @@
# CI/CD — `terraform-proxmox-serveur-debian-12`
Trois workflows Gitea Actions, un script de lecture des secrets, un backend S3.
Aucun secret ne vit dans Gitea en dehors de quoi ouvrir OpenBao.
| Workflow | Fichier | Déclencheur | Ce qu'il fait |
|---|---|---|---|
| plan | `.gitea/workflows/terraform-plan.yml` | pull request et push sur `main` | `fmt -check`, `validate`, `plan` — ne modifie rien |
| deploy | `.gitea/workflows/deploy.yml` | manuel, `action` = `plan` ou `apply` | plan enregistré puis appliqué si demandé |
| destroy | `.gitea/workflows/destroy.yml` | manuel, confirmation `DETRUIRE` | supprime la VM et son disque |
Les trois partagent un `concurrency` commun : deux exécutions ne peuvent pas se
disputer le verrou de l'état S3.
## 1. Préparer OpenBao
Le dépôt du snippet cloud-init passe par SSH, il faut donc le mot de passe du nœud
dans le secret Proxmox existant :
```bash
bao kv patch secret/proxmox ssh_password='<mot de passe root du noeud pve>'
```
Le secret doit contenir au final `api_url`, `api_token`, `node_name`, `ssh_password`.
L'adresse du nœud est déduite de `api_url`, il n'y a rien de plus à saisir.
Politique et AppRole dédiés, en lecture seule sur les deux secrets utiles :
```bash
bao policy write terraform-proxmox-debian-12 - <<'POLICY'
path "secret/data/proxmox" { capabilities = ["read"] }
path "secret/metadata/proxmox" { capabilities = ["read"] }
path "secret/data/aws" { capabilities = ["read"] }
path "secret/metadata/aws" { capabilities = ["read"] }
POLICY
bao write auth/approle/role/terraform-proxmox-debian-12 \
token_policies=terraform-proxmox-debian-12 token_ttl=1h token_max_ttl=4h
bao read auth/approle/role/terraform-proxmox-debian-12/role-id
bao write -f auth/approle/role/terraform-proxmox-debian-12/secret-id
```
## 2. Renseigner Gitea
**Paramètres → Actions → Secrets** :
| Nom | Valeur |
|---|---|
| `OPENBAO_ROLE_ID` | sortie de `role-id` |
| `OPENBAO_SECRET_ID` | sortie de `secret-id` |
**Paramètres → Actions → Variables** :
| Nom | Valeur |
|---|---|
| `OPENBAO_ADDR` | `https://openbao.tips-of-mine.com` |
| `TF_BACKEND_BUCKET` | le bucket déjà utilisé par `lab-ad` |
| `TF_BACKEND_KEY` | `terraform-proxmox-serveur-debian-12/terraform.tfstate` |
| `TF_BACKEND_REGION` | la région du bucket |
| `TF_VERSION` | facultatif, `1.9.8` sinon |
`TF_BACKEND_KEY` doit être différente de celle de `lab-ad` : deux projets sur la même
clé écraseraient mutuellement leur état.
Si votre version de Gitea n'expose pas encore les variables de dépôt, déclarez les
cinq entrées en secrets et remplacez `vars.` par `secrets.` dans les trois workflows —
ce sont des valeurs de configuration, pas des secrets, mais ça fonctionne pareil.
## 3. Pousser le code
```powershell
cd D:\Users\thedj\git\terraform-proxmox-serveur-debian-12
git add -A
git update-index --chmod=+x scripts/openbao-env.sh
git commit -m "CI/CD : plan, deploy, destroy"
git push
```
Le `--chmod=+x` n'est pas optionnel : le bit d'exécution se perd au transfert depuis
Windows et le job s'arrête sur un « Permission denied ». Le `.gitattributes` fige les
fins de ligne en LF pour la même raison — un script en CRLF échoue avec un
`bad interpreter` peu parlant.
## 4. Tester, dans cet ordre
**a. Le plan automatique.** Le push déclenche `Terraform - plan`. C'est le test le plus
utile : il valide la chaîne complète — AppRole, lecture des secrets, backend S3,
provider Proxmox — sans rien créer.
Le journal doit afficher `Secrets chargés depuis OpenBao : Proxmox (4 valeurs),
backend S3 (2 valeurs)` puis un plan annonçant **4 ressources à ajouter** : l'image
Debian téléchargée, le snippet cloud-init, la VM, le fichier d'inventaire.
**b. Le deploy en mode plan.** Lancer `Terraform - deployer la VM Debian` avec
`action=plan`. Même résultat, plus l'étape « Résumé du plan ». Cela vérifie le
déclenchement manuel et la construction du fichier `tfplan`.
**c. Le deploy en mode apply.** Relancer avec `action=apply`. Compter 2 à 4 minutes,
dont le téléchargement de l'image Debian au premier passage. À la fin, le job affiche
l'adresse et la commande SSH.
Puis, depuis votre poste :
```bash
ssh [email protected] "cloud-init status --wait && sudo nft list chain inet filter input"
```
La sortie ne doit montrer que `lo`, `established,related`, l'ICMP et le port 22.
**d. Le destroy.** Lancer `Terraform - detruire la VM Debian` avec une confirmation
erronée d'abord : le job doit s'arrêter immédiatement sans rien toucher. Recommencer
avec `DETRUIRE`.
## Les pannes les plus probables au premier essai
| Symptôme | Cause |
|---|---|
| `Secret manquant dans OpenBao : secret/proxmox → ssh_password` | l'étape 1 n'a pas été faite |
| `permission denied` sur `./scripts/openbao-env.sh` | bit d'exécution perdu, voir étape 3 |
| `403` du provider Proxmox au plan | rôle du jeton incomplet : il faut `SDN.Use`, et `Datastore.AllocateTemplate` pour le téléchargement de l'image |
| `unable to create file ... snippets` | contenu `snippets` absent du stockage `local` : `pvesm set local --content images,rootdir,vztmpl,backup,iso,snippets` |
| `Error acquiring the state lock` | un job précédent est mort en cours ; lever le verrou avec `terraform force-unlock <id>` |
| La VM démarre mais SSH refuse la clé | `ssh_public_keys` de `lab.tfvars` non remplacé. cloud-init ne rejoue rien : détruire et recréer |
+21 -11
View File
@@ -55,28 +55,38 @@ 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`.
## Exécution sans installer Terraform
## 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
d'administration, à remplacer avant le premier déploiement.
## Exécution ponctuelle, sans installer Terraform
Pour un essai hors CI, avec un `backend.hcl` local non versionné décrivant le bucket :
```bash
cp terraform.tfvars.example terraform.tfvars # puis adapter, surtout ssh_public_keys
export TF_VAR_pve_api_token='...' TF_VAR_pve_ssh_password='...'
export AWS_ACCESS_KEY_ID='...' AWS_SECRET_ACCESS_KEY='...'
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 \
hashicorp/terraform:latest init
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
hashicorp/terraform:latest init -backend-config=backend.hcl
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 \
hashicorp/terraform:latest apply
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
hashicorp/terraform:latest apply -var-file=lab.tfvars
```
Sous PowerShell, remplacer `"$PWD"` par `${PWD}` et passer les variables avec
`-e TF_VAR_pve_api_token=$env:TF_VAR_pve_api_token`.
L'état reste local (`terraform.tfstate`) : c'est une plateforme de test, il n'y a pas
de backend distant. Le passage sur le backend S3 se fait en ajoutant un `state.tf`,
comme dans `proxmox-create-vm`.
Attention à ne pas jouer un `apply` local pendant qu'un job tourne : l'état est
partagé, le verrou S3 est là pour ça mais mieux vaut ne pas le provoquer.
## Après l'apply
+24
View File
@@ -0,0 +1,24 @@
# 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
ansible_user = "ansible"
ssh_allowed_cidrs = ["10.0.4.0/24", "10.0.5.0/24"]
# Clé publique d'administration. Ce n'est pas un secret : elle reste ici plutôt que
# dans OpenBao, sinon un -var-file l'emporterait sur la variable d'environnement.
ssh_public_keys = [
"ssh-ed25519 AAAA... hcornet@poste",
]
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
#
# Lit les secrets nécessaires au déploiement dans OpenBao et les pousse dans
# l'environnement du job Gitea. Aucune valeur n'est affichée : chaque secret est
# masqué dans le journal avant d'être exporté.
#
# Attendus en entrée (variables d'environnement) :
# OPENBAO_ADDR variable de dépôt Gitea, ex. https://openbao.tips-of-mine.com
# OPENBAO_ROLE_ID secret Gitea
# OPENBAO_SECRET_ID secret Gitea
#
set -euo pipefail
: "${OPENBAO_ADDR:?variable OPENBAO_ADDR absente}"
: "${OPENBAO_ROLE_ID:?variable OPENBAO_ROLE_ID absente}"
: "${OPENBAO_SECRET_ID:?variable OPENBAO_SECRET_ID absente}"
: "${GITHUB_ENV:?ce script est prévu pour un job Gitea Actions}"
masquer() { printf '::add-mask::%s\n' "$1"; }
exporter() { printf '%s=%s\n' "$1" "$2" >>"${GITHUB_ENV}"; }
exiger() {
# $1 = valeur, $2 = description
if [ -z "$1" ] || [ "$1" = "null" ]; then
echo "Secret manquant dans OpenBao : $2" >&2
exit 1
fi
}
# --- Authentification AppRole -------------------------------------------------
BAO_TOKEN="$(
curl -sS --fail-with-body \
--request POST \
--data "{\"role_id\":\"${OPENBAO_ROLE_ID}\",\"secret_id\":\"${OPENBAO_SECRET_ID}\"}" \
"${OPENBAO_ADDR}/v1/auth/approle/login" | jq -r '.auth.client_token // empty'
)"
exiger "${BAO_TOKEN}" "jeton AppRole (vérifier role_id / secret_id)"
masquer "${BAO_TOKEN}"
lire_secret() {
curl -sS --fail-with-body \
-H "X-Vault-Token: ${BAO_TOKEN}" \
"${OPENBAO_ADDR}/v1/secret/data/$1" | jq -r '.data.data'
}
# --- Accès Proxmox ------------------------------------------------------------
PROXMOX="$(lire_secret proxmox)"
API_URL="$(jq -r '.api_url // empty' <<<"${PROXMOX}")"
API_TOKEN="$(jq -r '.api_token // empty' <<<"${PROXMOX}")"
NODE_NAME="$(jq -r '.node_name // empty' <<<"${PROXMOX}")"
SSH_PASSWORD="$(jq -r '.ssh_password // empty' <<<"${PROXMOX}")"
exiger "${API_URL}" "secret/proxmox → api_url"
exiger "${API_TOKEN}" "secret/proxmox → api_token"
exiger "${NODE_NAME}" "secret/proxmox → node_name"
exiger "${SSH_PASSWORD}" "secret/proxmox → ssh_password (à ajouter, le dépôt du snippet cloud-init passe par SSH)"
# Adresse du nœud déduite de l'URL de l'API, pour ne pas dupliquer l'information.
NODE_ADDRESS="$(sed -E 's#^https?://([^:/]+).*#\1#' <<<"${API_URL}")"
exiger "${NODE_ADDRESS}" "adresse du nœud déduite de api_url"
masquer "${API_TOKEN}"
masquer "${SSH_PASSWORD}"
exporter TF_VAR_pve_endpoint "${API_URL}"
exporter TF_VAR_pve_api_token "${API_TOKEN}"
exporter TF_VAR_pve_node "${NODE_NAME}"
exporter TF_VAR_pve_node_address "${NODE_ADDRESS}"
exporter TF_VAR_pve_ssh_password "${SSH_PASSWORD}"
# --- Backend S3 ---------------------------------------------------------------
AWS="$(lire_secret aws)"
AWS_KEY="$(jq -r '.AWS_ACCESS_KEY_ID // empty' <<<"${AWS}")"
AWS_SECRET="$(jq -r '.AWS_SECRET_ACCESS_KEY // empty' <<<"${AWS}")"
exiger "${AWS_KEY}" "secret/aws → AWS_ACCESS_KEY_ID"
exiger "${AWS_SECRET}" "secret/aws → AWS_SECRET_ACCESS_KEY"
masquer "${AWS_KEY}"
masquer "${AWS_SECRET}"
exporter AWS_ACCESS_KEY_ID "${AWS_KEY}"
exporter AWS_SECRET_ACCESS_KEY "${AWS_SECRET}"
echo "Secrets chargés depuis OpenBao : Proxmox (4 valeurs), backend S3 (2 valeurs)."
+8
View File
@@ -0,0 +1,8 @@
terraform {
# Configuration partielle : bucket, clé et région sont fournis au « terraform init »
# par la CI (variables de dépôt Gitea), les identifiants AWS par OpenBao.
# En local, utiliser les mêmes -backend-config ou un fichier backend.hcl non versionné.
backend "s3" {
encrypt = true
}
}