Setelah infrastruktur berdiri, ia bisa menyimpang dari kode karena perubahan manual di console cloud. Di episode ini kita mendeteksi drift dengan terraform plan -detailed-exitcode, mengotomasinya lewat scheduled pipeline, dan merestrukturisasi kode tanpa downtime.

Setelah di episode 17 sebelumnya kita membangun stack infrastruktur multi-tier lengkap — VPC, EKS, RDS, dan S3 yang terenkripsi — pada episode kali ini kita akan menghadapi salah satu musuh terbesar dari semua kode IaC yang sudah kalian tulis: infrastructure drift.
Ini cerita yang sangat umum di dunia nyata. Suatu Senin pagi, tim monitoring melaporkan biaya cloud naik secara misterius. Setelah diselidiki, ternyata ada seorang engineer — dengan niat baik — membuka AWS Console, masuk ke halaman EC2, lalu resize instance produksi dari t3.micro ke t3.medium karena "sepertinya aplikasinya agak lambat". Ia tidak sempat mengubah kode Terraform, tidak membuat Pull Request, dan tidak memberi tahu siapa pun. Esoknya, ketika tim lain menjalankan terraform plan, Terraform menampilkan perubahan yang tidak pernah mereka rencanakan — dan sekarang konfigurasi (kode), state, dan kondisi cloud nyata sudah saling bertentangan.
Fenomena inilah yang disebut infrastructure drift: jarak antara apa yang dideklarasikan di kode, apa yang tercatat di state, dan apa yang benar-benar ada di cloud. Ia terjadi bukan karena kode kalian salah, melainkan karena dunia nyata terus berubah — dan manusia, dengan segala ClickOps-nya, adalah sumber perubahan yang paling tidak terduga.
Mengapa ini penting? Karena inti janji IaC adalah reproducibility: infrastruktur harus bisa dibangun ulang persis dari kode. Selama ada drift, janji itu hancur — dan terraform apply yang tadinya aman bisa tiba-tiba menghapus perubahan yang disengaja, atau malah membiarkan ketidakkonsistenan menumpuk sampai tidak ada yang tahu state sebenarnya. Di episode ini kalian akan belajar mendeteksi drift secara otomatis, memutuskan kapan harus merekonsiliasi atau mengadopsi perubahan, serta merestrukturisasi kode tanpa downtime. Ini adalah keahlian yang membedakan pengguna Terraform dari engineer yang mengoperasikan Terraform.
Dalam sistem Terraform yang sehat, ada tiga sumber kebenaran yang harus selaras:
| Sumber Kebenaran | Isi | Dimana |
|---|---|---|
| Konfigurasi (kode) | State yang diinginkan — deklarasi HCL | Repositori Git |
| State file | Catatan Terraform tentang apa yang ia kelola | Backend S3/GCS/Azure Blob |
| Kondisi nyata | Objek yang sebenarnya hidup di cloud | API Cloud Provider |
Drift terjadi ketika kondisi nyata menyimpang dari apa yang tercatat di state (dan akhirnya dari kode). Sebelum plan atau apply, Terraform selalu melakukan refresh — membaca ulang kondisi objek dari API cloud dan membandingkannya dengan state. Jika ada perbedaan, itu adalah drift. Jika ada perbedaan antara state dan kode, itu adalah perubahan yang direncanakan.
Bayangkan tiga orang membaca satu resep masakan. Kode adalah resep tertulisnya, state adalah catatan si juru masak tentang apa yang sudah ia masak, dan cloud adalah masakan yang benar-benar disajikan. Jika ada tamu yang diam-diam menambahkan garam ke dalam panci (ClickOps), si juru masak tidak akan tahu sampai ia mencicipi lagi — dan begitulah, terraform plan adalah "cicip" itu.
Ada beberapa sumber drift yang umum:
Note
Penting dibedakan: drift (kondisi cloud ≠ state) berbeda dengan perubahan yang direncanakan (state ≠ kode). Yang pertama menandakan infrastruktur "di luar kendali" dan perlu keputusan; yang kedua adalah alur kerja normal yang memang akan diterapkan oleh apply.
Mari kita lihat drift dalam bentuk paling nyata. Ambil contoh sebuah EC2 instance yang dideklarasikan di kode:
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}Suatu hari seorang engineer masuk ke AWS Console, membuka instance ini, lalu mengganti instance type-nya menjadi t3.medium — katakanlah untuk menguji apakah aplikasi menjadi lebih cepat. Ia tidak mengubah kode apa pun. Sekarang terjadi drift: kode berkata t3.micro, cloud berkata t3.medium.
Ketika kalian menjalankan terraform plan, Terraform me-refresh state dari cloud lalu membandingkannya dengan konfigurasi. Hasilnya:
aws_instance.web: Refreshing state... [id=i-0abcd1234efgh5678]
Terraform will perform the following actions:
# aws_instance.web will be updated in-place
~ resource "aws_instance" "web" {
~ instance_type = "t3.medium" -> "t3.micro" # forces replacement
tags = {
"Name" = "web-server"
}
~ vpc_security_group_ids = [
+ "sg-0abc12345def67890",
]
}
Plan: 0 to add, 1 to change, 0 to destroy.Perhatikan tanda ~ di depan instance_type: itu berarti Terraform menemukan perbedaan antara kondisi cloud nyata (t3.medium) dan keinginan kode (t3.micro). Tanda + pada security group berarti ada security group yang ditambahkan manual di console yang tidak ada di kode. Ini adalah drift — dan plan berhasil "menangkap basah" mereka berdua.
Important
Perhatikan juga komentar # forces replacement pada perubahan instance_type untuk sebagian jenis resource. Artinya: jika kalian menjalankan apply untuk merekonsiliasi perubahan kecil di console, Terraform justru bisa menghancurkan dan membuat ulang resource tersebut — yang di EC2 berarti instance baru dengan IP baru dan data ephemeral hilang. Inilah mengapa keputusan "reconcile vs adopt" tidak boleh diambil dengan tergesa-gesa (detail lebih lanjut di bagian remediasi).
plan, refresh, dan plan -refresh-onlyCara paling dasar mendeteksi drift adalah menjalankan terraform plan secara berkala. Tapi ada nuansa penting: dalam alur normal, plan tidak hanya mendeteksi — ia juga menghitung apa yang harus diubah untuk menutup drift. Ada tiga varian yang perlu kalian kuasai:
| Perintah | Fungsi | Catatan |
|---|---|---|
terraform plan | Deteksi drift + susun rencana perubahan | Standar, menampilkan aksi yang akan dilakukan |
terraform refresh | Perbarui state sesuai kondisi nyata di cloud | Legacy — tidak menampilkan rencana, berisiko menutupi drift |
terraform plan -refresh-only | Tampilkan perubahan state saja, tanpa perubahan resource | Aman untuk inspeksi; tidak mengubah resource di cloud |
terraform refresh adalah warisan Terraform lama yang kini tidak direkomendasikan untuk penggunaan rutin. Masalahnya: refresh menulis ulang state agar cocok dengan cloud — artinya ia justru menyembunyikan drift dengan menganggap kondisi cloud sebagai kebenaran. Itu berguna dalam kasus tertentu (misal mengadopsi perubahan yang disengaja), tapi kalau dijalankan asal-asalan, kalian kehilangan kesempatan melihat perbedaan. Gantinya, gunakan terraform plan -refresh-only yang menunjukkan perubahan state secara eksplisit dan aman untuk ditinjau:
terraform plan -refresh-onlyOutputnya akan menampilkan perubahan yang terjadi pada state (misal instance_type berubah di state), tanpa ada niat mengubah resource di cloud.
terraform plan -detailed-exitcode: Bahasa Mesin untuk DriftMasalah dari terraform plan biasa: ia mengembalikan exit code 0 ketika sukses — baik ketika ada perubahan maupun tidak. Manusia bisa membaca output, tapi mesin (pipeline CI/CD, scheduler) tidak bisa membedakannya. Di situlah -detailed-exitcode hadir.
terraform plan -detailed-exitcode
echo "Exit code: $?"Dengan flag ini, Terraform mengembalikan salah satu dari tiga exit code:
| Exit Code | Arti | Makna Operasional |
|---|---|---|
0 | Tidak ada perubahan — infrastruktur selaras dengan kode | Bersih: tidak ada drift |
1 | Terjadi error (syntax, kredensial, backend, dll.) | Gagal: investigasi diperlukan |
2 | Ada perubahan yang perlu diterapkan | Drift (atau perubahan yang direncanakan): perlu keputusan |
Tip
Exit code 2 tidak selalu berarti drift berbahaya — ia juga muncul ketika kalian memang sedang merencanakan perubahan kode yang baru. Kuncinya ada pada konteks: di Pull Request, exit code 2 adalah hal yang wajar; di job terjadwal yang seharusnya "tidak ada yang berubah", exit code 2 adalah alarm drift. Gunakan -detailed-exitcode bersamaan dengan -out=tfplan agar rencana bisa disimpan dan direview: terraform plan -detailed-exitcode -out=tfplan.
Deteksi drift secara manual berarti mengandalkan disiplin seseorang untuk ingat menjalankan terraform plan setiap hari — dan disiplin manusia adalah pengaman yang paling mudah bocor. Solusi yang benar adalah scheduled pipeline: sebuah job di CI/CD yang berjalan otomatis setiap malam, menjalankan terraform plan -detailed-exitcode, dan melaporkan status drift.
Berikut contoh GitHub Actions yang menjalankan deteksi drift setiap hari pukul 02.00 pagi untuk environment production:
name: Drift Detection
on:
schedule:
- cron: '0 2 * * *' # Setiap hari 02.00 UTC
workflow_dispatch: # Bisa dipicu manual
permissions:
id-token: write
contents: read
issues: write
jobs:
detect-drift:
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra/environments/production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.9.0"
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gh-oidc-terraform-prod
aws-region: ap-southeast-1
- name: Terraform Init
run: terraform init
- name: Terraform Plan (deteksi drift)
id: plan
run: |
set +e
terraform plan -detailed-exitcode -out=tfplan
code=$?
set -e
echo "terraform_exit_code=$code" >> "$GITHUB_OUTPUT"
if [ "$code" -eq 2 ]; then
echo "drift_found=true" >> "$GITHUB_OUTPUT"
else
echo "drift_found=false" >> "$GITHUB_OUTPUT"
fi
- name: Laporkan drift ke issue
if: steps.plan.outputs.drift_found == 'true'
uses: actions/github-script@v7
with:
script: |
const body = `Drift terdeteksi di \`production\` pada \`${new Date().toISOString()}\`.
Jalankan \`terraform plan\` di direktori \`infra/environments/production\`
untuk melihat detail perubahan, lalu putuskan: **reconcile** (apply)
atau **adopt** (ubah kode). Jangan biarkan drift menumpuk!`;
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: "🚨 Drift terdeteksi: production",
body: body
})Beberapa detail yang perlu kalian pahami:
cron: '0 2 * * *' — menjadwalkan job di dini hari, saat lalu lintas perubahan tim rendah sehingga notifikasi drift tidak tenggelam.set +e / set -e — menonaktifkan exit-on-error sementara agar kita bisa menangkap exit code 2 tanpa membuat job langsung gagal. Tanpa ini, terraform plan yang mengembalikan 2 akan menghentikan workflow seketika.drift_found sebagai output — status drift diekspos sebagai variabel agar step berikutnya bisa bereaksi: membuat issue, mengirim notifikasi Slack, atau menandai PR.role-to-assume) — job berjalan passwordless dengan kredensial ephemeral (episode 13), jadi tidak ada Access Key yang tersimpan di secret workflow.Warning
Pada job drift detection, jangan pernah menjalankan terraform apply otomatis. Apply otomatis untuk menutup drift bisa menghancurkan perubahan yang disengaja — termasuk perubahan yang sengaja dibuat di console karena alasan emergency. Deteksi otomatis harus selalu berakhir dengan laporan ke manusia, dan remediasi tetap melalui alur review yang normal.
Saat drift terdeteksi, kalian menghadapi dua pilihan yang masing-masing punya arah berlawanan:
| Pilihan | Arah Perubahan | Kapan Digunakan |
|---|---|---|
| Rekonsiliasi (Reconcile) | Ubah cloud agar sesuai kode → terraform apply | Perubahan di console tidak disengaja / melanggar standar |
| Adopsi (Adopt) | Ubah kode agar sesuai cloud → edit HCL → apply | Perubahan di console memang disengaja dan layak dipertahankan |
Rekonsiliasi dilakukan ketika perubahan di console adalah kesalahan atau tidak diinginkan — misalnya resize instance yang ternyata membuat biaya membengkak. Cukup jalankan terraform apply, dan Terraform mengembalikan resource ke bentuk yang dideklarasikan di kode:
terraform plan -detailed-exitcode -out=tfplan # pastikan drift yang terdeteksi memang yang diinginkan
terraform apply tfplanAdopsi dilakukan ketika perubahan di console ternyata keputusan yang tepat (misal security group baru yang memang dibutuhkan, atau instance type baru yang memang lebih cocok). Maka kalian menulis perubahan itu kembali ke dalam kode — sehingga kode kembali menjadi satu-satunya sumber kebenaran, dan apply tidak lagi mencoba menutup drift:
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"Adopsi ini harus lewat alur normal: ubah kode → buka Pull Request → direview → di-merge → pipeline apply (episode 13). Dengan begitu, perubahan tetap tercatat di Git dan bisa diaudit — bukan menjadi rahasia yang hilang di dalam console.
Important
Aturan emas remediasi drift: jangan pernah menutup drift dengan terraform refresh atau state rm hanya agar "plan-nya bersih". Itu menipu sistem — drift tidak hilang, ia hanya dipindahkan ke dalam state yang kemudian akan menimpa kode kalian berikutnya. Pilihan yang jujur hanya dua: ubah cloud-nya (reconcile) atau ubah kodenya (adopt).
Selain drift dari luar, ada jenis "perubahan" lain yang sama pentingnya: refactoring kode — merapikan struktur, mengganti nama, atau memindahkan resource ke dalam modul. Naifnya, jika kita hanya mengubah nama resource di kode, Terraform akan menganggap resource lama hilang dan membuat yang baru — destroy dan recreate, sesuatu yang memicu downtime dan kehilangan data.
Untungnya, semua mekanisme yang kalian butuhkan sudah kalian pelajari di episode 7 dan 8. Mari kita rangkum tiga senjata utama refactoring tanpa downtime:
moved Block: Refactoring yang Tertulis di KodeKetika kalian mengganti nama resource atau memindahkannya ke dalam modul, blok moved memberi tahu Terraform: "objek di address lama sekarang hidup di address baru — perlakukan keduanya sebagai hal yang sama."
moved {
from = aws_instance.web
to = aws_instance.nginx
}Saat terraform plan, output akan menampilkan Moved: aws_instance.web → aws_instance.nginx dengan Plan: 0 to add, 0 to change, 0 to destroy — tidak ada penghancuran, hanya relokasi di dalam state. Setelah apply sukses, blok moved boleh dihapus dari kode.
terraform state mv: Refactoring Langsung di TerminalUntuk restrukturisasi besar — misalnya memindahkan puluhan resource ke dalam modul sekaligus — terraform state mv bekerja langsung di terminal tanpa perlu mengubah kode dulu:
terraform state mv 'module.web.aws_instance.app' 'aws_instance.legacy'Ingat: state mv hanya memindahkan di dalam state — kode konfigurasi juga harus diperbarui ke address baru setelahnya, jika tidak plan berikutnya akan mengira resource lama hilang.
lifecycle.create_before_destroy: Ganti Resource Tanpa Jeda LayananAda kalanya refactoring mengharuskan penggantian resource (misal mengubah AMI yang tidak kompatibel, atau mengubah atribut yang forces replacement). Untuk memastikan tidak ada jeda layanan, atur lifecycle agar resource baru dibuat sebelum yang lama dihancurkan:
resource "aws_instance" "web" {
ami = "ami-0updated123456789"
instance_type = "t3.medium"
lifecycle {
create_before_destroy = true
}
}Dengan create_before_destroy = true, Terraform membuat instance baru terlebih dahulu, menunggu hingga siap, baru menghancurkan yang lama — mengikuti urutan yang dijamin dependency graph. Ini adalah mekanisme inti di balik strategi blue-green dan rolling replacement di level resource.
moved {
from = aws_instance.web
to = aws_instance.nginx
}Tip
Pilihannya sederhana: moved block untuk refactoring yang merupakan bagian dari perubahan kode yang akan di-review (default project modern), terraform state mv untuk aksi state yang bersifat sekali jalan atau melibatkan pemindahan antar state file. Keduanya mencegah destroy/recreate — dan karenanya mencegah downtime.
| Kesalahan | Dampak | Solusi |
|---|---|---|
| Mengabaikan drift "kecil" | Perbedaan menumpuk sampai plan raksasa yang sulit direview | Deteksi otomatis harian + remediasi cepat |
Menutup drift dengan refresh/state rm | Drift disembunyikan, kode tidak sinkron, audit basi | Reconcile (apply) atau adopt (ubah kode) |
| Apply otomatis di drift detection | Menghancurkan perubahan manual yang disengaja | Deteksi hanya melapor; remediasi via review |
| Hanya mengandalkan plan manual | Drift dibiarkan lama karena manusia lupa | Scheduled pipeline + notifikasi otomatis |
Mengubah nama resource tanpa moved | Destroy + recreate = downtime | Gunakan moved/state mv (episode 8) |
Lupa memperbarui kode setelah state mv | Plan menampilkan to destroy untuk address lama | Pasangkan setiap state mv dengan edit kode |
Recreate resource kritis tanpa create_before_destroy | Jeda layanan saat penggantian resource | Atur lifecycle create_before_destroy |
Pada episode 18 ini kalian telah memahami infrastructure drift — jarak antara kode, state, dan kondisi cloud nyata akibat perubahan manual di console — dan tiga cara mengelolanya: mendeteksi dengan terraform plan (dan plan -refresh-only), mengotomatiskan deteksi dengan terraform plan -detailed-exitcode di scheduled pipeline, serta memutuskan remediasi antara reconcile dan adopt. Kalian juga mengulang dan menguatkan teknik refactoring tanpa downtime menggunakan moved block, terraform state mv, dan lifecycle.create_before_destroy.
Poin kunci yang perlu kalian bawa pulang:
0/1/2 dari plan -detailed-exitcode adalah bahasa mesin yang bisa dipakai scheduler untuk memberi alarm.Drift adalah pertanda bahwa "kehidupan berjalan" di infrastruktur kalian. Tapi ada satu ancaman yang jauh lebih menakutkan daripada drift: state file yang corrupt atau hilang total. Ketika itu terjadi, Terraform kehilangan ingatannya — dan seisi infrastruktur berubah menjadi pertanyaan besar: "apakah resource ini masih ada?"
Di episode 19 selanjutnya kita akan membahas Disaster Recovery (DR) & State Recovery — prosedur pemulihan saat state corrupt atau hilang, strategi backup & versioning di remote storage, serta operasi terraform state pull dan terraform state push untuk pemulihan manual yang aman. Pastikan tetap semangat!