Terraform용 GitOps: Flux TF 컨트롤러, Spacelift 및 드리프트 감지
GitOps 패러다임에 Terraform 도입: 조정을 위한 Flux Terraform 컨트롤러 저장소 상태, 고급 정책을 위한 Spacelift, RBAC 및 경고에서 계속됩니다. 중요한 환경을 위한 드리프트.
GitOps와 Terraform: 결합이 강력한 이유
GitOps는 Kubernetes 애플리케이션 배포를 변환했습니다. Git이 소스가 됩니다. 실제로 컨트롤러는 원하는 상태와 실제 상태를 지속적으로 조정합니다. 모든 변경 사항은 풀 요청(Pull Request)을 거칩니다. 2026년에도 같은 패러다임이 자리잡는다 Terraform으로 관리되는 클라우드 인프라의 경우 기존 CI/CD: 푸시하고 잊어버리는 트리거 대신 화해 계속하다 드리프트를 자동으로 감지하고 수정합니다.
GitHub Actions 또는 Atlantis e를 기반으로 하는 기존 Terraform 워크플로의 문제 나는 누구인가 반응성: AWS 콘솔에서 누군가가 수동으로 변경하고, 누구도 변경하지 않습니다. 다음 파이프라인이 실행될 때까지 이를 알고 있습니다. Terraform용 GitOps를 사용하면 모든 HCL 코드와 실제 상태 간의 불일치는 경고가 되거나 수정됩니다. 구성된 정책에 따라 자동으로.
무엇을 배울 것인가
- IaC용 GitOps 아키텍처: 풀 모델과 푸시 모델
- Flux Terraform 컨트롤러: 설치, Terraform 객체 CRD 및 조정
- S3 및 IRSA 백엔드를 사용하는 Kubernetes의 Terraform 상태 관리
- Spacelift: 스택, Rego 정책, RBAC 및 승인 워크플로
- 드리프트 감지: 무단 편차에 대한 Slack/PagerDuty 경고
- 중요한 환경의 패턴: 자동 수정 및 수동 승인
IaC용 풀 모델과 푸시 모델 비교
GitOps와 기존 CI/CD의 주요 차이점은 동기화 모델입니다. 에서 푸시 모델 (GitHub Actions, Jenkins), 파이프라인은 모든 커밋에서 실행됩니다. 인프라에 대한 변경 사항을 "푸시"합니다. 에서 풀 모델 (순수 GitOps), 클러스터 내부에서 실행되는 에이전트는 저장소에서 원하는 상태를 지속적으로 "가져옵니다". 그리고 화해하세요. 이러한 차이는 보안과 복원력에 중대한 영향을 미칩니다.
# 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 컨트롤러
Il Flux Terraform 컨트롤러 (tf-controller) 및 Kubernetes 컨트롤러 Terraform을 GitOps 세계로 가져오는 오픈 소스입니다. Flux 커뮤니티 프로젝트입니다. (Weaveworks + 독립 유지관리자) Flux를 실행 기능으로 확장합니다. Terraform을 기본 Kubernetes 조정 루프로 계획하고 적용합니다.
설치
# 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
GitRepository 및 Terraform CRD 구성
워크플로우는 두 개의 Kubernetes 객체를 기반으로 합니다. GitRepository 이는 다음을 가리킨다
HCL 코드가 있는 저장소 및 객체 Terraform (CRD 사용자 정의)
무엇을 화해해야 할까요?
# 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
Kubernetes에서 AWS 액세스를 위한 IRSA
Kubernetes e의 AWS 인증 모범 사례 IRSA (서비스 계정에 대한 IAM 역할): Terraform 포드는 키 없이 임시 AWS 자격 증명으로 교환되는 클러스터 클러스터에 하드코딩되었습니다.
# 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
드리프트 감지 및 알림
드리프트는 인프라의 실제 상태가 그것과 다를 때 발생합니다.
HCL 코드에 설명되어 있습니다. 일반적으로 클라우드 콘솔에서 수동으로 변경하는 경우입니다.
TF 컨트롤러는 각 조정 주기에서 드리프트를 감지하고 이를 다음을 통해 보고합니다.
그 사람 Alert 플럭스로.
# 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: Terraform용 GitOps Enterprise
우주 리프트 그리고 사려 깊은 SaaS 플랫폼(자체 호스팅 옵션 포함)입니다. 엔터프라이즈 환경에서 Terraform을 실행하는 팀을 위한 것입니다. TF 컨트롤러와 달리 Kubernetes 클러스터 내에 있는 Spacelift는 포괄적인 UI, 고급 정책을 제공합니다. 에 쓰여진 레고 (OPA와 동일한 언어), 세분화된 RBAC e 완전한 감사 추적이 가능한 승인 워크플로.
주요 우주 리프트 개념
# 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
}
Spacelift의 정책 레고
Rego 정책은 Spacelift의 장점입니다. 이를 통해 가드레일을 정의할 수 있습니다. 각 계획에 대해 평가를 거쳐 승인요청 여부를 결정하는 단지, 차단하거나 자동으로 적용하세요. 기본적으로 프로그래밍 가능한 게이트입니다.
# 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]
)
}
승인 워크플로우 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...
고급 드리프트 감지: 경고 및 자동 해결
명확한 대응 전략이 수반되지 않으면 드리프트 감지만으로는 충분하지 않습니다. 세 가지 접근 방식이 있으며 각각 장단점이 있습니다.
# 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
비교: TF 컨트롤러 vs Spacelift vs Atlantis
언제 어떤 도구를 사용해야 하는가
- 플럭스 TF 컨트롤러: 이미 Kubernetes용 Flux/Argo를 사용하고 있는 팀, 순수한 오픈 소스 GitOps를 원하고 IRSA를 통해 AWS 인프라를 관리합니다. 자체 호스팅, 무료, 중간 학습 곡선.
- 우주 리프트: 복잡한 RBAC 요구 사항, 감사를 갖춘 엔터프라이즈 팀 추적, 여러 승인자가 있는 승인 워크플로, 고급 Rego 정책. 유료 SaaS, 뛰어난 UX, 즉시 사용 가능한 통합(Slack, PagerDuty, Jira).
- 아틀란티스: PR 기반의 패러다임을 유지하고 싶은 팀 순수한 GitOps. PR에서 직접 댓글을 달 수 있도록 계획/적용합니다. 자체 호스팅, 무료, 매우 성숙했습니다. 기본 지속적 조정이 없습니다.
- Terraform 클라우드/엔터프라이즈: 이미 생태계에 있다면 자연 선택 HashiCorp, 기본 Sentinel 정책 언어, Vault 통합. 제10조를 참조하세요.
프로덕션 환경의 GitOps IaC 모범 사례
# 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
안티 패턴: 조정이 너무 공격적임
세트 interval: 1m ~와 함께 approvePlan: auto 환경에
생산적이고 위험함: 아직 메인에 병합되지 않은 변경 사항은
검토 전에 적용됩니다. 황금률: 환경이 더 중요할수록 더 오랜 시간이 걸립니다.
간격이 더욱 엄격해지고 승인 절차도 까다로워집니다. 프로덕션에서는 간격을 사용합니다.
30m+ 이상이며 구조 변경에 대해서는 항상 수동 승인이 필요합니다.
결론 및 다음 단계
Terraform용 GitOps는 더 이상 코드형 인프라의 성숙도를 나타냅니다. 트리거 기반 파이프라인이지만 지속적인 조정, 파이프라인에 더 이상 자격 증명이 없음 하지만 클러스터의 기본 ID는 더 이상 "변경한 사람"이 아니라 감사 추적입니다. Git에서 완료됩니다. Flux TF 컨트롤러는 Kubernetes 기반 팀에게 이상적인 선택입니다. Spacelift는 Rego 정책 엔진을 통해 기업 요구 사항을 충족합니다.
전체 시리즈: Terraform 및 IaC
- 제01조 — 처음부터 새로 만드는 Terraform: HCL, 공급자 및 계획-적용-파괴
- 제02조 — 재사용 가능한 Terraform 모듈 설계
- 제03조 — Terraform 상태: S3/GCS를 사용한 원격 백엔드
- 제04조 — CI/CD의 Terraform: GitHub Actions 및 Atlantis
- 제05조 — IaC 테스트: Terratest 및 Terraform 테스트
- 제06조 — IaC 보안: Checkov, Trivy 및 OPA
- 제07조 — Terraform 다중 클라우드: AWS + Azure + GCP
- 제08조(본) — Terraform용 GitOps: Flux TF 컨트롤러, Spacelift 및 드리프트 감지
- 제09조 — Terraform vs Pulumi vs OpenTofu: 비교 2026
- 제10조 — Terraform 엔터프라이즈 패턴: 작업 공간, Sentinel 및 팀 확장







