GitOps pentru Terraform: Controler Flux TF, Spacelift și Detecție Drift
Aduceți Terraform în paradigma GitOps: Flux Terraform Controller pentru reconciliere continuă de la starea depozitului, Spacelift pentru politici avansate și RBAC și alerte deriva pentru medii critice.
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







