Terraform — Intégration CI/CD
| Fiche express | |
|---|---|
| Type | Intégration de Terraform dans un pipeline CI/CD |
| Motif type | plan auto en MR (commenté) · apply manuel/gated sur branche principale |
| Credentials | OIDC / identité fédérée, pas de clé statique |
| Point critique | verrouillage du state (pas de plan/apply concurrents) |
| Voir aussi | GitLab CI/CD · Terraform — Concepts fondamentaux · Terraform — Tests et validation |
Faire tourner Terraform depuis un poste de travail individuel ne passe pas à l'échelle en équipe : chacun a une version différente du binaire, du state local ou d'identifiants divergents, et rien n'empêche deux personnes de lancer un apply en même temps. Cette page décrit le motif standard d'intégration dans une CI, avec un exemple concret sur GitLab CI/CD.
Motif type : plan en MR, apply gated sur la branche principale
<mermaid> flowchart TD
MR[Merge request] -->|pipeline auto| P[terraform plan]
P -->|commentaire du diff| REVIEW[Revue humaine]
REVIEW -->|fusion sur main| M[Merge sur branche principale]
M -->|pipeline auto| P2[terraform plan]
P2 --> G{Gate manuel}
G -->|approbation| A[terraform apply]
A --> INFRA[(Infrastructure)]
</mermaid>
- Sur la merge request — un job
terraform plans'exécute automatiquement à chaque push, et son résultat (le diff create/update/destroy) est posté en commentaire sur la MR — la revue porte alors autant sur le code HCL que sur son effet réel avant fusion. - Sur la branche principale — après fusion, un nouveau
planpuis unapplymanuel (déclenché explicitement, avec approbation) ou gated (environnement protégé nécessitant une validation) exécute réellement les changements. L'applyn'est jamais automatique sur la branche principale en production : un humain valide le diff final juste avant l'exécution réelle.
Exemple de pipeline GitLab CI
stages:
- validate
- plan
- apply
variables:
TF_ROOT: infra/
TF_STATE_NAME: production
default:
image: hashicorp/terraform:1.9
before_script:
- cd $TF_ROOT
- terraform init
validate:
stage: validate
script:
- terraform fmt -check -recursive
- terraform validate
plan:
stage: plan
script:
- terraform plan -out=plan.tfplan
- terraform show -no-color plan.tfplan > plan.txt
artifacts:
paths:
- $TF_ROOT/plan.tfplan
- $TF_ROOT/plan.txt
expire_in: 1 day
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
apply:
stage: apply
script:
- terraform apply plan.tfplan
dependencies:
- plan
environment:
name: production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
Points clés de cet exemple :
- le job
plantourne à la fois sur les merge requests (pour la revue) et surmain(juste avant l'apply) — le plan exécuté juste avant l'applydoit toujours être frais, pas celui de la MR (dérive possible entre-temps) ; terraform apply plan.tfplanrejoue le plan sauvegardé plutôt qu'un nouveau plan implicite, pour garantir que ce qui est appliqué est exactement ce qui a été revu ;when: manualcombiné àenvironment: productionmatérialise le gate : le job apparaît dans le pipeline mais attend un déclenchement humain explicite dans l'interface GitLab.
Pour poster le diff du plan en commentaire de merge request, GitLab CI/CD propose un rapport dédié (artifacts: reports: terraform, à vérifier selon version) qui affiche le plan formaté directement dans l'onglet Modifications de la MR, plutôt qu'un simple artefact téléchargeable.
Credentials cloud en CI : OIDC plutôt que clés statiques
Le pipeline doit s'authentifier auprès du provider cloud (AWS, Azure, GCP) pour exécuter plan/apply. Deux approches :
- Clé statique en variable protégée CI —
AWS_ACCESS_KEY_ID, secret de service principal Azure, clé JSON GCP stockés comme variables CI masquées/protégées. Fonctionne, mais reste un secret de longue durée à faire tourner, révoquer, et qui fuit potentiellement dans les logs si mal masqué. - OIDC (fédération d'identité) — le runner CI présente un jeton OIDC signé par GitLab (ou GitHub) ; le cloud (rôle IAM AWS, identité fédérée Azure/GCP) vérifie ce jeton et délivre des identifiants temporaires, sans aucun secret stocké côté CI. C'est la méthode recommandée, cohérente avec l'authentification par rôle/identité déjà privilégiée sur les pages providers.
apply:
stage: apply
id_tokens:
AWS_ID_TOKEN:
aud: https://gitlab.com
script:
- export AWS_ROLE_ARN=arn:aws:iam::123456789012:role/terraform-ci
- export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/token.txt
- echo "$AWS_ID_TOKEN" > token.txt
- terraform apply plan.tfplan
environment:
name: production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
Variables protégées et masquées restent nécessaires même en OIDC pour tout ce qui ne relève pas de l'identité cloud (jeton de registre privé, notification Slack) : les réserver aux branches/environnements protégés (protected: true) évite qu'un pipeline sur une branche de feature quelconque n'y accède.
Verrouillage du state en pipeline
Le verrouillage du state reste indispensable en CI, où plusieurs pipelines peuvent en théorie s'exécuter en parallèle (deux merge requests plannées au même moment, un rerun manuel pendant qu'un apply est en cours). Le backend distant (S3+DynamoDB, Storage Account, GCS — voir les pages providers) pose le verrou pendant toute la durée du plan/apply : un second job qui tente d'y accéder échoue proprement plutôt que de corrompre le state.
Bonnes pratiques complémentaires côté pipeline :
resource_groupGitLab CI (mot-cléresource_group:sur le jobapply) — empêche GitLab de lancer deux instances du même job en parallèle, en file d'attente plutôt qu'en concurrence, en complément du verrou du backend ;- limiter le déclenchement du job
applyà la seule branche principale (comme dans l'exemple ci-dessus) pour éviter lesapplyaccidentels depuis une branche de feature ; - un plan sauvegardé (
plan.tfplan) a une durée de vie courte : s'il est rejoué trop tard, Terraform détecte un état devenu incohérent et refuse de l'appliquer tel quel plutôt que de forcer unapplyobsolète.
apply:
stage: apply
resource_group: production-tfstate
script:
- terraform apply plan.tfplan
Voir aussi
- GitLab CI/CD — modèle général de pipeline, runners,
rules/needs - Terraform — Concepts fondamentaux — state, backend, verrouillage
- Terraform — Tests et validation — pipeline de qualité fmt → validate → lint → security scan → test
- Terraform — Provider AWS · Terraform — Provider Azure · Terraform — Provider GCP