GitOps și Terraform: de ce combinația este puternică

GitOps a transformat implementarea aplicației Kubernetes: Git devine sursa de adevăr, un controler reconciliază continuu starea dorită cu cea reală, fiecare modificare trece printr-o cerere de tragere. În 2026, aceeași paradigmă se instalează pentru infrastructura cloud gestionată cu Terraform, cu o diferență crucială față de CI/CD tradițional: în loc de un declanșator de apăsare și uitare, aveți un reconciliere continua care detectează și corectează automat derivele.

Problema fluxurilor de lucru tradiționale Terraform bazate pe GitHub Actions sau Atlantis e care sunt reactiv: Cineva face o modificare manuală pe consola AWS și nimeni știe asta până când următoarea conductă rulează. Cu GitOps pentru Terraform, fiecare discrepanța dintre codul HCL și starea reală devine o alertă - sau este corectată automat pe baza politicii configurate.

Ce vei învăța

  • Arhitectura GitOps pentru IaC: model pull vs model push
  • Controller Flux Terraform: instalare, CRD obiect Terraform și reconciliere
  • Gestionarea stării Terraform de la Kubernetes cu backend S3 și IRSA
  • Spacelift: stive, politici Rego, RBAC și fluxuri de lucru de aprobare
  • Detectare abateri: alertă Slack/PagerDuty pentru abateri neautorizate
  • Model pentru medii critice: remediere automată vs aprobare manuală

Pull Model vs Push Model pentru IaC

Distincția cheie dintre GitOps și CI/CD tradițional este modelul de sincronizare. În model push (GitHub Actions, Jenkins), conducta se declanșează la fiecare comitere și „împinge” modificări ale infrastructurii. În model de tragere (GitOps pur), un agent care rulează în interiorul clusterului „trage” continuu starea dorită din depozit si impaca. Această diferență are implicații profunde pentru securitate și reziliență:

# 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)

Controler Terraform Flux

Il Controler Terraform Flux (tf-controller) și un controler Kubernetes open-source care aduce Terraform în lumea GitOps. Este un proiect comunitar Flux (Weaveworks + întreținător independent) care extinde Flux cu capacitatea de a executa planifică și aplică Terraform ca bucle native de reconciliere Kubernetes.

Instalare

# 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

Configurarea GitRepository și Terraform CRD

Fluxul de lucru se bazează pe două obiecte Kubernetes: a GitRepository care indică depozit cu codul HCL și un obiect Terraform (CRD custom) care definește ce să împace.

# 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 pentru AWS Access de la Kubernetes

Cele mai bune practici pentru autentificarea AWS de la Kubernetes e IRSA (Roluri IAM pentru conturile de serviciu): podul Terraform primește un token JWT semnat de cluster care este schimbat cu acreditări temporare AWS, fără nicio cheie hardcoded în 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

Detectare și notificări de derive

Deriva apare atunci când starea reală a infrastructurii diferă de aceasta descrise în codul HCL — de obicei pentru modificări manuale pe consola cloud. Controlerul TF detectează deviația la fiecare ciclu de reconciliere și o raportează prin intermediul el Alert de 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 pentru Terraform

Lift spațial și o platformă SaaS atentă (cu opțiune de auto-găzduire). pentru echipele care rulează Terraform în medii de întreprindere. Spre deosebire de controlerul TF locuind în interiorul clusterului Kubernetes, Spacelift oferă o interfață de utilizare cuprinzătoare, politici avansate scris in Rego (același limbaj ca și OPA), RBAC granular e flux de lucru de aprobare cu pistă de audit completă.

Concepte cheie de transport spațial

# 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
}

Politica Rego în Spacelift

Politicile Rego sunt punctul forte al Spacelift: vă permit să definiți balustrade complexe care sunt evaluate pe fiecare plan înainte de a decide dacă să solicite aprobare, blocați sau aplicați automat. Și practic o poartă programabilă.

# 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]
  )
}

Aprobare Flux de lucru 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...

Detectare avansată a derivei: alertă și remediere automată

Detectarea deriva nu este suficienta daca nu este insotita de o strategie clara de raspuns. Există trei abordări, fiecare cu propriile compromisuri:

# 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

Comparație: TF Controller vs Spacelift vs Atlantis

Când să utilizați ce instrument

  • Controler Flux TF: Echipa care folosește deja Flux/Argo pentru Kubernetes, vrea GitOps pur și open-source, gestionează infrastructura AWS cu IRSA. Auto-găzduit, gratuit, curbă medie de învățare.
  • Spacelift: Echipa de întreprindere cu cerințe complexe RBAC, audit traseu, flux de lucru de aprobare cu mai mulți aprobatori, politici Rego avansate. SaaS plătit, UX excelent, integrări ieșite din cutie (Slack, PagerDuty, Jira).
  • Atlantida: Echipa care vrea să rămână în paradigma bazată pe PR fără GitOps pur. Planificați/Aplicați comentariu direct în PR. Auto-găzduit, gratuit, foarte matur. Nu are o reconciliere nativă continuă.
  • Terraform Cloud/Enterprise: Alegere naturală dacă deja se află în ecosistem HashiCorp, limbaj nativ de politică Sentinel, integrare Vault. Vezi articolul 10.

Cele mai bune practici pentru GitOps IaC în producție

# 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: Reconcilierea prea agresivă

Set interval: 1m cu approvePlan: auto pe medii producție și periculoasă: o schimbare încă necontopită în principal ar putea fi aplicat înainte de revizuire. Regula de aur: cu cât mediul este mai critic, cu atât mai mult iar intervalul este mai strict și procesul de aprobare. În prod, utilizați interval de 30 m+ și necesită întotdeauna aprobare manuală pentru modificări structurale.

Concluzii și pașii următori

GitOps pentru Terraform reprezintă maturitatea infrastructurii ca cod: nu mai conducte bazate pe declanșare, dar reconciliere continuă, fără acreditări în conducte dar identitățile native ale clusterului, nu mai „cine a făcut schimbarea”, ci pistele de audit complet în Git. Controlerul Flux TF este alegerea ideală pentru echipele native Kubernetes, în timp ce Spacelift îndeplinește cerințele întreprinderii cu motorul său de politici Rego.

Seria completă: Terraform și IaC

  • Articolul 01 — Terraform de la zero: HCL, Provider și Plan-Apply-Destroy
  • Articolul 02 — Proiectarea modulelor Terraform reutilizabile
  • Articolul 03 — Stare Terraform: Backend la distanță cu S3/GCS
  • Articolul 04 — Terraform în CI/CD: GitHub Actions și Atlantis
  • Articolul 05 — Testare IaC: Terratest și Terraform Test
  • Articolul 06 — Securitate IaC: Checkov, Trivy și OPA
  • Articolul 07 — Terraform Multi-Cloud: AWS + Azure + GCP
  • Articolul 08 (acest) — GitOps pentru Terraform: Controler Flux TF, Spacelift și Detecție de derive
  • Articolul 09 — Terraform vs Pulumi vs OpenTofu: Comparație 2026
  • Articolul 10 — Modele Terraform Enterprise: spațiu de lucru, Sentinel și scalarea echipei