Lab : l'infrastructure de Signalements en code
Ce lab rassemble le cours Terraform et OpenTofu : les fondamentaux. Vous écrivez le dépôt signalements-iac, qui décrit en code l'architecture construite à la main dans Le cloud : les fondamentaux, vous l'appliquez dans un environnement de préproduction, puis un script vérifie que le dépôt et l'infrastructure ont les propriétés attendues. Il valide le niveau 200 des compétences « Provisionnement déclaratif » et « État et dérive de l'infrastructure ».
Comptez une journée. La leçon 10 donne une configuration complète : le lab ne consiste pas à la recopier, mais à la reconstruire en comprenant chaque bloc, et à y ajouter ce qu'elle laisse au lecteur.
Ce qu'il faut
- Terraform 1.16 ou OpenTofu 1.11 ou plus récent, Git,
jq, et la CLIscwconfigurée. - Un projet Scaleway
signalements-preprod(cours Scaleway en pratique), et un budget surveillé : l'architecture complète coûte quelques euros par jour. Détruisez-la en fin de séance. - Un bucket d'état amorcé à la main, avec le versionnement activé (leçon 7).
Les exigences
| Exigence | Leçon |
|---|---|
terraform fmt -check -recursive et terraform validate réussissent | 2, 3, 12 |
Le fichier .terraform.lock.hcl est versionné, et contient les empreintes de linux_amd64 et darwin_arm64 | 4 |
Le fournisseur Scaleway est épinglé par une contrainte ~> ; aucune clé d'API n'apparaît dans le code | 4 |
Les valeurs propres à un environnement sont dans environnements/preprod.tfvars et environnements/prod.tfvars ; chaque variable a un type et une description, et au moins une a une validation | 5 |
L'état est dans le backend s3 sur Object Storage, avec use_lockfile = true, et la configuration du backend est partielle (backend-preprod.hcl, backend-prod.hcl) | 7 |
Aucun fichier d'état, de plan ou de valeurs secrètes n'est versionné ; le .gitignore les exclut | 2, 6 |
| Le mot de passe de la base ne se trouve pas dans l'état : ressource éphémère et arguments en écriture seule | 5, 10 |
Les deux instances sont générées par for_each sur une table de zones | 9 |
La base et le bucket sont protégés contre une destruction accidentelle (prevent_destroy) | 8 |
Après apply, un nouveau plan ne propose aucun changement | 6, 11 |
Pistes
- Partez d'un répertoire vide et ajoutez les fichiers un par un (
versions.tf,providers.tf,variables.tf,reseau.tf...), en lançantterraform validateaprès chacun : les erreurs sont plus faciles à lire une à une. - Pour vérifier qu'aucun secret n'est dans l'état, lisez-le :
terraform state pull | jqet cherchez le mot de passe (leçon 6). - Le plan qui suit l'
applyest le meilleur test de votre configuration : un changement proposé sur un attribut que vous n'avez pas touché révèle un argument mal écrit ou une valeur par défaut du fournisseur à reprendre dans le code (leçon 11). - Essayez ensuite une dérive volontaire : modifiez une étiquette d'instance avec
scw, puis observezterraform plan -refresh-only.
Vérifier
Enregistrez le script sous verifier-lab.sh à la racine du dépôt, initialisez le répertoire pour la préproduction (terraform init -backend-config=backend-preprod.hcl), puis lancez bash verifier-lab.sh. Il ne modifie rien : il lit le dépôt, la configuration et l'état, et fait un plan.
#!/usr/bin/env bash
# Vérifie les propriétés attendues du lab « l'infrastructure de Signalements en code ».
# Usage : bash verifier-lab.sh (à la racine du dépôt, après terraform init pour la préproduction)
# Chaque contrôle affiche OK ou ÉCHEC ; le code de sortie est le nombre d'échecs.
set -uo pipefail
export TF_IN_AUTOMATION=1
TF=${TF:-terraform} # TF=tofu pour OpenTofu
echecs=0
controle() { # controle "libellé" commande...
local libelle=$1; shift
if "$@" > /dev/null 2>&1; then
echo "OK $libelle"
else
echo "ÉCHEC $libelle"
echecs=$((echecs + 1))
fi
}
controle "le code est formaté" "$TF" fmt -check -recursive
controle "la configuration est valide" "$TF" validate -no-color
controle "le fichier de verrouillage est versionné" git ls-files --error-unmatch .terraform.lock.hcl
controle "il couvre linux_amd64 et darwin_arm64" \
bash -c 'grep -q "\"zh:" .terraform.lock.hcl && [ "$(grep -c "\"h1:" .terraform.lock.hcl)" -ge 2 ]'
controle "le fournisseur Scaleway est épinglé par ~>" \
bash -c 'grep -rEq "source *= *\"scaleway/scaleway\"" --include=*.tf . && grep -rEq "version *= *\"~> *[0-9]" --include=*.tf .'
controle "aucune clé d'API dans les fichiers versionnés" \
bash -c '! git grep -nE "SCW[A-Z0-9]{17}|secret_key *= *\"[0-9a-f-]{36}\"" -- . ":!verifier-lab.sh"'
controle "aucun état, plan ou fichier d'environnement secret versionné" \
bash -c '[ -z "$(git ls-files | grep -E "\.tfstate(\.backup)?$|\.tfplan$|(^|/)\.terraform/")" ]'
controle "le .gitignore exclut l'état et .terraform/" \
bash -c 'grep -q "tfstate" .gitignore && grep -q "\.terraform" .gitignore'
controle "les deux fichiers de valeurs existent" \
test -f environnements/preprod.tfvars -a -f environnements/prod.tfvars
controle "les deux configurations partielles de backend existent" \
test -f backend-preprod.hcl -a -f backend-prod.hcl
controle "le backend est s3 avec use_lockfile" \
bash -c 'grep -rEq "backend +\"s3\"" --include=*.tf . && grep -rqE "use_lockfile *= *true" --include=*.tf --include=*.hcl .'
controle "chaque variable a un type et une description" \
bash -c 'awk "/^variable /{v=1;t=0;d=0} v&&/^ +type *=/{t=1} v&&/^ +description *=/{d=1} /^}/{if(v&&!(t&&d))bad++; if(v)n++; v=0} END{exit !(n>0 && bad==0)}" *.tf'
controle "au moins une variable a une validation" \
bash -c 'grep -rq "validation {" --include=*.tf .'
controle "les instances sont générées par for_each" \
bash -c 'grep -rEzq "resource \"scaleway_instance_server\"[^{]*\{[^}]*for_each" --include=*.tf .'
controle "la base et le bucket sont protégés par prevent_destroy" \
bash -c '[ "$(grep -rc "prevent_destroy *= *true" --include=*.tf . | awk -F: "{s+=\$2} END{print s}")" -ge 2 ]'
controle "un mot de passe en écriture seule est utilisé" \
bash -c 'grep -rqE "password_wo|data_wo" --include=*.tf .'
etat=$("$TF" state pull 2>/dev/null)
controle "l'état est lisible depuis le backend distant" test -n "$etat"
controle "aucun fichier d'état local ne traîne" test ! -e terraform.tfstate
controle "l'état ne contient aucun attribut de mot de passe renseigné" \
bash -c 'jq -e "[.. | objects | to_entries[] | select((.key | test(\"password\")) and (.value | type == \"string\") and (.value != \"\"))] | length == 0" <<< "$1"' _ "$etat"
controle "les deux instances sont dans l'état" \
bash -c 'jq -e "[.resources[] | select(.type == \"scaleway_instance_server\") | .instances[]] | length == 2" <<< "$1"' _ "$etat"
"$TF" plan -var-file=environnements/preprod.tfvars -detailed-exitcode -input=false -lock=false -no-color > /dev/null 2>&1
code=$?
controle "le plan ne propose aucun changement (code 0)" test "$code" -eq 0
echo
echo "$echecs échec(s)"
exit "$echecs"Quelques contrôles demandent une explication :
- Les empreintes du fichier de verrouillage :
terraform providers lock -platform=linux_amd64 -platform=darwin_arm64ajoute des empreintesh1:pour chaque plateforme ; le script en compte au moins deux. - La recherche de mot de passe dans l'état parcourt tous les objets de l'état et signale toute clé contenant
passwordavec une valeur non vide. Une ressource éphémère n'est jamais écrite dans l'état, et un argument en écriture seule y apparaît vide (leçons 5 et 10). - Le plan final utilise
-detailed-exitcode:0signifie aucun changement,2des changements,1une erreur.-lock=falseévite de prendre le verrou pour une simple lecture. - Les contrôles du code par expressions régulières sont volontairement simples : ils supposent un style proche de celui de
terraform fmtet des leçons.
Démonter
terraform destroy -var-file=environnements/preprod.tfvars échoue tant que prevent_destroy protège la base et le bucket : c'est voulu. Retirez ces protections dans une branche dédiée, appliquez, détruisez, puis supprimez le bucket d'état si vous n'en avez plus besoin, après avoir vérifié avec l'inventaire de la leçon 8 du cours cloud qu'il ne reste rien.