Salta al contenuto principale

08 - GitOps per Terraform: Flux TF Controller, Spacelift e Drift Detection

Terraform IaC 16 min GitOps per Terraform: Flux TF Controller, Spacelift e Drift Detection Porta Terraform nel paradigma GitOps: Flux Terraform Controller...

Cosa ne pensi di questo articolo?

08 - GitOps per Terraform: Flux TF Controller, Spacelift e Drift Detection
Terraform IaCFoto:Danny MenesesPexels

In Breve

Terraform IaC 16 min GitOps per Terraform: Flux TF Controller, Spacelift e Drift Detection Porta Terraform nel paradigma GitOps: Flux Terraform Controller...

Terraform IaC 16 min

GitOps per Terraform: Flux TF Controller, Spacelift e Drift Detection

Porta Terraform nel paradigma GitOps: Flux Terraform Controller per la riconciliazione continua dallo stato del repository, Spacelift per policy e RBAC avanzati e alert di drift per ambienti critici.

Terraform GitOps Flux TF Controller Spacelift Drift Detection IaC Automation

GitOps e Terraform: Perche la Combinazione e Potente

GitOps ha trasformato il deployment delle applicazioni Kubernetes: Git diventa la fonte di verita, un controller riconcilia continuamente lo stato desiderato con quello reale, ogni modifica passa per una pull request. Nel 2026, lo stesso paradigma si sta affermando per l'infrastruttura cloud gestita con Terraform, con una differenza cruciale rispetto al CI/CD tradizionale: invece di un trigger "push-and-forget", hai una riconciliazione continua che rileva e corregge i drift automaticamente.

Il problema con i workflow Terraform tradizionali basati su GitHub Actions o Atlantis e che sono reattivi: qualcuno fa una modifica manuale sulla console AWS, e nessuno lo sa finche non viene eseguita la prossima pipeline. Con GitOps per Terraform, ogni discrepanza tra il codice HCL e lo stato reale diventa un alert - o viene corretta automaticamente in base alla policy configurata.

Cosa Imparerai

  • Architettura GitOps per IaC: pull model vs push model
  • Flux Terraform Controller: installazione, Terraform object CRD e riconciliazione
  • Gestione del state Terraform da Kubernetes con backend S3 e IRSA
  • Spacelift: stacks, policies Rego, RBAC e approval workflows
  • Drift detection: alert Slack/PagerDuty per deviazioni non autorizzate
  • Pattern per ambienti critici: auto-remediation vs manual approval

Pull Model vs Push Model per IaC

La distinzione fondamentale tra GitOps e CI/CD tradizionale e il modello di sincronizzazione. Nel push model (GitHub Actions, Jenkins), la pipeline si attiva su ogni commit e "spinge" i cambiamenti verso l'infrastruttura. Nel pull model (GitOps puro), un agente che gira all'interno del cluster "tira" continuamente lo stato desiderato dal repository e riconcilia. Questa differenza ha implicazioni profonde per la sicurezza e la resilienza:

# Push Model (GitHub Actions) - richiede credenziali cloud nella pipeline
# Il runner GitHub deve avere accesso outbound al cloud provider
# Problem: se il job fallisce a meta, lo state puo essere inconsistente

# Pull Model (Flux TF Controller) - l'agente vive dentro il cluster
# Solo il cluster Kubernetes ha le credenziali cloud (via IRSA o Workload Identity)
# Vantaggio: single point of trust, nessuna credenziale nelle GitHub Secrets
# Vantaggio: riconciliazione continua ogni N minuti, non solo su commit

# Confronto security:
# Push Model: GitHub runner --[credenziali]--> AWS/Azure/GCP
# Pull Model: Kubernetes pod -[IRSA/WI]--> AWS/Azure/GCP
#             Git repository -[SSH/HTTPS]--> Flux controller (dentro cluster)

Flux Terraform Controller

Il Flux Terraform Controller (tf-controller) e un controller Kubernetes open-source che porta Terraform nel mondo GitOps. E un progetto della community Flux (Weaveworks + maintainer indipendenti) che estende Flux con la capacita di eseguire piani e apply Terraform come reconciliation loop nativi di Kubernetes.

Installazione

# Prerequisiti: cluster Kubernetes + Flux installato
# Installa Flux sul cluster (se non presente)
flux install

# Installa il TF Controller tramite HelmRelease
cat <<'EOF' | kubectl apply -f -
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: tf-controller
  namespace: flux-system
spec:
  interval: 1h
  url: https://weaveworks.github.io/tf-controller
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: tf-controller
  namespace: flux-system
spec:
  interval: 1h
  chart:
    spec:
      chart: tf-controller
      version: "0.16.x"
      sourceRef:
        kind: HelmRepository
        name: tf-controller
        namespace: flux-system
  values:
    replicaCount: 1
    resources:
      limits:
        cpu: "1"
        memory: 1Gi
      requests:
        cpu: 200m
        memory: 512Mi
    # Runner pods: eseguono il processo terraform effettivo
    runner:
      image:
        tag: "v1.5.x-flux"
EOF

# Verifica installazione
kubectl get pods -n flux-system | grep tf-controller
# NAME                                          READY   STATUS    RESTARTS
# tf-controller-6d8f9b4b5-xn7q2               1/1     Running   0

Configurazione GitRepository e Terraform CRD

Il workflow si basa su due oggetti Kubernetes: un GitRepository che punta al repository con il codice HCL, e un oggetto Terraform (CRD custom) che definisce cosa riconciliare.

# 1. GitRepository: sorgente del codice HCL
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
  name: infra-repo
  namespace: flux-system
spec:
  interval: 1m          # Controlla il repo ogni minuto
  url: https://github.com/myorg/terraform-infra
  ref:
    branch: main
  secretRef:
    name: github-ssh-key  # Secret con chiave SSH o token

---
# 2. Terraform CRD: definisce il modulo da riconciliare
apiVersion: infra.contrib.fluxcd.io/v1alpha2
kind: Terraform
metadata:
  name: aws-networking
  namespace: flux-system
spec:
  # Intervallo di riconciliazione
  interval: 10m

  # Sorgente HCL
  sourceRef:
    kind: GitRepository
    name: infra-repo
  path: ./environments/prod/networking   # Path nel repo

  # Approvazione automatica (auto-apply) o manuale
  approvePlan: auto

  # Gestione del drift: se lo stato reale differisce dal desired
  # force: riconcilia automaticamente
  # drift: solo alert, non corregge
  enableInventory: true

  # Backend per lo state (S3 con IRSA)
  backendConfig:
    customConfiguration: |
      backend "s3" {
        bucket         = "myorg-terraform-state-prod"
        key            = "networking/terraform.tfstate"
        region         = "eu-west-1"
        dynamodb_table = "terraform-state-lock"
        encrypt        = true
      }

  # Variabili passate al modulo
  vars:
    - name: environment
      value: prod
    - name: aws_region
      value: eu-west-1

  # Variabili da Secret Kubernetes (per segreti)
  varsFrom:
    - kind: Secret
      name: terraform-vars-prod
      varsKeys:
        - db_password
        - api_key

IRSA per Accesso AWS da Kubernetes

La best practice per l'autenticazione AWS da Kubernetes e IRSA (IAM Roles for Service Accounts): il pod Terraform riceve un token JWT firmato dal cluster che viene scambiato con credenziali AWS temporanee, senza nessuna chiave hardcoded nel cluster.

# Crea il Service Account con annotazione IRSA
kubectl create serviceaccount tf-runner -n flux-system

kubectl annotate serviceaccount tf-runner \
  -n flux-system \
  eks.amazonaws.com/role-arn=arn:aws:iam::123456789:role/TerraformRunnerRole

# IAM Role Trust Policy (da configurare su AWS):
# {
#   "Version": "2012-10-17",
#   "Statement": [{
#     "Effect": "Allow",
#     "Principal": {
#       "Federated": "arn:aws:iam::123456789:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/..."
#     },
#     "Action": "sts:AssumeRoleWithWebIdentity",
#     "Condition": {
#       "StringEquals": {
#         "oidc.eks.eu-west-1.amazonaws.com/...:sub":
#           "system:serviceaccount:flux-system:tf-runner"
#       }
#     }
#   }]
# }

# Aggiorna il CRD Terraform per usare il Service Account
# Aggiungi nella spec:
# serviceAccountName: tf-runner

Drift Detection e Notifiche

Il drift si verifica quando lo stato reale dell'infrastruttura differisce da quello descritto nel codice HCL - solitamente per modifiche manuali sulla console cloud. Il TF Controller rileva il drift a ogni ciclo di riconciliazione e lo notifica tramite gli Alert di Flux.

# Alert Flux per notifiche Slack sul drift
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
  name: slack-infra
  namespace: flux-system
spec:
  type: slack
  channel: "#infra-alerts"
  secretRef:
    name: slack-webhook-url

---
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
  name: terraform-drift-alert
  namespace: flux-system
spec:
  providerRef:
    name: slack-infra
  eventSeverity: warning
  eventSources:
    - kind: Terraform
      name: "*"   # Tutti gli oggetti Terraform
  # Invia alert per questi eventi:
  # - drift detected
  # - reconciliation failed
  # - plan pending approval
# Verificare lo stato di drift manualmente
kubectl get terraform -n flux-system
# NAME              READY   STATUS                          AGE
# aws-networking    True    Reconciliation succeeded        2h
# aws-database      False   Drift detected: 3 resources     15m

# Dettaglio del drift
kubectl describe terraform aws-database -n flux-system | grep -A 20 "Conditions:"
# Conditions:
#   Last Transition Time:  2026-03-20T10:30:00Z
#   Message:               Drift detected: aws_db_instance.main (tags changed),
#                           aws_security_group.db (ingress rule added manually)
#   Reason:                TerraformOutputsWritten
#   Status:                False
#   Type:                  Ready

Spacelift: GitOps Enterprise per Terraform

Spacelift e una piattaforma SaaS (con opzione self-hosted) pensata per team che gestiscono Terraform in ambienti enterprise. A differenza del TF Controller che vive dentro il cluster Kubernetes, Spacelift offre una UI completa, policy avanzate scritte in Rego (lo stesso linguaggio di OPA), RBAC granulare e approval workflow con audit trail completo.

Concetti Chiave di Spacelift

# Struttura Spacelift
# Stack = equivalente di un workspace Terraform
# Ogni stack ha:
# - Source: GitHub/GitLab repository + branch + path
# - Runner image: immagine Docker con Terraform + provider
# - Environment variables: variabili e segreti
# - Policies: regole Rego applicate a plan/apply
# - Contexts: set di variabili condivisibili tra stack

# Creare uno stack via Spacelift API (Terraform provider spacelift):
resource "spacelift_stack" "networking_prod" {
  name        = "networking-prod"
  repository  = "terraform-infra"
  branch      = "main"
  project_root = "environments/prod/networking"

  # Auto-deploy su push al branch
  autodeploy = false   # Per prod: richiede approvazione manuale

  # Terraform version
  terraform_version = "1.9.x"

  labels = ["team:platform", "env:prod", "tier:networking"]
}

resource "spacelift_context_attachment" "networking_prod" {
  context_id = spacelift_context.aws_prod.id
  stack_id   = spacelift_stack.networking_prod.id
  priority   = 1
}

Policy Rego in Spacelift

Le policy Rego sono il punto di forza di Spacelift: permettono di definire guardrail complessi che vengono valutati su ogni piano prima di decidere se richiedere approvazione, bloccare, o fare auto-apply. E fondamentalmente un gate programmabile.

# policy: require-approval-for-destructive-changes.rego
# Richiede approvazione umana se il plan contiene distruzioni

package spacelift

# Nega auto-apply se ci sono risorse da distruggere
deny[sprintf("Destroy richiede approvazione: %s", [resource])] {
  change := input.terraform.resource_changes[_]
  change.change.actions[_] == "delete"
  resource := change.address
}

# Blocca completamente se piu di 5 risorse vengono distrutte
deny["Piu di 5 destroy in un singolo plan: richiede approvazione senior"] {
  destroy_count := count([c |
    c := input.terraform.resource_changes[_]
    c.change.actions[_] == "delete"
  ])
  destroy_count > 5
}

# Warn (non blocca) per modifiche ai security group
warn[sprintf("Security group modificato: %s", [resource])] {
  change := input.terraform.resource_changes[_]
  change.type == "aws_security_group"
  change.change.actions[_] != "no-op"
  resource := change.address
}
# policy: cost-control.rego
# Blocca istanze grandi in ambienti non-prod

package spacelift

expensive_instance_types := {
  "m5.4xlarge", "m5.8xlarge", "m5.16xlarge",
  "c5.4xlarge", "c5.9xlarge",
  "r5.4xlarge", "r5.8xlarge"
}

deny[msg] {
  # Leggi i tag dallo stack Spacelift
  not contains(input.spacelift.stack.labels[_], "env:prod")

  # Cerca istanze EC2 con instance_type costoso
  change := input.terraform.resource_changes[_]
  change.type == "aws_instance"
  instance_type := change.change.after.instance_type
  expensive_instance_types[instance_type]

  msg := sprintf(
    "Istanza %s di tipo %s non consentita in ambienti non-prod",
    [change.address, instance_type]
  )
}

Approval Workflow Spacelift

# Spacelift approval workflow con notifiche Slack

# 1. Developer fa push al branch feature/add-rds
# 2. Spacelift crea automaticamente un preview run
# 3. La policy Rego valuta il plan: contiene 1 destroy (vecchio RDS)
# 4. Spacelift blocca l'auto-deploy e notifica Slack
#    "Run #abc123 richiede approvazione: destroy aws_db_instance.old_db"
# 5. Senior engineer esamina il plan su Spacelift UI
# 6. Approva cliccando "Confirm" oppure aggiunge commento e rifiuta
# 7. Spacelift esegue l'apply o notifica il developer del blocco

# Via Spacelift CLI (spacectl):
spacectl stack run list --id networking-prod
# ID        COMMIT    STATE           CREATED AT
# abc123    f3a8b91   PENDING_REVIEW  2026-03-20 10:30
# xyz789    a1c2d3e   FINISHED        2026-03-19 14:22

spacectl run confirm --run abc123 --stack networking-prod
# Run abc123 confirmed, applying...

Drift Detection Avanzata: Alert e Auto-Remediation

La drift detection non basta se non e accompagnata da una chiara strategia di risposta. Ci sono tre approcci, ognuno con i propri trade-off:

# Approccio 1: Solo Alert (ambienti critici, audit trail necessario)
# Il drift viene rilevato e segnalato, ma non corretto automaticamente
# Uso: database di produzione, networking critico

# Approccio 2: Auto-Remediation per drift minore
# Modifiche ai tag, aggiornamenti di patch: correggi automaticamente
# Blocca e avvisa per modifiche strutturali

# Approccio 3: Full Auto-Apply (ambienti dev/staging)
# Qualsiasi drift viene corretto immediatamente dal controller

---
# Esempio Flux TF Controller: configurazione per approccio ibrido
apiVersion: infra.contrib.fluxcd.io/v1alpha2
kind: Terraform
metadata:
  name: aws-networking-prod
  namespace: flux-system
spec:
  interval: 5m
  approvePlan: "auto"    # "auto" per ambienti non critici

  # Plan runner: genera il piano ma NON lo applica
  # L'apply richiede un secondo passaggio (manuale o automatico)
  planOnly: false

  # Dopo quanti drift consecutivi inviare un alert critico
  # (configurato via Flux Alert con severita error)
  retryInterval: 1m
  timeout: 5m
# Script di scheduled drift check (alternativa leggera senza GitOps controller)
#!/bin/bash
# drift-check.sh - eseguito ogni ora via cron o GitHub Actions scheduled

set -euo pipefail

ENVIRONMENTS=("dev" "staging" "prod")
SLACK_WEBHOOK="${SLACK_DRIFT_WEBHOOK}"

for ENV in "${ENVIRONMENTS[@]}"; do
  cd "/infra/environments/${ENV}"

  # Inizializza senza output
  terraform init -reconfigure -input=false -no-color > /dev/null 2>&1

  # Esegui plan e cattura l'exit code
  # 0 = no changes, 1 = error, 2 = changes detected (drift)
  set +e
  terraform plan -detailed-exitcode -no-color -out=/tmp/plan-${ENV} 2>&1
  EXITCODE=$?
  set -e

  if [ $EXITCODE -eq 2 ]; then
    CHANGES=$(terraform show -no-color /tmp/plan-${ENV} | \
      grep -E "^\s+(#|~|\+|-)" | head -20)

    curl -s -X POST "$SLACK_WEBHOOK" \
      -H "Content-Type: application/json" \
      -d "{
        \"text\": \"*DRIFT DETECTED* in environment: ${ENV}\n\`\`\`${CHANGES}\`\`\`\"
      }"
    echo "Drift alert sent for ${ENV}"
  elif [ $EXITCODE -eq 0 ]; then
    echo "${ENV}: no drift detected"
  else
    echo "ERROR: terraform plan failed for ${ENV}" >&2
    exit 1
  fi
done

Confronto: TF Controller vs Spacelift vs Atlantis

Quando Usare Quale Strumento

  • Flux TF Controller: Team che usa gia Flux/Argo per Kubernetes, vuole GitOps puro e open-source, gestisce infrastruttura AWS con IRSA. Self-hosted, gratis, curva di apprendimento media.
  • Spacelift: Team enterprise con requisiti RBAC complessi, audit trail, approval workflow con piu approvatori, policy avanzate Rego. SaaS a pagamento, ottima UX, integrazioni out-of-the-box (Slack, PagerDuty, Jira).
  • Atlantis: Team che vuole rimanere nel paradigma PR-based senza GitOps puro. Piano/Apply commentabili direttamente nella PR. Self-hosted, gratis, molto maturo. Non ha riconciliazione continua nativa.
  • Terraform Cloud/Enterprise: Scelta naturale se gia nell'ecosistema HashiCorp, Sentinel policy language nativo, integrazione con Vault. Vedi Articolo 10.

Best Practices per GitOps IaC in Produzione

# Repository structure per GitOps Terraform
terraform-infra/
├── modules/                    # Moduli riusabili (non riconciliati direttamente)
│   ├── networking/
│   ├── compute/
│   └── database/
├── environments/
│   ├── dev/
│   │   ├── networking/         # Stack separati per ogni layer
│   │   │   ├── main.tf
│   │   │   └── terraform.auto.tfvars
│   │   ├── compute/
│   │   └── database/
│   ├── staging/
│   └── prod/
│       ├── networking/         # Ogni ambiente ha il suo state isolato
│       ├── compute/
│       └── database/
├── flux/                       # Manifesti Flux per i CRD Terraform
│   ├── dev/
│   │   ├── networking-tf.yaml
│   │   └── compute-tf.yaml
│   └── prod/
│       ├── networking-tf.yaml  # approvePlan: "auto" o manuale
│       └── compute-tf.yaml
└── policies/                   # Policy Rego (se Spacelift)
    ├── require-approval.rego
    └── cost-control.rego

Anti-Pattern: Riconciliazione Troppo Aggressiva

Impostare interval: 1m con approvePlan: auto su ambienti di produzione e pericoloso: una modifica non ancora mergiata in main potrebbe essere applicata prima della review. La regola d'oro: piu l'ambiente e critico, piu lungo e l'intervallo e piu stringente e il processo di approvazione. In prod, usa interval di 30m+ e richiedi sempre approvazione manuale per le modifiche strutturali.

Conclusioni e Prossimi Passi

GitOps per Terraform rappresenta la maturita dell'Infrastructure as Code: non piu pipeline trigger-based ma riconciliazione continua, non piu credenziali nelle pipeline ma identita native del cluster, non piu "chi ha fatto quella modifica" ma audit trail completo in Git. Il Flux TF Controller e la scelta ideale per team Kubernetes-native, mentre Spacelift soddisfa i requisiti enterprise con la sua policy engine Rego.

La Serie Completa: Terraform e IaC

  • Articolo 01 - Terraform da Zero: HCL, Provider e Plan-Apply-Destroy
  • Articolo 02 - Progettare Moduli Terraform Riusabili
  • Articolo 03 - Terraform State: Backend Remoto con S3/GCS
  • Articolo 04 - Terraform in CI/CD: GitHub Actions e Atlantis
  • Articolo 05 - Testing IaC: Terratest e Terraform Test
  • Articolo 06 - Sicurezza IaC: Checkov, Trivy e OPA
  • Articolo 07 - Terraform Multi-Cloud: AWS + Azure + GCP
  • Articolo 08 (questo) - GitOps per Terraform: Flux TF Controller, Spacelift e Drift Detection
  • Articolo 09 - Terraform vs Pulumi vs OpenTofu: Confronto 2026
  • Articolo 10 - Terraform Enterprise Patterns: Workspace, Sentinel e Team Scaling

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti