Anti-Pattern: Terraform as CM #

Terraform bisa membuat instance EC2, dan instance EC2 perlu dikonfigurasi setelah dibuat — install nginx, copy file konfigurasi, setup monitoring agent. Karena Terraform sudah ada di tangan dan bisa menjalankan remote-exec provisioner, godaan untuk melakukan konfigurasi server langsung dari Terraform sangat besar. Ini adalah anti-pattern yang terlihat seperti solusi sederhana di awal, tapi menciptakan masalah yang makin besar seiring waktu. Terraform dirancang untuk provisioning infrastruktur, bukan configuration management.

Apa yang Dimaksud Terraform sebagai CM #

Configuration Management (CM) adalah praktik mengelola konfigurasi dalam server: install package, manage file, set service state, create user, dan seterusnya. Tool yang dirancang untuk ini adalah Ansible, Chef, Puppet, dan SaltStack. Terraform adalah provisioning tool — ia membuat dan mengelola infrastruktur cloud, bukan konten di dalam infrastruktur tersebut.

BATAS YANG SEHARUSNYA JELAS:

  TERRAFORM (provisioning):
    ✓ Buat EC2 instance
    ✓ Buat VPC, security group, load balancer
    ✓ Buat RDS database
    ✓ Buat S3 bucket
    ✓ Kelola IAM roles

  CONFIGURATION MANAGEMENT TOOL (konfigurasi dalam server):
    ✓ Install nginx di dalam EC2 instance
    ✓ Copy file konfigurasi ke server
    ✓ Start dan enable service
    ✓ Buat user dan set permission
    ✓ Deploy aplikasi

  GARIS INI TIDAK BOLEH DICAMPUR.

Mengapa remote-exec Provisioner Bermasalah #

# ANTI-PATTERN: Menggunakan remote-exec untuk konfigurasi server

resource "aws_instance" "web" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
  key_name      = aws_key_pair.deployer.key_name

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update -y",
      "sudo apt-get install -y nginx",
      "sudo systemctl enable nginx",
      "sudo systemctl start nginx",
      "sudo bash -c 'echo Hello > /var/www/html/index.html'",
    ]

    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/id_rsa")  # ✗ Path hardcoded, tidak portable
      host        = self.public_ip
    }
  }
}

Masalah dengan pendekatan ini:

MASALAH remote-exec:

  1. TIDAK IDEMPOTEN
     Jalankan terraform apply dua kali → kemungkinan error karena
     apt-get atau nginx sudah ada. Atau lebih buruk: silent success
     yang sebenarnya tidak mengeksekusi apa yang diharapkan.

  2. TIDAK BISA DIVERIFIKASI
     terraform plan tidak bisa menunjukkan apa yang akan dilakukan
     provisioner. Kamu tidak tahu efeknya sebelum dijalankan.

  3. BUTUH SSH/WinRM ACCESS
     EC2 instance harus bisa diakses dari runner yang menjalankan Terraform.
     Ini berarti security group harus buka port 22 ke CI/CD server —
     security concern yang tidak perlu.

  4. KONFIGURASI TIDAK TERSIMPAN DI STATE
     Jika provisioner sudah berjalan, Terraform tidak tahu dan tidak melacak
     apa yang sudah dikonfigurasi. Tidak ada idempotency check.

  5. PROVISIONER HANYA BERJALAN SAAT CREATE
     Jika konfigurasi server perlu diupdate, Terraform tidak bisa
     "re-run" provisioner tanpa destroy dan recreate instance.

  6. TERRAFORM PLAN MENJADI TIDAK BISA DIPERCAYA
     "No changes" tidak berarti konfigurasi server sesuai harapan.

Masalah file Provisioner #

# ANTI-PATTERN: Gunakan file provisioner untuk deploy konfigurasi

resource "aws_instance" "app" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"

  provisioner "file" {
    source      = "config/nginx.conf"    # ✗ Path relatif yang rapuh
    destination = "/etc/nginx/nginx.conf"

    connection {
      type = "ssh"
      # ...
    }
  }

  provisioner "remote-exec" {
    inline = ["sudo nginx -t && sudo systemctl reload nginx"]
    # ...
  }
}

# Masalah:
# - Jika nginx.conf berubah, Terraform tidak tahu karena tidak ada di state
# - terraform plan selalu "No changes" meski config file sudah berubah
# - Tidak ada cara untuk update konfigurasi server tanpa recreate instance

Cara yang Benar: Pisahkan Provisioning dan Konfigurasi #

# BENAR: Terraform hanya provisioning infrastruktur
# Konfigurasi server diserahkan ke tool yang tepat

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  # Minimal bootstrap via user_data (cloud-init)
  # Hanya untuk setup awal yang sangat dasar
  user_data = base64encode(<<-EOF
    #!/bin/bash
    # Install SSM agent agar bisa dimanage AWS Systems Manager
    snap install amazon-ssm-agent --classic
    systemctl enable snap.amazon-ssm-agent.amazon-ssm-agent
    systemctl start snap.amazon-ssm-agent.amazon-ssm-agent

    # Install Ansible untuk config management selanjutnya
    apt-get update
    apt-get install -y ansible
  EOF
  )

  # Tidak ada provisioner — tidak ada SSH dari Terraform
  # Tidak ada remote-exec — tidak ada file provisioner
}

# Output yang akan dipakai Ansible atau pipeline berikutnya
output "instance_ids" {
  value = aws_instance.web[*].id
}
# Pipeline yang benar: Terraform untuk infra, Ansible untuk konfigurasi

# .github/workflows/deploy.yml
jobs:
  provision:
    name: Terraform — Provision Infrastructure
    steps:
      - name: Terraform Apply
        run: terraform apply -auto-approve
      - name: Save Instance IDs
        run: terraform output -json instance_ids > instances.json

  configure:
    name: Ansible — Configure Servers
    needs: provision  # Jalankan setelah provision selesai
    steps:
      - name: Generate Ansible Inventory
        run: |
          cat instances.json | python3 scripts/generate-inventory.py > inventory.ini          

      - name: Run Ansible Playbook
        run: |
          ansible-playbook \
            -i inventory.ini \
            playbooks/web-server.yml          

Kapan user_data Boleh Digunakan #

user_data (cloud-init) adalah pengecualian yang dapat diterima untuk konfigurasi minimal saat instance pertama kali boot.

# user_data yang TEPAT — minimal bootstrap saja
resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.medium"

  user_data = base64encode(<<-EOF
    #!/bin/bash
    # 1. Install agent untuk configuration management
    apt-get install -y ansible
    # atau: install Puppet agent, Chef client, SSM agent

    # 2. Register ke CM tool untuk konfigurasi selanjutnya
    ansible-pull -U https://github.com/myorg/infra-playbooks.git

    # JANGAN: install aplikasi lengkap, copy banyak file, setup kompleks
    # Minimal bootstrap hanya untuk "bootstrap" ke CM tool
  EOF
  )

  lifecycle {
    # user_data biasanya tidak perlu diupdate jika sudah pakai CM tool
    ignore_changes = [user_data]
  }
}

Alternatif: Immutable Infrastructure #

Pendekatan terbaik menghindari configuration management sama sekali adalah immutable infrastructure — buat AMI baru yang sudah berisi semua konfigurasi, lalu ganti instance lama dengan yang baru.

# Pola immutable infrastructure dengan Packer + Terraform

# 1. Packer buat AMI dengan semua konfigurasi di dalamnya
#    (lihat: packer build web-server.pkr.hcl)

# 2. Terraform hanya perlu tahu AMI ID terbaru
data "aws_ami" "web_server" {
  most_recent = true
  owners      = ["self"]  # AMI yang dibuat tim sendiri

  filter {
    name   = "name"
    values = ["web-server-*"]
  }

  filter {
    name   = "tag:Version"
    values = [var.app_version]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.web_server.id  # AMI yang sudah dikonfigurasi
  instance_type = "t3.micro"
  # Tidak ada provisioner, tidak ada user_data yang kompleks
  # Semua konfigurasi sudah ada di dalam AMI
}

Ringkasan #

  • Terraform adalah provisioning tool, bukan configuration management tool — membuat resource cloud adalah ranahnya, mengkonfigurasi isi server bukan.
  • remote-exec provisioner menciptakan masalah idempotency, keamanan (butuh SSH terbuka), dan visibility (tidak ada di plan output).
  • file provisioner tidak melacak perubahan di state — konfigurasi yang berubah tidak terdeteksi oleh terraform plan.
  • Pisahkan pipeline: Terraform untuk infrastruktur, Ansible/Chef/Puppet untuk konfigurasi server — keduanya bisa dijalankan berurutan di pipeline CI/CD.
  • user_data boleh digunakan untuk minimal bootstrap (install agent CM tool) — bukan untuk konfigurasi server yang lengkap.
  • Immutable infrastructure adalah pendekatan terbaik — gunakan Packer untuk buat AMI yang sudah dikonfigurasi, Terraform hanya deploy AMI tersebut.

← Sebelumnya: Performance Optimization   Berikutnya: Anti-Pattern: Over-Complex Module →

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