Episode pamungkas: merangkai seluruh konsep dari episode 0-19 menjadi satu arsitektur IaC enterprise yang terpadu — modular + Terragrunt, remote backend, OIDC, Vault, OPA, dan drift detection — lengkap dengan checklist kesiapan produksi dan panduan code review.

Setelah di episode 19 sebelumnya kita membahas disaster recovery dan pemulihan state — menyelamatkan "memori" Terraform saat yang terburuk terjadi — pada episode kali ini, episode terakhir dari seri Belajar Terraform, kita akan merangkai seluruh perjalanan kalian menjadi satu kesatuan utuh: arsitektur IaC enterprise production-grade.
Perjalanan kita dimulai dari episode 0 dengan pertanyaan yang sangat sederhana: "apa itu Infrastructure as Code dan mengapa dunia butuh Terraform?" Dua puluh episode kemudian, kalian tidak lagi bertanya apa — melainkan bagaimana merancang sistem yang hidup, aman, dan bisa dioperasikan di skala organisasi. Di antara keduanya, kalian telah belajar menulis HCL, mengelola state, membuat modul, mengotomasi pipeline, mengamankan secret, menegakkan policy, dan menyelamatkan state dari kebakaran.
Semua keterampilan itu terasa terpisah-pisah selama ini. Tapi di dunia kerja nyata, mereka berjalan sebagai satu sistem. Bayangkan sebuah perusahaan dengan puluhan engineer, ratusan repo, dan ribuan resource cloud. Tanpa arsitektur yang terpadu, masing-masing tim akan membuat "cara sendiri": satu tim memakai local state, tim lain hardcode secret di kode, tim lain lagi melewatkan review. Kekacauan yang dihasilkan bukan soal teknis semata — ia menjadi risiko bisnis.
Episode 20 ini adalah puncak gunung es. Kita akan merancang satu arsitektur enterprise end-to-end yang menyatukan modular architecture + Terragrunt, remote S3/DynamoDB backend, autentikasi passwordless OIDC di GitHub Actions, secret dari HashiCorp Vault, policy gate OPA, dan drift detection otomatis. Lalu kita tutup dengan checklist kesiapan produksi, panduan code review, dan rekap perjalanan lengkap dari episode 0. Ini bukan episode tentang fitur — ini episode tentang cara berpikir seorang platform engineer.
Sebelum melihat kodenya, pahami dulu alurnya secara keseluruhan. Bayangkan seorang engineer ingin mengubah infrastruktur produksi. Alur lengkapnya:
fmt → validate → tflint → security scan (Checkov/Trivy, episode 11) → terraform plan → hasil plan diposting ke PR.apply berjalan — masuk ke cloud tanpa password melalui OIDC (episode 13), mengambil secret dari HashiCorp Vault saat runtime (episode 15).plan -detailed-exitcode dan melaporkan bila infrastruktur menyimpang.Semua alur ini ditopang oleh struktur modular + Terragrunt (episode 9 & 12) yang menjaga kode tetap DRY antar environment. Mari kita petakan setiap komponen ke episode asalnya:
| Lapisan | Komponen | Episod |
|---|---|---|
| Struktur | Modular architecture + Terragrunt / directory structure | 9, 10, 12 |
| State | Remote backend S3 + DynamoDB locking + versioning | 5, 19 |
| Otentikasi | Passwordless OIDC di GitHub Actions | 13 |
| Secret | HashiCorp Vault sebagai source of secrets | 15 |
| Policy | OPA policy gate sebelum apply | 16 |
| Operasional | Automated drift detection | 18 |
| Testing | fmt, validate, tflint, Checkov, plan review | 11, 13 |
Note
Perhatikan pola pentingnya: tidak ada langkah yang bergantung pada ingatan atau disiplin manusia. Setiap gerbang — format, lint, security scan, policy, review, drift — dijaga oleh mesin. Ini filosofi inti IaC enterprise: automation over documentation, dan policy over personality.
Fondasi arsitektur adalah struktur repositori. Berikut struktur monorepo yang umum dipakai di perusahaan yang menjalankan Terraform di scale:
infra/
├── terragrunt.hcl # Konfigurasi global Terragrunt
├── account.hcl # Variabel bersama per akun/env
├── environments/
│ ├── dev/
│ │ ├── vpc/terragrunt.hcl
│ │ ├── eks/terragrunt.hcl
│ │ ├── rds/terragrunt.hcl
│ │ └── s3/terragrunt.hcl
│ ├── staging/
│ │ └── ... # Sama, nilai berbeda
│ └── production/
│ ├── vpc/terragrunt.hcl
│ ├── eks/terragrunt.hcl
│ ├── rds/terragrunt.hcl
│ └── s3/terragrunt.hcl
├── modules/ # Modul internal (episode 9)
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── versions.tf
│ ├── eks/
│ ├── rds/
│ └── s3/
├── policies/ # Policy as Code (episode 16)
│ ├── deny_public_s3.rego
│ ├── require_tags.rego
│ └── deny_public_ssh.rego
├── scripts/
│ ├── opa_gate.sh # Evaluasi OPA pada tfplan.json
│ └── drift_report.sh
├── .github/workflows/
│ ├── pr-plan.yml # Pipeline PR (episode 13)
│ ├── apply.yml # Pipeline merge → apply
│ └── drift-detection.yml # Scheduled drift check (episode 18)
├── terragrunt-modules/ # (Opsional) shared terragrunt units
└── docs/
├── runbooks/
└── inventory.md # Daftar ID resource (episode 19)Struktur ini memisahkan dua hal yang berbeda: modules/ berisi logika (kode reusable), sedangkan environments/*/ berisi instansiasi (nilai per environment). Terragrunt menjembatani keduanya dengan menghilangkan duplikasi konfigurasi backend dan provider yang biasanya ditulis ulang di setiap direktori environment.
State disimpan di satu bucket terpusat, dengan satu key per komponen per environment. Terragrunt memastikan setiap unit (vpc, eks, rds, s3) punya state-nya sendiri — bukan satu state raksasa untuk semua:
# Generate konfigurasi backend & provider secara terpusat
generate "backend" {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
terraform {
backend "s3" {
bucket = "acme-tfstate-global"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "acme-tfstate-lock"
}
}
EOF
}
remote_state {
backend = "s3"
config = {
bucket = "acme-tfstate-global"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "acme-tfstate-lock"
}
}include "root" {
path = find_in_parent_folders()
}
dependency "vpc" {
config_path = "../vpc"
}
inputs = {
engine = "postgres"
engine_version = "16.3"
instance_class = "db.r6g.large"
multi_az = true
deletion_protection = true
vpc_id = dependency.vpc.outputs.vpc_id
subnet_ids = dependency.vpc.outputs.private_subnets
}Yang perlu kalian perhatikan:
path_relative_to_include() membuat setiap unit otomatis mendapat key unik seperti production/rds/terraform.tfstate. Ini memberi isolasi state per komponen sekaligus konsistensi konfigurasi.dependency — RDS membaca output VPC secara otomatis (episode 12). Tanpa Terragrunt, kalian harus menulis terraform_remote_state data source berulang kali di setiap unit.Important
Terragrunt adalah wrapper, bukan pengganti. Ia tidak mengubah cara kerja Terraform — ia hanya menghilangkan duplikasi dan memastikan semua konfigurasi state/provider dihasilkan dari satu sumber. Untuk tim kecil, directory structure + remote backend polos (episode 10) sudah cukup; Terragrunt menjadi bernilai saat jumlah unit dan environment mulai puluhan.
Tidak ada secret cloud statis di repositori. Setiap job mendapatkan kredensial ephemeral melalui OIDC (episode 13) — GitHub menerbitkan ID token, dan AWS meng-asumsikan role IAM khusus untuk repo, branch, dan environment tersebut:
name: Terraform Apply
on:
push:
branches: [main]
paths: ["infra/**"]
permissions:
id-token: write # Kunci: izinkan job meminta token OIDC
contents: read
env:
TF_IN_AUTOMATION: "1"
VAULT_ADDR: "https://vault.acme.internal:8200"
jobs:
apply:
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra/environments/production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Configure AWS Credentials (OIDC)
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 Apply
run: terraform apply -auto-approve
env:
TF_VAR_vault_token: ${{ secrets.VAULT_TOKEN }}Perhatikan VAULT_ADDR dan TF_VAR_vault_token: kredensial cloud datang dari OIDC, sedangkan token Vault disuntikkan sebagai secret workflow — sekali lagi tanpa ada access key statis yang tertulis di kode. Role IAM gh-oidc-terraform-prod hanya bisa di-asumsikan oleh job dari repo ini di environment production — bukan dari laptop, bukan dari repo lain.
Secret tidak pernah disimpan di kode atau di state input. Vault menjadi single source of secrets, dan Terraform mengambilnya saat runtime lewat data source (episode 15):
provider "vault" {
address = var.vault_addr
token = var.vault_token
}
data "vault_kv_secret_v2" "database" {
mount = "secret"
name = "production/database"
}
resource "aws_db_instance" "primary" {
identifier = "acme-postgres"
engine = "postgres"
engine_version = "16.3"
username = data.vault_kv_secret_v2.database.data["username"]
password = data.vault_kv_secret_v2.database.data["password"]
multi_az = true
deletion_protection = true
}Nilai username dan password masuk ke state (hal yang tak terhindarkan, episode 15) — tapi sumbernya tidak pernah bocor ke Git. Plus, Vault mendukung dynamic secrets dan rotasi otomatis, yang tidak bisa dilakukan secret statis di .tfvars.
Warning
Token Vault (TF_VAR_vault_token) adalah rahasia berumur pendek — jangan pernah menaruhnya sebagai nilai default di variables.tf atau di commit. Ia disuntikkan dari GitHub Secrets di pipeline, dan di laptop developer lewat auth method modern (misal OIDC atau vault login) — bukan dengan menempel token di shell history.
Sebelum apply diizinkan, plan dievaluasi terhadap kebijakan organisasi. Policy ditulis dalam Rego (episode 16) — misalnya melarang bucket publik, melarang port SSH ke 0.0.0.0/0, dan mewajibkan tags standar:
package terraform
import rego.v1
deny contains msg if {
some rc in input.resource_changes
rc.type == "aws_security_group"
rc.change.after.ingress[_].from_port == 22
rc.change.after.ingress[_].cidr_blocks[_] == "0.0.0.0/0"
msg := sprintf("%s: port SSH terbuka ke publik dilarang", [rc.address])
}Gate-nya berjalan di pipeline PR — antara plan dan review:
#!/usr/bin/env bash
set -euo pipefail
cd infra/environments/production
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
opa eval \
--data ../../../policies \
--input tfplan.json \
"data.terraform.deny" > result.json
if jq -e '.result[].expressions[].value | length > 0' result.json > /dev/null; then
echo "❌ POLICY BLOCKED — pelanggaran ditemukan:"
jq -r '.result[].expressions[].value[]' result.json
exit 1
fi
echo "✅ Policy check lulus"Note
Static scanner seperti Checkov/Trivy (episode 11) menyisir kode untuk mencari miskonfigurasi; OPA mengevaluasi plan yang berisi nilai-nilai hasil komputasi (misal IP yang dihitung dari variabel). Keduanya saling melengkapi: jalankan keduanya di pipeline PR.
Penutup arsitektur: sistem yang menjaga diri sendiri. Workflow drift detection dari episode 18 dijadwalkan tiap malam untuk setiap environment, menggunakan terraform plan -detailed-exitcode:
name: Drift Detection
on:
schedule:
- cron: '0 3 * * *'
workflow_dispatch:
jobs:
drift:
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra/environments/production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- 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
- run: terraform init
- name: Deteksi drift
id: plan
run: |
set +e
terraform plan -detailed-exitcode -out=tfplan
code=$?
set -e
if [ "$code" -eq 2 ]; then
echo "drift_found=true" >> "$GITHUB_OUTPUT"
else
echo "drift_found=false" >> "$GITHUB_OUTPUT"
fi
- name: Buka issue bila drift ditemukan
if: steps.plan.outputs.drift_found == 'true'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: "🚨 Drift terdeteksi: production",
body: "Infrastruktur menyimpang dari kode. Jalankan `terraform plan` dan putuskan: reconcile (apply) atau adopt (ubah kode)."
})Beginilah arsitektur enterprise terpadu menutup lingkaran: kode menuju cloud lewat gerbang otomatis yang berlapis, state dijaga dan di-backup, secret tidak pernah bocor, policy tidak bisa di-bypass, dan drift tidak bisa bersembunyi lebih dari satu malam.
Sebelum arsitektur kalian layak disebut production-grade, lewati seluruh checklist berikut — adaptasi dari pelajaran seluruh seri:
| No | Item | Episod Referensi | Status |
|---|---|---|---|
| 1 | Remote state + locking (S3 + DynamoDB) + versioning bucket | 5, 19 | ☐ |
| 2 | Enkripsi state & KMS + IAM strict | 15 | ☐ |
| 3 | Directory-based multi-environment (dev/staging/prod) | 10 | ☐ |
| 4 | Modul reusable untuk komponen utama | 9 | ☐ |
| 5 | Secrets dari Vault / Secrets Manager, bukan dari kode | 15 | ☐ |
| 6 | sensitive = true pada variabel rahasia | 15 | ☐ |
| 7 | prevent_destroy / deletion_protection pada data kritis | 7, 17 | ☐ |
| 8 | lifecycle yang tepat (create_before_destroy, ignore_changes) | 7 | ☐ |
| 9 | terraform fmt + validate di pipeline | 11, 13 | ☐ |
| 10 | tflint + Checkov/Trivy security scan | 11 | ☐ |
| 11 | plan di PR + hasil diposting untuk review | 13 | ☐ |
| 12 | Policy gate OPA/Sentinel sebelum apply | 16 | ☐ |
| 13 | Apply hanya lewat pipeline dengan OIDC passwordless | 13 | ☐ |
| 14 | Approval gate manual untuk production | 13 | ☐ |
| 15 | Drift detection terjadwal + notifikasi | 18 | ☐ |
| 16 | Backup state terjadwal + prosedur restore teruji | 19 | ☐ |
| 17 | DR runbook ditulis dan dilatih (tabletop exercise) | 19 | ☐ |
| 18 | Dokumentasi: README, runbook, inventory resource | 9, 19 | ☐ |
Important
Jadikan checklist ini gerbang, bukan aspirasi. Di tim yang sehat, item yang belum tercentang berarti arsitektur belum boleh menyentuh production. Idealnya, sebagian besar item di-enforce oleh pipeline itu sendiri (item 9-15) — karena mesin selalu lebih andal daripada niat baik.
Saat mereview Pull Request Terraform, jangan hanya melihat "apakah syntax-nya benar". Ada lapisan pertanyaan yang harus ditanyakan setiap kali:
| Dimensi | Pertanyaan Kunci untuk Reviewer |
|---|---|
| Kebenaran | Apakah resource yang dibuat benar-benar dibutuhkan? Apakah referensi antar resource valid? |
| Drift / immutability | Apakah perubahan ini akan memicu forces replacement? Jika ya, apakah ada create_before_destroy? |
| Keamanan | Apakah ada CIDR 0.0.0.0/0 yang tidak perlu? Apakah bucket publik? Apakah secret didefinisikan di kode? |
| Data loss | Apakah ada resource dengan data yang berisiko dihancurkan? Ada prevent_destroy/deletion_protection? |
| Biaya | Berapa perkiraan biaya tambahan? Apakah instance class dan replica wajar untuk environment ini? |
| DRY & konsistensi | Apakah ada duplikasi yang bisa dipindah ke modul? Apakah tags/standar organisasi terpenuhi? |
| Reviewability | Apakah plan yang diposting bisa dipahami? Apakah scope perubahan terlalu besar untuk satu PR? |
| Idempotency & state | Apakah ada import/moved? Apakah operasi state tercatat dan bisa di-audit? |
Aturan praktis yang selalu berlaku: PR Terraform yang baik adalah PR yang kecil, satu tujuan, dan disertai hasil terraform plan. Jika sebuah PR mengubah 40 resource sekaligus, minta dipecah — sebuah bug di tengah perubahan besar jauh lebih sulit ditemukan daripada bug di perubahan kecil yang fokus.
Tip
Selalu tanya "apa yang terjadi kalau apply ini gagal di tengah jalan?" dan "apa yang terjadi kalau apply ini berhasil tapi ternyata salah?" Jawaban atas dua pertanyaan itu — rollback, state pull backup, dan prosedur pemulihan (episode 19) — adalah tanda seorang reviewer yang benar-benar paham operasional, bukan sekadar pengecek syntax.
Mari kita lihat peta besar yang sudah kalian lalui bersama selama dua puluh episode:
| Fase | Episod | Materi Inti |
|---|---|---|
| Fundamentals | 0–2 | Setup environment, sejarah IaC, HCL syntax & core workflow |
| Core Resources | 3–5 | Providers, variables, state management (local vs remote) |
| Expressions & State | 6–8 | Functions & loops, dependencies & lifecycle, import/moved/state |
| Modularity | 9–11 | Modules, multi-environment, fmt/lint/test |
| Automation | 12–14 | Terragrunt, CI/CD pipeline, managed IaC platforms |
| Security & Compliance | 15–16 | Secret management, Policy as Code (OPA/Sentinel) |
| Production Readiness | 17–20 | Complex stack, drift detection, disaster recovery, enterprise architecture |
Dari sekadar memahami perintah terraform init di episode 2, kalian kini mampu merancang sistem di mana infrastruktur perusahaan seluruhnya ditulis sebagai kode, diverifikasi oleh mesin, diamankan oleh policy, dan dipulihkan dengan prosedur yang teruji. Itu adalah kemampuan langka yang bernilai sangat tinggi di pasar kerja DevOps, SRE, dan Cloud Engineering.
Selamat — kalian telah menyelesaikan seri Belajar Terraform dari episode 0 hingga episode 20!
Mari sejenak kita rekap makna perjalanan ini. Di episode 20 ini kita telah merancang arsitektur IaC enterprise yang terpadu: struktur monorepo modular dengan Terragrunt, remote backend S3 + DynamoDB yang terpusat dan ter-versioning, autentikasi OIDC yang passwordless, secret dari HashiCorp Vault, policy gate OPA yang memblokir pelanggaran sebelum apply, dan drift detection yang menjaga sistem setiap malam. Kita menutupnya dengan checklist kesiapan produksi dan panduan code review yang menjadikan seluruh pelajaran seri ini sebagai satu gerbang menuju production.
Namun yang paling berharga bukanlah kodenya — melainkan cara berpikir yang sekarang kalian miliki:
Perjalanan kalian tidak berhenti di sini. Beberapa langkah berikutnya yang bisa kalian ambil untuk terus tumbuh:
Ingatlah fondasi yang kita bangun sejak episode 0: rumah yang kokoh berdiri di atas fondasi yang kuat. Fondasi yang kalian bangun selama dua puluh episode ini bukan sekadar kode — ia adalah kepercayaan: kepercayaan bahwa infrastruktur bisa dibangun ulang, dipulihkan, dan dipertanggungjawabkan. Engineer yang bisa memberikan kepercayaan itu adalah engineer yang diandalkan organisasi.
Terima kasih sudah menemani perjalanan ini sampai akhir. Sekarang — buka terminal, tulis modul pertama kalian, dan jadikan infrastruktur sebagai sesuatu yang bisa dibangun ulang dengan satu perintah. Selamat berkarya sebagai engineer infrastruktur yang sesungguhnya!