โ† semua materi
15:00 Teknis ยท 07 Nov 2026 ยท 15:19

๐Ÿ”ฌ BACOT โ€” Sesi 15:00 Technical Exploration

๐Ÿ“š Topik: Terraform State, Drift, dan Cara Aman Mengubah Infra Hidup

Versi terverifikasi (7 Nov 2026): Terraform CLI v1.17.1 (rilis 28 Okt 2026), v1.17.0 (21 Okt 2026), v1.16.0 (26 Agu 2026). OpenTofu v1.13.9 (5 Nov 2026), 1.13.0 (29 Sep 2026). Semua contoh di bawah dieksekusi nyata di Terraform 1.17.1 linux_amd64 saat sesi ini dibuat.


๐ŸŽฏ Tujuan Belajar

Setelah sesi ini kamu bisa:

  1. Menjelaskan isi file state dan kenapa ia satu-satunya sumber kebenaran mapping resource โ†’ objek nyata.
  2. Mendeteksi drift secara terprogram di CI (-detailed-exitcode + event JSON resource_drift), bukan cuma "liat plan manual".
  3. Mengubah infra hidup tanpa destroy: moved block, import block, removed block, lifecycle { destroy = false }.
  4. Memilih mode refresh yang benar (default / -refresh-only / -minimal-refresh / -refresh=false) dan tahu kapan tiap mode menyembunyikan masalah.

๐Ÿ—๏ธ Arsitektur & Alur Teknis

Apa isi state

State bukan "catatan" โ€” ia binding table. Format v4, isi kunci: version, terraform_version, serial, lineage, resources[]. serial naik tiap tulis; lineage mengidentifikasi state itu milik siapa (mencegah dua state tercampur saat backend dipindah).

terraform plan
  โ”œโ”€ 1. Refresh   : GET tiap objek nyata via provider โ†’ bangun "current state"
  โ”œโ”€ 2. Diff      : config (HCL) vs current state vs prior state
  โ”œโ”€ 3. Plan      : daftar aksi (create/update/replace/destroy/no-op)
  โ””โ”€ 4. Apply     : eksekusi + tulis state baru (serial+1)

Tiga hal yang sering dikira satu:

Konsep Arti Gejala kalau salah paham
State record Terraform state hilang = Terraform mau create ulang semua
Drift objek nyata โ‰  state/config plan tiba-tiba muncul ~ atau + tanpa kamu ubah kode
Config drift kode di repo โ‰  yang terakhir di-apply plan bersih tapi infra beda dari yang kamu kira

Backend & locking (per 1.17)

S3 backend sekarang native lock lewat use_lockfile = true. DynamoDB deprecated (masih bisa, tapi akan dihapus di minor berikutnya). Ini menghapus satu komponen infra (tabel DynamoDB) dari setup klasik.

terraform {
  backend "s3" {
    bucket       = "example-bucket"
    key          = "prod/network/terraform.tfstate"
    region       = "ap-southeast-3"
    use_lockfile = true   # objek <key>.tflock; butuh s3:DeleteObject di lockfile
    encrypt      = true
  }
}

IAM minimum: s3:ListBucket (prefix state), s3:GetObject+s3:PutObject (state), plus s3:GetObject+s3:PutObject+s3:DeleteObject pada *.tflock. Bucket versioning wajib โ€” itu satu-satunya jalan pulang kalau state ke-corrupt.


๐Ÿ’ป Kode Nyata / Konfigurasi

1. Rename resource tanpa destroy โ€” moved block

Dulu: terraform state mv. Sekarang deklaratif, ikut code review. Diuji: plan menghasilkan has moved to, 0 to add, 0 to change, 0 to destroy.

# sebelum: resource "local_file" "a" { ... }
resource "local_file" "b" {
  filename = "${path.module}/x.txt"
  content  = "hi"
}

moved {
  from = local_file.a
  to   = local_file.b
}

Output nyata:

  # local_file.a has moved to local_file.b
    resource "local_file" "b" { ... }
Plan: 0 to add, 0 to change, 0 to destroy.

Catatan: moved block jangan dihapus selama masih ada state lama di environment lain (staging/prod belum apply). Biarkan minimal satu rilis penuh.

2. Lepas resource dari state tanpa destroy โ€” removed block

Kasus: resource mau diserahkan ke tim lain / dikelola manual. Dulu terraform state rm (imperatif, tidak ter-review, tidak ada jejak di plan).

removed {
  from = terraform_data.old
  lifecycle {
    destroy = false    # hapus dari state, JANGAN hapus objek nyata
  }
}

Output nyata (Terraform 1.17.1):

If you apply this plan, Terraform will discard its tracking information for
the following objects, but it will not delete them:
 - terraform_data.old
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

terraform state list setelahnya hanya berisi terraform_data.keep. Objek lama tetap hidup.

removed block tersedia sejak 1.7. Sejak 1.9 mendukung provisioner destroy-time (buat cleanup custom saat melepas).

3. Kunci resource dari destroy โ€” lifecycle { destroy = false }

Baru di 1.16.0. Berbeda dari prevent_destroy: prevent_destroy menggagalkan plan/apply (error), destroy = false membiarkan plan jalan tapi melepas tracking alih-alih menghapus.

resource "terraform_data" "keep" {
  input = "keep-me-v2"
  lifecycle {
    destroy = false
  }
}

Diuji: terraform destroy -auto-approve โ†’

 - terraform_data.keep
After applying this plan, Terraform will no longer manage these objects.
Destroy complete! Resources: 0 destroyed.

Cocok untuk: bucket data, KMS key, DNS zone legacy, IP elastic โ€” benda yang salah hapus = insiden.

4. Drift detection di CI โ€” exit code + JSON stream

#!/usr/bin/env bash
set -euo pipefail
terraform init -input=false -lock-timeout=5m >/dev/null

set +e
terraform plan -input=false -lock-timeout=5m \
  -detailed-exitcode -out=tfplan -no-color > plan.txt 2>&1
code=$?
set -e

case $code in
  0) echo "no drift" ;;
  1) echo "PLAN ERROR"; cat plan.txt; exit 1 ;;
  2) echo "DRIFT / CHANGES DETECTED"
     # ringkas per-resource, jangan dump seluruh plan
     grep -E '^\s+#\s+\S+ (will be|must be|has moved|has been)' plan.txt || true
     exit 2 ;;
esac

Exit code: 0 = bersih, 1 = error, 2 = ada diff. Jangan pakai || true โ€” itu mematikan seluruh gunanya.

Versi JSON lebih kuat untuk gating berbasis tipe perubahan (misal: blokir kalau ada delete):

terraform plan -json -detailed-exitcode -no-color > plan.jsonl 2>&1 || true
python3 - <<'PY'
import json
drift, deletes = [], []
for line in open('plan.jsonl'):
    line = line.strip()
    if not line.startswith('{'): continue
    e = json.loads(line)
    if e.get('type') == 'resource_drift':
        drift.append((e['change']['resource']['addr'], e['change']['action']))
    if e.get('type') == 'planned_change' and e['change']['action'] == 'delete':
        deletes.append(e['change']['resource']['addr'])
print("drift:", drift)
print("deletes:", deletes)
raise SystemExit(1 if deletes else 0)
PY

Diuji: event resource_drift muncul persis saat file dikelola diubah out-of-band (replicas=2 โ†’ replicas=7), dengan action: "update". Event types yang teramati: version, refresh_start, refresh_complete, resource_drift, planned_change, change_summary.

โš ๏ธ 1.17.0 menambah format_version pada output JSON version. Tooling yang parse JSON harus mengabaikan field tak dikenal / pin versi hashicorp/terraform-json.

5. Mode refresh โ€” pilih sadar, bukan default buta

Flag Yang dilakukan Risiko
(default) refresh semua objek, lalu diff lambat di estate besar (ratusan resource ร— API call)
-refresh-only hanya rekonsiliasi state ke kenyataan, tanpa usul perubahan config tidak mendeteksi config drift
-minimal-refresh (baru 1.17.0) refresh hanya resource yang punya usulan perubahan bisa plan terhadap record remote yang stale
-refresh=false skip refresh total buta total terhadap drift

Diuji โ€” ini jebakan utamanya: file dihapus/diubah di luar Terraform, lalu:

plan -detailed-exitcode            โ†’ exit=2  (drift terdeteksi, usul create ulang)
plan -refresh-only -detailed-exitcode โ†’ exit=2  (lapor "has been deleted")
plan -refresh=false -detailed-exitcode โ†’ exit=0  "No changes. Your infrastructure matches the configuration."

Baris terakhir itu bohong. -refresh=false di pipeline drift-check = pipeline yang selalu hijau. Pakai hanya untuk speed-up plan berulang di lokal, jangan di gate.

6. Test tanpa cloud โ€” terraform test + mock_provider

# tests/basic.tftest.hcl
mock_provider "local" {}

run "plan_is_clean" {
  command = plan
  assert {
    condition     = local_file.b.content == "hi"
    error_message = "content mismatch"
  }
}
$ terraform test
tests/basic.tftest.hcl... in progress
  run "plan_is_clean"... pass
Success! 1 passed, 0 failed.

mock_provider = tidak butuh kredensial, tidak butuh network. Ini yang membuat terraform test layak masuk CI tanpa biaya cloud. (1.16.0 menambah mock untuk ephemeral resources.)


๐Ÿ“Š Angka & Benchmark

Yang bisa saya verifikasi hari ini:

Item Nilai Sumber
Terraform CLI terbaru 1.17.1, 28 Okt 2026 GitHub releases hashicorp/terraform
Terraform 1.17.0 21 Okt 2026 GitHub releases
Terraform 1.16.0 26 Agu 2026 CHANGELOG v1.16
OpenTofu terbaru 1.13.9, 5 Nov 2026 GitHub releases opentofu/opentofu
Native S3 locking sejak 1.11 (use_lockfile) docs S3 backend + changelog
DynamoDB locking deprecated, akan dihapus di minor berikutnya docs S3 backend (live)
State format version 4 terraform state pull (diuji)
Plan exit codes 0 bersih / 1 error / 2 diff terraform plan -help (diuji)

Tidak terverifikasi / jangan dikutip sebagai fakta: angka persentase penghematan waktu plan dengan -minimal-refresh, benchmark kecepatan refresh di estate besar, biaya API call per 1000 resource. Tidak ada sumber resmi yang saya temukan dengan angka tersebut. Kalau butuh, ukur sendiri (lihat latihan).


โš–๏ธ Trade-off & Alternatif

State lokal vs remote vs managed

Aspek local S3 + use_lockfile HCP Terraform / TFE
Locking tidak ada native (.tflock) built-in
Kolaborasi 1 orang manual (siapa apply duluan) queue + RBAC
Biaya 0 ~storage + request per-resource (mahal di skala besar)
Drift detection terjadwal bikin sendiri bikin sendiri (cron+CI) bawaan
Audit trail tidak ada S3 versioning + CloudTrail bawaan

Ladder-nya: lokal untuk eksperimen โ†’ S3+DynamoDB (legacy) โ†’ S3+use_lockfile (sekarang default waras) โ†’ managed hanya kalau butuh RBAC/audit/policy as code.

Terraform vs OpenTofu (per Nov 2026)

Aspek Terraform 1.17 OpenTofu 1.13
Lisensi BUSL (sejak 1.6) MPL 2.0
State encryption at rest tidak ada di core ada โ€” PBKDF2 / AWS KMS / GCP KMS / Azure Key Vault / OpenBao
removed + destroy=false ya (1.7 / 1.16) ya (destroy=false di lifecycle resource sejak 1.12)
Policy as code Terraform Policy GA (1.17) tidak setara
Registry/provider penuh mirror, kompatibel
32-bit build masih dihapus setelah 1.13

Soal state encryption: ini alasan paling sering orang pindah. State berisi plaintext secret (password DB, private key). Terraform core tidak mengenkripsi state โ€” kamu bergantung pada enkripsi bucket + IAM. OpenTofu mengenkripsi payload-nya sendiri, jadi bocornya bucket โ‰  bocornya secret.

โš ๏ธ Peringatan operasional encryption: begitu diaktifkan, state tidak bisa dibaca tanpa key. Backup state plaintext dulu, backup key, uji disaster recovery sebelum enable di prod. Encryption juga tidak melindungi dari replay attack (state lama dipakai ulang) dan tidak melindungi dari orang yang menjalankan tofu itu sendiri.

moved vs state mv

moved block terraform state mv
Ter-review di PR ya tidak
Reproducible di semua env ya tidak (manual per env)
Bisa untuk rename module ya ya
Butuh dihapus nanti ya (setelah semua env apply) tidak

prevent_destroy vs destroy = false

prevent_destroy destroy = false (1.16+)
Efek saat destroy plan error, blokir total plan jalan, resource dilepas dari state
Objek nyata tetap tetap
Tracking Terraform tetap hilang
Pakai untuk resource yang tidak boleh hilang sama sekali hand-off ke tool/tim lain

๐Ÿญ Use Case Produksi & Failure Mode Umum

Failure mode 1 โ€” dua pipeline apply bersamaan. Tanpa lock, dua apply saling timpa โ†’ state korup, resource orphan. S3 use_lockfile menutup ini. Tapi lock tidak mencegah dua orang apply berurutan dengan plan basi. Mitigasi: simpan -out=tfplan, apply file plan itu saja, dan tolak apply kalau serial state berubah sejak plan dibuat.

Failure mode 2 โ€” -target jadi kebiasaan. terraform plan -target=aws_instance.web hanya refresh dependency target. Sisa estate tidak dicek. Dipakai berulang โ†’ drift menumpuk tak terlihat berbulan-bulan. -target untuk darurat saja, dan selalu jalankan plan penuh setelahnya.

Failure mode 3 โ€” state terpecah tanpa moved. Refactor modul tanpa moved โ†’ plan berisi -/+ destroy and then create replacement untuk resource yang isinya data. Ini yang bikin bucket produksi hilang. Aturan: baca plan sampai habis sebelum approve, khususnya cari must be replaced dan forces replacement.

Failure mode 4 โ€” for_each dengan key yang berubah. Ganti key dari nama resource ke index (for_each = toset([...]) โ†’ count) = semua instance dianggap baru. Selalu for_each dengan key stabil (ID, nama logis), jangan pernah index.

Failure mode 5 โ€” drift-check yang hijau palsu. -refresh=false di cron, atau terraform plan || true, atau plan di workspace salah. Gejalanya: dashboard "no drift" 6 bulan, lalu suatu hari apply menghapus 40 resource. Verifikasi gate-nya dengan sengaja membuat drift (ubah satu tag manual) dan pastikan pipeline merah.

Failure mode 6 โ€” removed { destroy = false } lalu lupa. Resource lepas dari state, tapi masih ada di config sebagai resource baru โ†’ Terraform coba create objek yang sudah ada โ†’ error konflik nama, atau lebih buruk: berhasil bikin duplikat. Setelah hand-off, hapus juga blok resource-nya dari config.

Pola produksi yang waras (ringkas):

PR  โ†’ plan (speculative, -detailed-exitcode) โ†’ review diff
     โ†’ merge โ†’ apply plan file yang sama (bukan plan ulang)
cron โ†’ plan -detailed-exitcode tiap 6 jam โ†’ alert kalau exit 2
     โ†’ plan -json โ†’ klasifikasi: drift kosmetik vs drift destruktif
     โ†’ auto-remediate HANYA untuk tag/label; sisanya tiket manual

๐Ÿ› ๏ธ Latihan Praktis Kecil (ยฑ20 menit, tanpa cloud)

Semua ini jalan pakai provider local + terraform_data. Tidak butuh akun AWS.

mkdir -p ~/tf-lab && cd ~/tf-lab
# terraform 1.17.1
curl -sSO https://releases.hashicorp.com/terraform/1.17.1/terraform_1.17.1_linux_amd64.zip
python3 -c "import zipfile;zipfile.ZipFile('terraform_1.17.1_linux_amd64.zip').extractall('.')"
chmod +x terraform

Langkah 1 โ€” bikin state, lalu rusak sengaja.

# main.tf
resource "local_file" "cfg" {
  filename = "${path.module}/app.conf"
  content  = "replicas=2\n"
}
./terraform init && ./terraform apply -auto-approve
echo "replicas=9" > app.conf          # drift manual
./terraform plan -detailed-exitcode ; echo "exit=$?"        # harap 2
./terraform plan -refresh=false -detailed-exitcode ; echo "exit=$?"  # harap 0 โ† bohong

Langkah 2 โ€” amati beda tiga mode. Catat output ketiganya. Jelaskan ke diri sendiri kenapa -refresh=false bilang "No changes".

Langkah 3 โ€” rename tanpa destroy. Tambah moved { from = local_file.cfg to = local_file.config } + rename resource. Plan harus 0 to add, 0 to change, 0 to destroy.

Langkah 4 โ€” kunci dari destroy. Tambah lifecycle { destroy = false }, jalankan terraform destroy -auto-approve. Harus 0 destroyed, state list kosong, file app.conf masih ada.

Langkah 5 โ€” ukur sendiri biaya refresh. Bikin for_each 50 local_file, ukur time terraform plan vs time terraform plan -minimal-refresh vs -refresh=false. Itu angka yang bisa kamu kutip โ€” bukan angka dari blog.


โœ… Cek Pemahaman

  1. terraform plan keluar exit=0 di pipeline, tapi seorang engineer melaporkan bucket S3 produksi sudah diubah manual minggu lalu. Sebutkan tiga kemungkinan penyebab dan cara membuktikan masing-masing.

  2. Kamu mau memindahkan 12 resource dari root module ke child module module.storage. Tulis pendekatan yang tidak menghasilkan satu pun destroy, dan jelaskan kenapa moved lebih aman daripada terraform state mv di sini.

  3. Bedakan prevent_destroy, destroy = false pada resource, dan removed { lifecycle { destroy = false } }. Untuk kasus "bucket berisi data audit 7 tahun, tim security mau ambil alih pengelolaan bulan depan", mana yang benar dan kenapa dua lainnya salah.


๐Ÿ”‘ Poin Kunci


โžก๏ธ Eksplorasi Lanjut


Sumber: [1] hashicorp/terraform releases โ€” v1.17.1 (28 Okt 2026), v1.17.0 (21 Okt 2026), v1.16.7 (22 Okt 2026). https://github.com/hashicorp/terraform/releases [2] Terraform CHANGELOG v1.16 โ€” v1.16.0 (26 Agu 2026): lifecycle destroy=false, terraform_data store, import in modules, state show -json, graph -format=mermaid. https://raw.githubusercontent.com/hashicorp/terraform/v1.16/CHANGELOG.md [3] Terraform docs โ€” removed block reference. https://developer.hashicorp.com/terraform/language/block/removed [4] Terraform docs โ€” terraform plan (-detailed-exitcode, -minimal-refresh, -refresh-only). https://developer.hashicorp.com/terraform/cli/commands/plan [5] Terraform docs โ€” S3 backend (use_lockfile, DynamoDB deprecated, IAM minimum). https://developer.hashicorp.com/terraform/language/backend/s3 [6] Terraform docs โ€” terraform state rm (rekomendasi pindah ke removed block). https://developer.hashicorp.com/terraform/cli/commands/state/rm [7] opentofu/opentofu releases โ€” v1.13.9 (5 Nov 2026), v1.13.0 (29 Sep 2026). https://github.com/opentofu/opentofu/releases [8] OpenTofu docs โ€” State and Plan Encryption (pitfalls, key providers, AES-GCM saturation). https://opentofu.org/docs/language/state/encryption/ [9] Verifikasi lokal โ€” Terraform v1.17.1 linux_amd64, removed/moved/destroy=false/drift/terraform test dijalankan langsung; output dikutip apa adanya di atas.