Lifecycle Management #

Membuat resource dengan Terraform itu mudah. Yang lebih menantang adalah mengelolanya setelah dibuat — terutama ketika perlu diubah, dipindahkan, atau dihapus tanpa menyebabkan gangguan pada infrastruktur yang sedang berjalan. Terraform menyediakan beberapa mekanisme untuk ini: lifecycle block yang mengontrol perilaku resource, moved block untuk refactoring tanpa destroy, dan removed block untuk decommission yang terkontrol. Memahami alat-alat ini membuat kamu bisa melakukan perubahan besar pada konfigurasi dengan aman.

flowchart TD
    A["lifecycle block"] --> B["prevent_destroy"]
    A --> C["create_before_destroy"]
    A --> D["ignore_changes"]
    E["moved block"] --> F["Rename\nwithout destroy"]
    G["removed block"] --> H["Decommission\ncontrolled"]

    style A fill:#8b5cf6,stroke:#6d28d9,color:#fff
    style B fill:#3b82f6,stroke:#1e40af,color:#fff
    style C fill:#3b82f6,stroke:#1e40af,color:#fff
    style D fill:#3b82f6,stroke:#1e40af,color:#fff
    style E fill:#10b981,stroke:#059669,color:#fff
    style F fill:#10b981,stroke:#059669,color:#fff
    style G fill:#f59e0b,stroke:#d97706,color:#fff
    style H fill:#f59e0b,stroke:#d97706,color:#fff

Lifecycle Block: Kontrol Perilaku Resource #

# Empat opsi lifecycle yang paling berguna

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type

  lifecycle {
    # 1. create_before_destroy
    # Buat resource baru SEBELUM menghapus yang lama
    # Berguna untuk resource yang tidak boleh ada jedanya
    create_before_destroy = true

    # 2. prevent_destroy
    # Terraform error jika ada yang mencoba destroy resource ini
    # Perlindungan untuk resource kritikal (database production, dll.)
    prevent_destroy = true

    # 3. ignore_changes
    # Abaikan perubahan pada atribut tertentu
    # Berguna jika atribut diubah di luar Terraform
    ignore_changes = [
      tags["LastModified"],
      user_data,
    ]

    # 4. replace_triggered_by (Terraform 1.2+)
    # Trigger replacement saat resource lain berubah
    replace_triggered_by = [
      aws_launch_template.web.id
    ]
  }
}

create_before_destroy: Zero-Downtime Replacement #

# Tanpa create_before_destroy (default):
# 1. Hapus resource lama
# 2. Buat resource baru
# → Ada jeda di mana tidak ada resource yang jalan (downtime)

# ANTI-PATTERN: Resource yang butuh high availability tanpa create_before_destroy
resource "aws_lb_target_group" "app" {
  name     = "production-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id
  # Jika nama TG perlu diubah → destroy dulu, baru create → downtime
}

# BENAR: Dengan create_before_destroy dan name_prefix
resource "aws_lb_target_group" "app" {
  name_prefix = "app-"    # AWS generate suffix unik
  port        = 80
  protocol    = "HTTP"
  vpc_id      = var.vpc_id

  lifecycle {
    create_before_destroy = true
    # 1. Buat TG baru dengan nama baru
    # 2. Pindahkan listener ke TG baru
    # 3. Hapus TG lama
    # → Tidak ada downtime
  }
}

moved Block: Refactoring Tanpa Destroy #

moved block adalah cara yang tepat untuk mengubah address resource di state tanpa menghapus dan membuat ulang resource di cloud. Berguna saat refactoring konfigurasi — rename resource, pindah ke module, atau reorganisasi struktur.

# Skenario 1: Rename resource
# Sebelum: resource "aws_instance" "server"
# Sesudah: resource "aws_instance" "web_server"

moved {
  from = aws_instance.server
  to   = aws_instance.web_server
}

# Terraform akan memperbarui state (rename address)
# tanpa menghapus dan membuat ulang instance di AWS
# Skenario 2: Pindah resource ke dalam module
# Sebelum: resource "aws_vpc" "main" di root module
# Sesudah: dipindah ke module "networking"

moved {
  from = aws_vpc.main
  to   = module.networking.aws_vpc.main
}

# terraform plan akan menampilkan:
# # aws_vpc.main has moved to module.networking.aws_vpc.main
# → Tidak ada resource yang dihapus atau dibuat ulang
# Skenario 3: Pindah dari count ke for_each
# Sebelum: resource "aws_subnet" "public" count = 3
# Sesudah: resource "aws_subnet" "public" for_each

moved {
  from = aws_subnet.public[0]
  to   = aws_subnet.public["ap-southeast-1a"]
}

moved {
  from = aws_subnet.public[1]
  to   = aws_subnet.public["ap-southeast-1b"]
}

moved {
  from = aws_subnet.public[2]
  to   = aws_subnet.public["ap-southeast-1c"]
}
moved block bisa dihapus setelah semua environment sudah di-apply dan state sudah diperbarui. Tapi sebaiknya biarkan beberapa sprint sebelum dihapus agar environment yang jarang di-apply juga kebagian.

removed Block: Decommission yang Terkontrol #

removed block (Terraform 1.7+) memungkinkan kamu menghapus resource dari konfigurasi dan state tanpa menghapus resource aktual di cloud — berguna untuk “melepaskan” resource dari kontrol Terraform tanpa menghancurkannya.

# Skenario: Database lama tidak lagi dikelola Terraform
# tapi tidak mau dihapus dari cloud (masih ada data)

removed {
  from = aws_db_instance.legacy

  lifecycle {
    destroy = false  # Jangan hapus dari cloud, hanya hapus dari state
  }
}

# Setelah apply:
# - Resource dihapus dari state Terraform
# - Resource TETAP ada di AWS
# - Terraform tidak lagi mengelola resource ini
# Berbeda dengan destroy = true (untuk resource yang memang perlu dihapus):
removed {
  from = aws_instance.deprecated_worker

  lifecycle {
    destroy = true  # Hapus dari state DAN dari cloud
  }
}

Mengelola Breaking Change di Module #

Ketika modul yang digunakan banyak tempat perlu perubahan breaking, ada cara yang lebih aman dari “langsung ubah dan semua pemanggilnya pecah”.

# Strategi: Backward compatibility dengan deprecation period

# modules/networking/main.tf — tambahkan alias untuk input yang berubah nama

variable "vpc_cidr_block" {
  description = "CIDR block untuk VPC"
  type        = string
}

# Variable lama yang sudah deprecated — masih ada untuk backward compatibility
variable "cidr_block" {
  description = "DEPRECATED: Gunakan vpc_cidr_block. Akan dihapus di v3.0"
  type        = string
  default     = null
}

locals {
  # Gunakan yang baru jika ada, fallback ke yang lama
  vpc_cidr = coalesce(var.vpc_cidr_block, var.cidr_block)
}

resource "aws_vpc" "main" {
  cidr_block = local.vpc_cidr
}
TIMELINE DEPRECATION YANG AMAN:
  v2.0 — Variable baru ditambahkan, variable lama masih ada
  v2.1 — Warning ditampilkan jika masih menggunakan variable lama
  v3.0 — Variable lama dihapus (di CHANGELOG dan README disebutkan)

Pemanggilnya punya waktu untuk migrasi di antara versi major.

Kapan State Surgery Dibutuhkan #

Ada situasi di mana manipulasi state manual diperlukan — ini harus jadi pilihan terakhir, bukan default.

# State surgery — gunakan dengan sangat hati-hati

# Pindahkan resource antar state (jika moved block tidak cukup)
terraform state mv \
  -state=old/terraform.tfstate \
  -state-out=new/terraform.tfstate \
  aws_vpc.main \
  aws_vpc.main

# Hapus resource dari state tanpa menghapus dari cloud
terraform state rm aws_instance.orphaned

# Import resource yang sudah ada ke state
terraform import aws_instance.existing i-0abcdef1234567890

# SELALU backup sebelum state surgery
terraform state pull > backup-$(date +%Y%m%d-%H%M%S).tfstate

# Verifikasi setelah state surgery
terraform plan  # Harusnya "No changes" jika state sudah benar

flowchart LR
    A["Old resource\nname/path"] -->|"moved block"| B["New resource\nname/path"]
    B --> C["State updated\nNo destroy"]

    style A fill:#f59e0b,stroke:#d97706,color:#fff
    style B fill:#10b981,stroke:#059669,color:#fff
    style C fill:#3b82f6,stroke:#1e40af,color:#fff

Lifecycle Hooks dalam CI/CD #

Lifecycle management yang baik mengintegrasikan Terraform dengan CI/CD pipeline.

# GitHub Actions: Lifecycle-aware pipeline
name: Terraform Lifecycle
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # Drift detection setiap Senin jam 6

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: terraform init
      - run: terraform plan -out=tfplan

  apply:
    needs: plan
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
      - run: terraform apply tfplan

  drift:
    if: github.event_name == 'schedule'
    steps:
      - run: terraform plan -refresh-only -detailed-exitcode

Resource Lifecycle Monitoring #

# Monitor resource yang mencegah destroy
grep -r "prevent_destroy = true" --include="*.tf"

# Cek resource yang punya lifecycle rules
terraform state list | while read resource; do
  terraform state show "$resource" 2>/dev/null | grep -A2 lifecycle
done

# Audit: resource mana yang butuh prevent_destroy?
# - Database (production data)
# - S3 bucket (data loss risk)
# - KMS key (encrypted data jadi tidak bisa diakses)
# - DNS records (service disruption)

Resource Drift Detection #

# Drift = perbedaan antara state Terraform dan kondisi aktual di cloud
# Terjadi ketika:
# 1. Seseorang mengubah resource manual di console/CLI
# 2. Auto-scaling mengubah jumlah instance
# 3. External process mengubah tag/attribute

# Detect drift:
terraform plan -refresh-only
# Menampilkan perbedaan antara state dan real infrastructure

# Atau: hanya refresh state tanpa apply
terraform apply -refresh-only

# Scheduled drift detection di CI/CD:
# Jalankan setiap hari/hour, alert jika ada drift
flowchart TD
    A["Scheduled Job"] --> B["terraform plan
-refresh-only"]
    B --> C{"Drift
detected?"}
    C -->|"Ya"| D["Alert team"]
    C -->|"Tidak"| E["Log: OK ✅"]
    D --> F["Review changes"]
    F --> G{"Expected?"}
    G -->|"Ya"| H["Update state
(apply -refresh-only)"]
    G -->|"Tidak"| I["Revert manual
changes"]

Migration Planning #

# Sebelum melakukan breaking changes:

# 1. Analisis impact
terraform plan 2>&1 | grep -E "must be replaced|will be destroyed"

# 2. Use -target untuk staged migration
terraform apply -target=aws_instance.new_web
# Verify new resource works
terraform apply -target=aws_instance.old_web
# Remove old resource

# 3. Use moved blocks (Terraform 1.1+)
# Refactor tanpa destroy/create
# moved block: rename resource tanpa destroy
moved {
  from = aws_instance.web
  to   = aws_instance.application_server
}

# moved block: restructure module
moved {
  from = module.networking.aws_subnet.public
  to   = module.networking.aws_subnet.dmz
}

Ringkasan #

  • create_before_destroy penting untuk resource high-availability — pastikan terpasang pada load balancer, target group, dan resource yang tidak boleh ada jedanya saat diganti.
  • prevent_destroy sebagai perlindungan terakhir untuk resource kritikal — Terraform error jika ada plan yang akan menghapus resource ini.
  • moved block adalah cara yang tepat untuk refactoring konfigurasi — rename resource, pindah ke module, atau ganti dari count ke for_each tanpa destroy.
  • removed block untuk decommission yang terkontrol — bisa memilih apakah resource dihapus dari cloud atau hanya dilepaskan dari kontrol Terraform.
  • Breaking change di module butuh deprecation period — tambahkan variable baru sambil jaga yang lama, beri waktu pemanggilnya untuk migrasi sebelum variable lama dihapus.
  • State surgery (terraform state mv/rm) adalah pilihan terakhir — selalu backup state sebelum, verifikasi dengan terraform plan sesudah.

← Sebelumnya: Monorepo vs Multirepo   Berikutnya: Scale Terraform →

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