State Sharing #

Ketika infrastruktur tumbuh dan dipecah menjadi beberapa konfigurasi Terraform yang terpisah, muncul kebutuhan yang tidak terhindarkan: konfigurasi A perlu tahu output dari konfigurasi B. Tim networking membuat VPC, tim compute perlu VPC ID itu untuk membuat EC2. Tim platform membuat EKS, tim aplikasi perlu endpoint cluster untuk deploy workload. Bagaimana informasi ini berpindah antar konfigurasi adalah keputusan arsitektur yang punya implikasi jangka panjang pada coupling, fleksibilitas, dan kemudahan troubleshooting.

Dua Pendekatan State Sharing #

Ada dua cara utama untuk berbagi informasi antar konfigurasi Terraform yang terpisah, dengan trade-off yang sangat berbeda.

PENDEKATAN 1: terraform_remote_state
  Cara   : Konfigurasi B membaca state konfigurasi A secara langsung
  Coupling: Ketat — B harus tahu detail backend A
  Fleksibel: Rendah — perubahan output A bisa breaking B
  Debugging: Sulit — dependency tersembunyi di dalam konfigurasi

PENDEKATAN 2: Variable Input
  Cara   : Nilai dioper secara eksplisit saat terraform apply
  Coupling: Longgar — B hanya menerima nilai, tidak tahu asalnya
  Fleksibel: Tinggi — sumber nilai bisa berubah tanpa modifikasi B
  Debugging: Mudah — dependency eksplisit dan terlihat jelas

terraform_remote_state: Ketika Coupling Bisa Diterima #

terraform_remote_state paling masuk akal ketika hubungan antar konfigurasi sangat stabil dan dikelola oleh tim yang sama.

# Konfigurasi "networking" menghasilkan output:
# outputs.tf di workspace networking

output "vpc_id" {
  value       = aws_vpc.main.id
  description = "VPC ID — dikonsumsi oleh workspace compute dan database"
}

output "private_subnet_ids" {
  value       = aws_subnet.private[*].id
}

output "database_subnet_ids" {
  value       = aws_subnet.database[*].id
}
# Konfigurasi "compute" membaca state networking
data "terraform_remote_state" "networking" {
  backend = "s3"

  config = {
    bucket = "my-terraform-state"
    key    = "production/networking/terraform.tfstate"
    region = "ap-southeast-1"
  }
}

resource "aws_instance" "app" {
  ami       = data.aws_ami.ubuntu.id
  subnet_id = data.terraform_remote_state.networking.outputs.private_subnet_ids[0]
}
KAPAN terraform_remote_state MASUK AKAL:
  ✓ Konfigurasi dikelola oleh tim yang sama
  ✓ Output yang dibaca stabil (tidak sering berubah nama/tipe)
  ✓ Dependency antar konfigurasi memang by design (bukan kebetulan)
  ✓ Konfigurasi sumber lebih "foundational" dari konfigurasi konsumen
    (networking → compute, bukan sebaliknya)

KAPAN HINDARI terraform_remote_state:
  ✗ Konfigurasi dikelola tim yang berbeda tanpa koordinasi
  ✗ Output di konfigurasi sumber sering berubah
  ✗ Ada circular dependency (A membaca state B, B membaca state A)
  ✗ Konfigurasi konsumen butuh berjalan secara independen
    (misalnya: testing, development isolated)

Variable Input: Decoupling yang Lebih Sehat #

Alih-alih membaca state secara langsung, konfigurasi menerima nilai yang dibutuhkan sebagai variable input. Sumber nilai tersebut bisa dari output konfigurasi lain, dari CI/CD pipeline, atau dari file tfvars.

# Konfigurasi "compute" menerima nilai sebagai variable — tidak tahu asalnya
variable "vpc_id" {
  description = "ID VPC tempat resource akan ditempatkan"
  type        = string
}

variable "private_subnet_ids" {
  description = "List ID private subnet"
  type        = list(string)
}

resource "aws_instance" "app" {
  ami       = data.aws_ami.ubuntu.id
  subnet_id = var.private_subnet_ids[0]
}
# Cara 1: Ambil output dari konfigurasi lain secara manual
cd environments/production/networking
NETWORKING_OUTPUTS=$(terraform output -json)

VPC_ID=$(echo $NETWORKING_OUTPUTS | jq -r '.vpc_id.value')
SUBNET_IDS=$(echo $NETWORKING_OUTPUTS | jq -r '.private_subnet_ids.value | @json')

# Lalu oper ke konfigurasi compute
cd ../compute
terraform apply \
  -var="vpc_id=$VPC_ID" \
  -var="private_subnet_ids=$SUBNET_IDS"
# Cara 2: CI/CD pipeline yang mengoper nilai antar konfigurasi
# .github/workflows/deploy.yml

jobs:
  deploy-networking:
    outputs:
      vpc_id: ${{ steps.outputs.outputs.vpc_id }}
      subnet_ids: ${{ steps.outputs.outputs.subnet_ids }}
    steps:
      - name: Apply networking
        run: terraform apply -auto-approve
        working-directory: environments/production/networking

      - name: Capture outputs
        id: outputs
        run: |
          echo "vpc_id=$(terraform output -raw vpc_id)" >> $GITHUB_OUTPUT
          echo "subnet_ids=$(terraform output -json private_subnet_ids)" >> $GITHUB_OUTPUT          
        working-directory: environments/production/networking

  deploy-compute:
    needs: deploy-networking
    steps:
      - name: Apply compute
        run: |
          terraform apply -auto-approve \
            -var="vpc_id=${{ needs.deploy-networking.outputs.vpc_id }}" \
            -var='private_subnet_ids=${{ needs.deploy-networking.outputs.subnet_ids }}'          
        working-directory: environments/production/compute

Hybrid: Outputs File sebagai Interface Eksplisit #

Pola ini membuat dependency lebih eksplisit dibanding terraform_remote_state tapi lebih otomatis dari oper manual via CLI.

# Setelah networking apply, generate file outputs.auto.tfvars
# yang akan otomatis dibaca oleh konfigurasi compute

# Script atau CI step setelah networking apply:
# terraform output -json | jq '{
#   vpc_id: .vpc_id.value,
#   private_subnet_ids: .private_subnet_ids.value
# }' > ../compute/networking-outputs.auto.tfvars.json

# environments/compute akan membaca file ini otomatis
# (*.auto.tfvars.json dibaca tanpa perlu -var-file)

Anti-Pattern: Circular Dependency #

# ANTI-PATTERN: A membaca state B, B membaca state A
# Circular dependency yang tidak bisa di-apply tanpa memecah siklus

# networking/main.tf:
data "terraform_remote_state" "compute" {
  # Networking baca output dari compute
  config = { key = "production/compute/terraform.tfstate" ... }
}

# compute/main.tf:
data "terraform_remote_state" "networking" {
  # Compute baca output dari networking
  config = { key = "production/networking/terraform.tfstate" ... }
}

# Masalah:
# - networking tidak bisa di-apply karena butuh state compute
# - compute tidak bisa di-apply karena butuh state networking
# - Tidak ada yang bisa jalan pertama kali (chicken-and-egg)

# SOLUSI: Identifikasi resource yang menjadi "foundational"
# dan pisahkan ke konfigurasi yang tidak bergantung pada konfigurasi lain

Menentukan Granularitas Pemisahan #

TERLALU MONOLITIK:
  Satu konfigurasi untuk seluruh infrastruktur
  → Plan lambat, blast radius besar, bottleneck kolaborasi

TERLALU TERFRAGMENTASI:
  50 konfigurasi kecil yang saling bergantung
  → Orkestrasi rumit, banyak state sharing, sulit dipahami

GRANULARITAS YANG TEPAT — pisahkan berdasarkan:

  LIFECYCLE: Resource yang berubah dengan frekuensi berbeda
    Networking (jarang berubah) → konfigurasi tersendiri
    Compute (sering berubah) → konfigurasi tersendiri
    Aplikasi (sering deploy) → konfigurasi tersendiri

  OWNERSHIP: Resource yang dimiliki tim berbeda
    Tim networking → konfigurasi networking
    Tim platform → konfigurasi platform (EKS, RDS)
    Tim aplikasi → konfigurasi aplikasi

  BLAST RADIUS: Resource yang tidak boleh saling mempengaruhi
    Production database → konfigurasi tersendiri dengan akses sangat terbatas
    Networking production → konfigurasi tersendiri

Ringkasan #

  • Dua pendekatan: terraform_remote_state (coupling ketat, mudah setup) vs variable input (decoupled, lebih fleksibel). Pilih berdasarkan seberapa stabil hubungan antar konfigurasi.
  • terraform_remote_state untuk konfigurasi yang sangat terkait dan dikelola tim yang sama — networking → compute adalah contoh klasik yang tepat.
  • Variable input untuk decoupling antar tim — konfigurasi tidak perlu tahu siapa yang menyediakan nilainya, hanya perlu tahu nilainya sendiri.
  • CI/CD pipeline bisa menjadi orkestrator yang mengambil output dari satu konfigurasi dan mengopernya sebagai variable ke konfigurasi berikutnya.
  • Hindari circular dependency — jika A butuh B dan B butuh A, identifikasi resource foundational dan pisahkan ke lapisan yang tidak bergantung pada siapapun.
  • Granularitas pemisahan berdasarkan lifecycle (seberapa sering berubah), ownership (siapa yang mengelola), dan blast radius (apa yang tidak boleh saling mempengaruhi).

← Sebelumnya: Cross Provider Reference   Berikutnya: CI Pipeline →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact