๐ฌ 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:
- Menjelaskan isi file state dan kenapa ia satu-satunya sumber kebenaran mapping resource โ objek nyata.
- Mendeteksi drift secara terprogram di CI (
-detailed-exitcode+ event JSONresource_drift), bukan cuma "liat plan manual". - Mengubah infra hidup tanpa destroy:
movedblock,importblock,removedblock,lifecycle { destroy = false }. - 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
-
terraform plankeluarexit=0di pipeline, tapi seorang engineer melaporkan bucket S3 produksi sudah diubah manual minggu lalu. Sebutkan tiga kemungkinan penyebab dan cara membuktikan masing-masing. -
Kamu mau memindahkan 12 resource dari root module ke child module
module.storage. Tulis pendekatan yang tidak menghasilkan satu pundestroy, dan jelaskan kenapamovedlebih aman daripadaterraform state mvdi sini. -
Bedakan
prevent_destroy,destroy = falsepada resource, danremoved { 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
- State = binding table, bukan dokumentasi. Hilang state = Terraform ingin create ulang semua.
- Drift hanya terdeteksi kalau refresh jalan.
-refresh=false= gate buta;-refresh-only= rekonsiliasi tanpa usul perubahan config. -detailed-exitcode(0/1/2) + event JSONresource_drift= drift detection yang bisa di-CI, tanpa tool tambahan.- Ubah infra hidup tanpa destroy:
moved(rename),import(adopsi),removed(lepas),lifecycle { destroy = false }(kunci). moveddanremovedadalah kode โ masuk PR, ter-review, reproducible.state mv/state rmtidak.destroy = false(1.16+) โprevent_destroy. Yang pertama melepas tracking, yang kedua memblokir plan.- S3 backend:
use_lockfile = truemenggantikan DynamoDB. DynamoDB locking deprecated per docs hari ini. - Terraform core tidak mengenkripsi state. OpenTofu 1.13 punya state encryption (KMS/PBKDF2/OpenBao) โ tapi aktifkan hanya setelah backup + uji DR.
- Plan dibaca sampai habis. Cari
must be replaceddanforces replacement. Itu tempat data hilang.
โก๏ธ Eksplorasi Lanjut
terraform query+listblock โ inventarisasi resource yang belum dikelola Terraform (fitur 1.14+, makin matang di 1.17 dengan Terraform Policy GA). Ini arah "import massal" berikutnya.- Terraform Stacks โ untuk estate multi-account/multi-region; beda model state (per-component, bukan per-workspace). Cek apakah layak untuk skala kamu sebelum menyentuh.
- Policy as code โ Terraform Policy GA di 1.17 (
-policiesdi plan/apply/query) vs OPA/conftest. Trade-off: native = terintegrasi, OPA = portabel lintas tool. - State encryption OpenTofu โ baca bagian "General guidance and pitfalls" sebelum enable; ada jebakan key rotation untuk AES-GCM (key saturation).
- Provider-side drift โ beberapa provider (AWS) punya resource import/refresh yang lebih murah; cek apakah
-minimal-refreshcukup untuk estate kamu atau butuh strategi sharding state.
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.