Minggu lalu seorang engineer TokoKita membuat bucket baru lewat konsol web; tiga hari kemudian tidak ada yang ingat pengaturan lengkapnya, dan lingkungan staging berbeda dari production tanpa yang menyadarinya. Mengubah infrastruktur lewat konsol memang cepat sekali, tetapi tidak meninggalkan riwayat, tidak bisa direview, dan tidak bisa diulang. Infrastructure as Code (IaC) memindahkan seluruh definisi infrastruktur ke berkas deklaratif di Git: bucket, topic, dan kebijakannya ditulis, direview, lalu diterapkan oleh mesin. Chapter ini membedah konsep intinya — state, plan, apply, dan drift — lewat Terraform dan mini IaC engine yang bisa kita jalankan lokal.
Infrastruktur sebagai Kode #
Pendekatan deklaratif berbeda dari skrip: alih-alih menulis langkah “buat bucket lalu aktifkan versioning”, kita menuliskan keadaan akhir yang diinginkan dan biarkan mesin menghitung cara mencapainya. Terraform adalah tool IaC paling umum, dengan bahasa HCL. Contoh berikut mendefinisikan bucket S3 beserta versioning untuk TokoKita:
resource "aws_s3_bucket" "data_toko_raw" {
bucket = "data-toko-raw"
tags = {
Proyek = "TokoKita"
Lingkungan = "production"
}
}
resource "aws_s3_bucket_versioning" "data_toko_raw" {
bucket = aws_s3_bucket.data_toko_raw.id
versioning_configuration {
status = "Enabled"
}
}Blok resource menyatakan keadaan akhir: sebuah bucket bernama data-toko-raw dan versioning aktif. Sintaks aws_s3_bucket.data_toko_raw.id merujuk hasil resource pertama, sehingga urutan pembuatan diurus Terraform. Lingkungan penulisan tidak memasang binary terraform, jadi blok HCL di atas tidak dieksekusi; sintaksnya mengikuti dokumentasi resmi di https://developer.hashicorp.com/terraform/language/resources/syntax
. Alur kerja Terraform terdiri dari tiga perintah: terraform plan membandingkan spesifikasi dengan state yang tercatat, terraform apply mengeksekusi rencananya, dan berkas terraform.tfstate menyimpan keadaan terakhir yang diketahui.
Mini IaC Engine: Spec, State, Plan, Apply #
Untuk melihat ketiga konsep itu bekerja tanpa akun cloud, kita bangun mesin IaC mini: spesifikasi deklaratif dalam infra.yaml, keadaan terkini dalam state.json, dan fungsi rencana() yang membandingkan keduanya. Spesifikasi TokoKita berisi dua bucket dan satu topic Kafka:
region: ap-southeast-3
buckets:
- nama: data-toko-raw
versioning: true
- nama: data-toko-curated
versioning: true
topics:
- nama: pesanan-events
partisi: 6
retensi_hari: 7Inti mesinnya adalah diff antara keadaan yang diinginkan dan state:
"""Mini IaC engine: bandingkan spesifikasi deklaratif (infra.yaml) dengan
state (state.json), buat rencana, lalu terapkan. Pemetaan konsep ke Terraform:
infra.yaml = berkas .tf, state.json = terraform.tfstate, rencana = terraform plan.
"""
import json
import pathlib
import yaml
AKAR = pathlib.Path(__file__).resolve().parent
JENIS = [("bucket", "buckets"), ("topic", "topics")]
# State awal: hasil deployment lama (disetor ulang tiap run agar idempoten).
STATE_AWAL = {"buckets": {
"data-toko-legacy": {"nama": "data-toko-legacy", "versioning": False},
"data-toko-raw": {"nama": "data-toko-raw", "versioning": False},
}, "topics": {}}
def muat_spec():
return yaml.safe_load((AKAR / "infra.yaml").read_text())
def muat_state():
return json.loads((AKAR / "state.json").read_text())
def simpan_state(state):
(AKAR / "state.json").write_text(json.dumps(state, indent=2, sort_keys=True) + "\n")
def rencana(spec, state):
"""Diff keadaan yang diinginkan vs state -> daftar aksi."""
aksi = []
for jenis, kunci in JENIS:
ingin = {item["nama"]: item for item in spec.get(kunci, [])}
nyata = state.get(kunci, {})
for nama in sorted(ingin):
if nama not in nyata:
aksi.append({"tipe": "buat", "jenis": jenis, "nama": nama,
"atribut": ingin[nama]})
else:
for k, v in ingin[nama].items():
if k != "nama" and nyata[nama].get(k) != v:
aksi.append({"tipe": "ubah", "jenis": jenis, "nama": nama,
"atribut": k, "dari": nyata[nama].get(k), "ke": v})
for nama in sorted(nyata):
if nama not in ingin:
aksi.append({"tipe": "destroy", "jenis": jenis, "nama": nama})
return aksiFungsi cetak_rencana dan terapkan (diringkas di sini) mencetak rencana ala terraform plan, memutasi state sesuai daftar aksi, lalu menyetornya kembali. Skenario lengkapnya dijalankan seperti ini:
print("=== Mini IaC: plan ===")
simpan_state(STATE_AWAL)
spec, state = muat_spec(), muat_state()
print("Spesifikasi : region {}, {} bucket, {} topic".format(
spec["region"], len(spec["buckets"]), len(spec["topics"])))
print("State saat ini: {} bucket, {} topic".format(
len(state["buckets"]), len(state["topics"])))
print()
aksi = rencana(spec, state)
cetak_rencana(aksi)
print()
print("=== Mini IaC: apply ===")
terapkan(aksi, state)
print()
print("=== Mini IaC: plan ulang setelah apply ===")
cetak_rencana(rencana(muat_spec(), muat_state()))
print()
print("=== Drift: versioning dimatikan langsung lewat console ===")
state_drift = muat_state()
state_drift["buckets"]["data-toko-curated"]["versioning"] = False
simpan_state(state_drift)
print()
print("=== Mini IaC: plan setelah drift ===")
cetak_rencana(rencana(muat_spec(), muat_state()))Output yang Dihasilkan:
=== Mini IaC: plan ===
Spesifikasi : region ap-southeast-3, 2 bucket, 1 topic
State saat ini: 2 bucket, 0 topic
+ bucket data-toko-curated akan dibuat (versioning=true)
~ bucket data-toko-raw akan diubah: versioning false -> true
- bucket data-toko-legacy akan di-destroy
+ topic pesanan-events akan dibuat (partisi=6, retensi_hari=7)
Rencana: 2 dibuat, 1 diubah, 1 di-destroy.
=== Mini IaC: apply ===
+ bucket data-toko-curated dibuat
~ bucket data-toko-raw diubah: versioning -> true
- bucket data-toko-legacy di-destroy
+ topic pesanan-events dibuat
=== Mini IaC: plan ulang setelah apply ===
Tidak ada perubahan. Infrastruktur sesuai spesifikasi.
=== Drift: versioning dimatikan langsung lewat console ===
=== Mini IaC: plan setelah drift ===
~ bucket data-toko-curated akan diubah: versioning false -> true
Rencana: 0 dibuat, 1 diubah, 0 di-destroy.Mari kita telaah baris-baris kode di atas:
- Baris 14-17 (
STATE_AWAL): state deployment lama — bucket legacy yang sudah tidak dipakai dan bucket raw tanpa versioning; disetor ulang tiap run agar simulasi idempoten. - Baris 28-45 (
rencana): untuk setiap jenis resource, resource yang ada di spesifikasi tetapi tidak ada di state menjadi aksibuat; atribut yang berbeda menjadi aksiubah; resource yang hanya ada di state menjadi aksidestroy. - Baris 83-92 (skenario plan):
planmembandingkan spesifikasi dengan state lama sehingga muncul campuran buat, ubah, dan destroy dalam satu rencana. - Baris 104-106 (drift): seseorang mematikan versioning langsung lewat console — state berubah di luar IaC — dan
planberikutnya mendeteksinya sebagai aksiubahuntuk konvergen kembali ke spesifikasi.
Pemetaan konsep mini engine ke Terraform dirangkum berikut ini:
| Mini engine | Terraform |
|---|---|
infra.yaml | berkas .tf berbahasa HCL |
state.json | terraform.tfstate |
rencana() | terraform plan |
terapkan() | terraform apply |
STATE_AWAL | state hasil import atau deployment sebelumnya |
Drift: Ketika Realita Menyimpang #
Drift adalah perbedaan antara infrastruktur nyata dan definisi deklaratifnya — biasanya akibat perubahan manual lewat konsol, misalnya perbaikan darurat di luar jam kerja yang lupa dilaporkan. plan berfungsi sebagai pendeteksi drift: bagian akhir keluaran di atas menunjukkan versioning yang dimatikan manual akan dikembalikan ke true pada apply berikutnya. Praktik terbaiknya: jalankan plan terjadwal di CI dan perlakukan setiap perubahan manual sebagai insiden yang harus dipulihkan ke spesifikasi atau diresmikan lewat Git.
Infrastruktur kini dideklarasikan sebagai berkas — tetapi berkas yang sama harus melayani lingkungan development, staging, dan production dengan nilai berbeda. Strategi pemisahannya dibahas pada chapter Environment Development dan Production.