Aller au contenu

Lab : l'infrastructure de Signalements en code

200 Pratiquer ⏱ 6 h terraformopentofuscalewayiac

Prérequis

Testé avec scaleway 2.84.0 terraform 1.16.1 , vérifié le 5 octobre 2026

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 CLI scw configuré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

ExigenceLeçon
terraform fmt -check -recursive et terraform validate réussissent2, 3, 12
Le fichier .terraform.lock.hcl est versionné, et contient les empreintes de linux_amd64 et darwin_arm644
Le fournisseur Scaleway est épinglé par une contrainte ~> ; aucune clé d'API n'apparaît dans le code4
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 validation5
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 exclut2, 6
Le mot de passe de la base ne se trouve pas dans l'état : ressource éphémère et arguments en écriture seule5, 10
Les deux instances sont générées par for_each sur une table de zones9
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 changement6, 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çant terraform validate aprè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 | jq et cherchez le mot de passe (leçon 6).
  • Le plan qui suit l'apply est 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 observez terraform 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_arm64 ajoute des empreintes h1: 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 password avec 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 : 0 signifie aucun changement, 2 des changements, 1 une 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 fmt et 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.

Plan du cours