La supervision dans Git avec Terraform et la CI
Décrivez vos monitors, vos alertes et votre page de statut en code, faites relire chaque changement en merge request, et laissez la CI l’appliquer après la fusion. Ce tutoriel construit un projet complet de bout en bout ; le guide Terraform reste la référence de chaque attribut.
Ce que vous allez construire
Section intitulée « Ce que vous allez construire »Un dépôt qui surveille une petite boutique en ligne :
- trois monitors : le site, l’API de paiement et la base de données ;
- un canal Discord rattaché aux trois, et un groupe « Shop » ;
- une page de statut publique et une maintenance planifiée ;
- un pipeline qui affiche le
terraform plandans chaque merge request et l’applique après fusion surmain.
Les exemples de code sont en anglais, comme les noms qu’ils créent : remplacez-les par les vôtres.
Avant de commencer
Section intitulée « Avant de commencer »- L’offre Max, pour une clé API Complète (Paramètres → Clés API). Créez aussi une clé Lecture seule : elle suffit pour
plan. - Terraform 1.5 ou plus récent.
- Un projet GitLab. Une version GitHub Actions figure à la fin de l’étape 6.
1. Le dépôt
Section intitulée « 1. Le dépôt »okstatus/├── versions.tf # provider and backend├── monitors.tf # the three monitors├── alerting.tf # Discord channel and group├── status.tf # status page and maintenance├── variables.tf└── .gitlab-ci.ymlterraform { required_providers { okstatus = { source = "clearcloud-fr/okstatus", version = "~> 1.8" } } backend "http" {} # state kept by GitLab, configured by the CI (step 5)}
provider "okstatus" {} # the key comes from OKSTATUS_API_KEYvariable "db_password" { type = string sensitive = true}
variable "discord_webhook" { type = string sensitive = true}2. Les monitors
Section intitulée « 2. Les monitors »locals { regions = ["fr-dc3", "de-fra", "nl-ams"]}
resource "okstatus_monitor" "site" { name = "Shop · website" url = "https://shop.example.com" interval = 60 regions = local.regions keyword = "Add to cart"}
resource "okstatus_monitor" "payments" { name = "Shop · payment API" url = "https://api.shop.example.com/health" interval = 30 regions = local.regions latency_threshold_ms = 800 slo_target = 99.9}
resource "okstatus_monitor" "db" { name = "Shop · PostgreSQL" type = "postgres" url = "postgres://db.shop.example.com:5432/shop" db_query = "SELECT 1" basic_auth_user = "okstatus_ro" basic_auth_password = var.db_password regions = ["fr-dc3"]}Avec trois régions, une alerte part quand au moins deux d’entre elles voient le service en panne. Une seule région en échec ne réveille personne.
3. Canal d’alerte et groupe
Section intitulée « 3. Canal d’alerte et groupe »resource "okstatus_alert_channel" "discord" { type = "discord" config = { webhookUrl = var.discord_webhook }}
resource "okstatus_monitor_group" "shop" { name = "Shop" monitor_ids = [okstatus_monitor.site.id, okstatus_monitor.payments.id, okstatus_monitor.db.id] alert_channel_ids = [okstatus_alert_channel.discord.id]}Les monitors qui rejoignent le groupe reçoivent ses canaux. N’écrivez donc pas alert_channel_ids sur les monitors eux-mêmes : le prochain apply détacherait d’eux les canaux du groupe.
4. Page de statut et maintenance
Section intitulée « 4. Page de statut et maintenance »resource "okstatus_status_page" "public" { slug = "shop" title = "Shop status" custom_domain = "status.shop.example.com"}
resource "okstatus_maintenance" "pg_upgrade" { title = "PostgreSQL upgrade" scheduled_start = "2026-11-12T02:00:00Z" scheduled_end = "2026-11-12T03:00:00Z" monitor_ids = [okstatus_monitor.db.id]}5. L’état Terraform
Section intitulée « 5. L’état Terraform »GitLab conserve l’état et le verrouille pendant un apply : rien à héberger. Le bloc backend "http" {} de l’étape 1 reste vide ; la CI lui passe l’adresse au terraform init.
6. Le pipeline
Section intitulée « 6. Le pipeline »Dans Paramètres → CI/CD → Variables, créez :
| Variable | Valeur | Options |
|---|---|---|
OKSTATUS_API_KEY | la clé Lecture seule | masquée |
OKSTATUS_API_KEY_FULL | la clé Complète | masquée, protégée |
TF_VAR_db_password, TF_VAR_discord_webhook | les secrets | masquées, protégées |
Une variable protégée n’est lue que sur main. Les merge requests calculent leur plan avec la clé en lecture seule, et seule la branche principale peut modifier vos monitors.
image: name: hashicorp/terraform:1.9 entrypoint: [""]
variables: TF_ADDR: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/okstatus
before_script: - terraform init -input=false -backend-config="address=${TF_ADDR}" -backend-config="lock_address=${TF_ADDR}/lock" -backend-config="unlock_address=${TF_ADDR}/lock" -backend-config="username=gitlab-ci-token" -backend-config="password=${CI_JOB_TOKEN}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE"
stages: [plan, apply]
plan: stage: plan script: - terraform plan -input=false -out=plan.tfplan artifacts: paths: [plan.tfplan] rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" variables: # the real secrets are protected: placeholders for the review plan TF_VAR_db_password: merge-request-placeholder TF_VAR_discord_webhook: merge-request-placeholder - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
apply: stage: apply variables: OKSTATUS_API_KEY: $OKSTATUS_API_KEY_FULL script: - terraform apply -input=false plan.tfplan resource_group: okstatus # one apply at a time rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHLes secrets (TF_VAR_…) sont protégés : une merge request ne reçoit que des valeurs de remplacement. Son plan montre donc le mot de passe et le webhook comme modifiés ; c’est attendu, et jamais appliqué. Sur main, le job lit les vraies valeurs ; s’il en manque une, terraform plan s’arrête sur « No value for required variable » au lieu d’appliquer une valeur de remplacement.
Avec GitHub Actions
Section intitulée « Avec GitHub Actions »GitHub n’héberge pas d’état Terraform : gardez-le dans un bucket compatible S3, par exemple chez Scaleway Object Storage. Remplacez le backend de versions.tf :
backend "s3" { bucket = "acme-terraform-state" key = "okstatus/terraform.tfstate" region = "fr-par" endpoints = { s3 = "https://s3.fr-par.scw.cloud" } use_lockfile = true # Terraform 1.10 or later skip_credentials_validation = true skip_region_validation = true skip_requesting_account_id = true skip_s3_checksum = true }Ajoutez la clé en lecture seule comme secret du dépôt OKSTATUS_API_KEY. Créez un environnement okstatus (Settings → Environments), limité à main, qui détient OKSTATUS_API_KEY_FULL, DB_PASSWORD et DISCORD_WEBHOOK. Les clés du bucket (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) vont dans les secrets du dépôt.
name: okstatuson: pull_request: push: branches: [main]
env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
jobs: plan: if: github.event_name == 'pull_request' runs-on: ubuntu-latest env: OKSTATUS_API_KEY: ${{ secrets.OKSTATUS_API_KEY }} TF_VAR_db_password: pull-request-placeholder TF_VAR_discord_webhook: pull-request-placeholder steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - run: terraform init -input=false - run: terraform plan -input=false
apply: if: github.event_name == 'push' runs-on: ubuntu-latest environment: okstatus concurrency: okstatus env: OKSTATUS_API_KEY: ${{ secrets.OKSTATUS_API_KEY_FULL }} TF_VAR_db_password: ${{ secrets.DB_PASSWORD }} TF_VAR_discord_webhook: ${{ secrets.DISCORD_WEBHOOK }} steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - run: terraform init -input=false - run: terraform apply -input=false -auto-approveLes pièges
Section intitulée « Les pièges »- Une région hors de votre offre échoue à l’
apply, pas auplan(erreur 400). Vérifiez les régions de votre offre avant de fusionner. - Ce que vous n’écrivez pas n’est pas géré. Un tag ajouté dans le tableau de bord reste en place et Terraform ne le voit pas. Pour tout figer, écrivez l’attribut, même vide :
tags = []. - Les identifiants sont en écriture seule. Un mot de passe modifié dans le tableau de bord n’apparaît pas dans le plan : changez-le dans la variable de CI.
- Monitors existants : importez-les avec
terraform import okstatus_monitor.site <id>avant de les décrire, sinon vous créez des doublons.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Guide Terraform : tous les types de monitors, les agents privés, l’import.
- Budget d’erreur dans Grafana : suivre le
slo_targetdéfini ici. - Monitors heartbeat : surveiller les tâches cron du même projet.