Infrastructure as Code (IaC) artinya server dan semua perlengkapannya ditulis sebagai file teks, lalu sebuah tool membuat kenyataannya sama dengan file itu. Bayangin denah rumah dan kontraktor yang membawa buku catatan. Kamu cuma mengubah denahnya. Kontraktor membandingkan denah dengan rumah yang berdiri, lalu mengerjakan selisihnya saja: tambah kamar berarti bangun, ganti warna cat berarti cat ulang di tempat, tapi pindah fondasi berarti bongkar lalu bangun lagi. Buku catatannya mencatat apa saja yang sudah dia bangun. Tool IaC paling populer untuk membuat resource cloud adalah Terraform: denahnya file .tf, kontraktornya terraform, buku catatannya state.
web-1 dan web-2 dibuat dengan tangan lewat dashboard provider (pelajaran VPS Setup). Tiga bulan lagi, siapa yang ingat ukurannya, region-nya, atau SSH key mana yang dipasang? Dengan Terraform, semua itu ada di file yang masuk Git: bisa dibuat ulang persis, punya riwayat, dan perubahannya di-review lewat PR seperti kode biasa. Contoh di sini memakai DigitalOcean; di AWS, GCP, atau Hetzner bentuknya sama, cuma nama resource-nya yang beda.
🧪 Tebakan cepat: kamu mengubah
sizeweb-1 daris-1vcpu-1gbkes-2vcpu-2gb. Terraform mengubah server yang ada, atau membuat server baru? Kalauregiondarisgp1keblr1? Jawabannya ada di bagian "Update in-place atau replace".
web-1 dalam Terraform
provider "digitalocean" {} # token dari env DIGITALOCEAN_TOKEN
resource "digitalocean_droplet" "web_1" {
name = "web-1"
region = "sgp1"
size = "s-1vcpu-1gb"
image = "ubuntu-24-04-x64"
resize_disk = false # CPU dan RAM saja, bisa diturunkan lagi
ssh_keys = ["3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa"]
tags = ["catatan-api"]
}
output "web_1_ip" {
value = digitalocean_droplet.web_1.ipv4_address
}
- Bahasanya HCL (HashiCorp Configuration Language): blok, nama, lalu
kunci = nilai. provideradalah plugin yang tahu cara bicara dengan API DigitalOcean. Token-nya dari environment variable, jangan pernah ditulis di file. Di atasnya biasanya ada blokterraform { required_providers { ... } }yang mengunci versi provider (lengkapnya di Lab).resource "digitalocean_droplet" "web_1": jenis resource lalu nama lokalnya. Alamat lengkapnyadigitalocean_droplet.web_1.- Isinya deklaratif: kamu menulis keadaan akhir yang diinginkan, bukan langkah-langkah membuatnya.
outputmencetak nilai yang baru diketahui setelah server dibuat, seperti IP-nya.
init, plan, apply
| Perintah | Yang terjadi |
|---|---|
terraform init |
mengunduh provider dan menyiapkan tempat state. Sekali per folder. |
terraform plan |
mengecek keadaan nyata (refresh), membandingkan dengan file, lalu mencetak rencana. Gak mengubah apa pun. |
terraform apply |
mencetak rencana yang sama, menunggu kamu mengetik yes, lalu menjalankannya dan mencatat hasilnya di state. |
terraform destroy |
menghapus semua resource yang tercatat di state. |
web-1 sudah ada sebelum file ini ditulis. Tanpa langkah tambahan, apply akan membuat droplet kedua. Jadi masukkan dulu yang sudah ada ke state dengan terraform import digitalocean_droplet.web_1 412345678 (id droplet-nya), lalu pastikan terraform plan bilang No changes. Setelah itu baru ubah file.
Ini apply setelah size diubah ke s-2vcpu-2gb, bagian akhirnya:
Enter a value: yes
digitalocean_droplet.web_1: Modifying... [id=412345678]
digitalocean_droplet.web_1: Modifications complete after 58s [id=412345678]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Outputs:
web_1_ip = "203.0.113.10"
Update in-place atau replace
Plan memberi tanda di depan tiap resource. Mengubah size:
# digitalocean_droplet.web_1 will be updated in-place
~ resource "digitalocean_droplet" "web_1" {
id = "412345678"
name = "web-1"
~ size = "s-1vcpu-1gb" -> "s-2vcpu-2gb"
tags = [
"catatan-api",
]
# (21 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
~ artinya update in-place: server yang sama di-resize, id dan IP-nya tetap. Mengubah region (potongan plan-nya, versi lengkapnya di Lab):
# digitalocean_droplet.web_1 must be replaced
-/+ resource "digitalocean_droplet" "web_1" {
~ id = "412345678" -> (known after apply)
~ ipv4_address = "203.0.113.10" -> (known after apply)
~ region = "sgp1" -> "blr1" # forces replacement
}
Plan: 1 to add, 0 to change, 1 to destroy.
-/+ artinya replace: server lama dihapus, lalu server baru dibuat. # forces replacement menunjuk atribut penyebabnya. Jadi jawaban tebakannya: size mengubah server yang ada, region membuat server baru. Droplet gak bisa pindah region atau ganti image di tempat. Akibatnya besar: IP baru (DNS catatan.example.com harus ikut diganti), dan isi disk hilang, termasuk volume Postgres di dalamnya. Selalu baca baris Plan: sebelum mengetik yes; + saja berarti create, - saja berarti destroy.
State: buku catatan Terraform
terraform.tfstate adalah file JSON yang mencatat resource mana di file yang terhubung ke benda nyata mana: digitalocean_droplet.web_1 adalah droplet 412345678 dengan IP 203.0.113.10. Tanpa state, Terraform gak tahu server itu miliknya, dan akan membuat yang baru. Dua aturan:
- Jangan commit state ke Git. Isinya bisa memuat password database dan token sebagai teks biasa.
- Simpan di remote backend dengan locking begitu kalian lebih dari satu orang. State di laptop gak bisa dipakai bersama dan bisa hilang, dan dua
applybersamaan bisa merusaknya. Dengan backend dan lock, apply kedua berhenti diError acquiring the state locksampai yang pertama selesai.
terraform {
backend "s3" {
bucket = "catatan-terraform-state"
key = "prod/web.tfstate"
region = "ap-southeast-1"
encrypt = true
use_lockfile = true # lock di bucket, Terraform 1.10 ke atas
}
}
Versi lama memakai tabel DynamoDB (dynamodb_table) untuk lock. Pilihan lain: bucket yang kompatibel S3 seperti DigitalOcean Spaces, atau HCP Terraform.
Drift: diubah di luar Terraform
Saat traffic melonjak, Dewi me-resize web-1 ke s-2vcpu-4gb lewat dashboard. File dan state masih bilang s-2vcpu-2gb. Itu drift: kenyataan gak lagi sama dengan catatan. terraform plan berikutnya me-refresh dulu dan melaporkannya (dipotong):
Note: Objects have changed outside of Terraform
# digitalocean_droplet.web_1 has changed
~ resource "digitalocean_droplet" "web_1" {
~ memory = 2048 -> 4096
~ size = "s-2vcpu-2gb" -> "s-2vcpu-4gb"
}
Lalu plan-nya berencana mengembalikan web-1 ke s-2vcpu-2gb, karena file adalah sumber kebenarannya. Dua pilihan: apply untuk mengembalikan, atau tulis s-2vcpu-4gb di file supaya plan bilang No changes. Jangan apply tanpa membaca plan. Banyak tim menjalankan terraform plan terjadwal di CI untuk menangkap drift.
Plan itu diff
Inti plan adalah membandingkan keadaan sekarang dengan yang diinginkan:
function planChanges(currentState, desiredState) {
const creates = [];
const updates = [];
const destroys = [];
for (const name in desiredState) {
if (!currentState[name]) {
creates.push({ action: "create", resource: name, config: desiredState[name] });
} else if (JSON.stringify(currentState[name]) !== JSON.stringify(desiredState[name])) {
const changes = {};
for (const key in desiredState[name]) {
if (currentState[name][key] !== desiredState[name][key]) changes[key] = desiredState[name][key];
}
updates.push({ action: "update", resource: name, changes });
}
}
for (const name in currentState) {
if (!desiredState[name]) destroys.push({ action: "destroy", resource: name });
}
return [...creates, ...updates, ...destroys];
}
| current | desired | Hasil |
|---|---|---|
{ server1: { type: "t3.micro" } } |
{ server1: { type: "t3.small" }, server2: { type: "t3.micro" } } |
create server2 dengan config, lalu update server1 dengan changes: { type: "t3.small" } |
{ db: { size: "10GB" }, cache: { size: "1GB" } } |
{ db: { size: "10GB" } } |
destroy cache; db sama, dilewati |
- Ada di desired tapi gak di current: create, membawa seluruh
config. - Ada di keduanya tapi beda: update,
changesberisi atribut yang berubah saja, dengan nilai barunya. - Ada di current tapi gak di desired: destroy. Yang identik dilewati.
- Hasilnya diurutkan: semua create, lalu update, lalu destroy.
Terraform dan Ansible
Terraform membuat dan mengubah resource lewat API provider: server, DNS, database, bucket. Ini provisioning. Ansible masuk ke dalam server lewat SSH dan mengatur isinya: paket, file config, service. Ini configuration management. Keduanya idempotent: dijalankan sepuluh kali, hasil akhirnya sama dengan sekali, karena keduanya mengecek keadaan dulu dan cuma mengerjakan selisihnya. Alternatif Terraform: OpenTofu (turunan open source-nya) dan Pulumi (ditulis dengan TypeScript atau Python).
⚠️ Jebakan yang sering ketemu
- Mengetik
yestanpa membaca plan. Satu-/+di server database berarti datanya hilang. Lindungi resource penting denganlifecycle { prevent_destroy = true }. - State di Git atau cuma di laptop. Rahasia bocor, dan apply dari dua orang saling menimpa. Pakai remote backend dengan locking dan enkripsi.
- Memperbaiki lewat dashboard lalu lupa. Apply berikutnya mengembalikan perubahan itu tanpa pamit. Tulis perubahan di file, atau baca drift di plan.
- Di latihan: urutan hasil atau isi
changes. Semua create dulu, lalu update, lalu destroy.changescuma memuat atribut yang beda, dan resource yang identik gak masuk hasil.
TL;DR: IaC menulis infrastruktur sebagai file yang masuk Git, jadi bisa dibuat ulang, punya riwayat, dan di-review. Terraform (HCL, deklaratif) membaca provider dan resource, lalu init mengunduh provider, plan me-refresh dan mencetak rencana tanpa mengubah apa pun, apply menjalankannya setelah yes, destroy menghapus semuanya. ~ berarti update in-place (size: server sama, IP sama), -/+ berarti replace karena atribut # forces replacement (region, image: server baru, IP baru, disk hilang). State mencatat hubungan resource dengan benda nyatanya: jangan di-commit, simpan di remote backend dengan locking. Drift muncul saat ada yang mengubah di luar Terraform, dan plan melaporkannya sebagai "Objects have changed outside of Terraform". Plan pada dasarnya diff: create, lalu update dengan atribut yang berubah, lalu destroy. Terraform untuk provisioning, Ansible untuk mengatur isi server.