GitOps と Terraform: この組み合わせが強力な理由

GitOps で変革された Kubernetes アプリケーションのデプロイメント: Git がソースになる 実のところ、コントローラーは望ましい状態と実際の状態を継続的に調和させます。 すべての変更はプルリクエストを経由します。 2026 年にも同じパラダイムが定着しつつある Terraform で管理されるクラウド インフラストラクチャの場合は、Terraform と比べて 1 つの決定的な違いがあります。 従来の CI/CD: プッシュして忘れるトリガーの代わりに、 和解 続ける ドリフトを自動的に検出して修正します。

GitHub Actions または Atlantis に基づく従来の Terraform ワークフローの問題 私は誰ですか 反応的な: 誰かが AWS コンソールで手動で変更を加えますが、誰も変更しません これは、次のパイプラインが実行されるまでわかります。 GitOps for Terraform を使用すると、 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 の構成

ワークフローは 2 つの 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 からの 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

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...

高度なドリフト検出: アラートと自動修復

明確な対応戦略が伴っていなければ、ドリフト検出だけでは十分ではありません。 3 つのアプローチがあり、それぞれに独自のトレードオフがあります。

# 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 スペースリフト vs アトランティス

いつどのツールを使用するか

  • 磁束 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 con approvePlan: auto 環境について 本番環境で危険: メインにまだマージされていない変更は、 審査前に適用されました。黄金律: 環境がより重要であればあるほど、より長い時間がかかります 間隔と承認プロセスがより厳しくなります。本番環境では間隔を使用します 30m 以上あり、構造変更には常に手動による承認が必要です。

結論と次のステップ

GitOps for Terraform はコードとしてのインフラストラクチャの成熟度を表します: もはやそうではありません トリガーベースのパイプラインですが、継続的なリコンシリエーションが行われるため、パイプラインに資格情報は不要になります ただし、クラスターのネイティブ 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 コントローラー、スペースリフト、ドリフト検出
  • 第09条 — Terraform vs Pulumi vs OpenTofu: 比較 2026
  • 第10条 — Terraform エンタープライズ パターン: ワークスペース、センチネル、チーム スケーリング