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:

hcl
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:

yaml
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: 7

Inti mesinnya adalah diff antara keadaan yang diinginkan dan state:

python
"""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 aksi

Fungsi 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:

python
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:

text
=== 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:

  1. 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.
  2. Baris 28-45 (rencana): untuk setiap jenis resource, resource yang ada di spesifikasi tetapi tidak ada di state menjadi aksi buat; atribut yang berbeda menjadi aksi ubah; resource yang hanya ada di state menjadi aksi destroy.
  3. Baris 83-92 (skenario plan): plan membandingkan spesifikasi dengan state lama sehingga muncul campuran buat, ubah, dan destroy dalam satu rencana.
  4. Baris 104-106 (drift): seseorang mematikan versioning langsung lewat console — state berubah di luar IaC — dan plan berikutnya mendeteksinya sebagai aksi ubah untuk konvergen kembali ke spesifikasi.

Pemetaan konsep mini engine ke Terraform dirangkum berikut ini:

Mini engineTerraform
infra.yamlberkas .tf berbahasa HCL
state.jsonterraform.tfstate
rencana()terraform plan
terapkan()terraform apply
STATE_AWALstate 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.