Modul sebaiknya mengelola resource yang harus selalu disediakan bersama dan memiliki coupling siklus hidup yang kuat. Misalnya, VPC GCP dan subnetnya cocok dijadikan satu modul, begitu pula instance Cloud SQL beserta binding IAM-nya. Hindari modul yang terlalu sempit (sekadar pembungkus tipis satu resource) maupun terlalu luas (seluruh stack environment), karena keduanya mempersulit pengujian dan penggunaan ulang.
Output modul menjadi perekat antar modul — VPC ID dari modul networking dilewatkan ke modul compute, sementara connection string database dari modul database dilewatkan ke modul aplikasi. Jika output tidak disertakan sejak awal, modul harus dimodifikasi kemudian dan semua pemanggil yang mereferensikannya harus diperbarui. Berdasarkan pengalaman produksi, over-output dari hari pertama jauh lebih mudah daripada menambahkannya belakangan.
Mereferensikan modul langsung dari branch main berarti setiap perubahan yang di-merge — termasuk perubahan breaking — langsung memengaruhi semua environment yang menggunakan modul tersebut. Dengan menandai rilis (misalnya v1.0.0, v1.1.0) dan mempin setiap environment ke tag tertentu, perubahan menjadi deliberat dan terkontrol. Perubahan breaking mendapat bump versi mayor, sehingga setiap environment dapat memperbarui sesuai jadwalnya sendiri.
Blok validasi memberlakukan batasan pada waktu plan, menangkap miskonfigurasi sebelum infrastruktur apa pun disentuh. Sebaiknya digunakan secara agresif — misalnya, membatasi environment prod agar hanya menggunakan tipe mesin database db-custom-4-8192 atau lebih besar, sehingga mencegah under-provisioning yang tidak disengaja. Pengaturan keamanan yang wajib seperti enkripsi dan retensi backup tidak boleh memiliki nilai default, agar setiap environment dipaksa membuat keputusan eksplisit.
Modul komunitas dari Terraform Registry — seperti modul Google Network dan Google Cloud SQL — terawat dengan baik dan menghemat waktu pengembangan yang signifikan untuk pola umum. Modul internal khusus hanya diperlukan ketika modul komunitas tidak sesuai dengan kebutuhan spesifik, seperti kebijakan IAM tertentu, konvensi penamaan, atau konfigurasi multi-region untuk region Indonesia/Singapura. Aturan praktisnya: gunakan modul komunitas sebagai titik awal, fork dan kustomisasi hanya bila perlu, dan kontribusikan peningkatan ke upstream bila memungkinkan.
Terraform tanpa modul adalah infrastruktur copy-paste. Anda mendefinisikan VPC yang sama, instance komputasi yang sama, dan konfigurasi database yang sama di setiap environment — lalu menghabiskan berjam-jam merekonsiliasi drift ketika Anda mengubah sesuatu di satu environment tetapi lupa yang lain. Saya belajar hal ini dengan cara yang keras di Commsult Indonesia, di mana kami memiliki tiga environment GCP yang hampir identik (dev, staging, prod) dengan konfigurasi yang disinkronkan secara manual. Setelah insiden ketiga di mana staging dan prod mengalami drift, saya menulis ulang semuanya sebagai modul Terraform yang dapat dikomposisikan. Panduan ini menunjukkan pola persis yang saya gunakan.
Modul Terraform adalah wadah untuk beberapa resource yang digunakan bersama. Kata kuncinya adalah 'bersama' — modul harus merepresentasikan komponen infrastruktur yang koheren, bukan jenis resource tunggal atau seluruh stack aplikasi. Modul untuk VPC masuk akal. Modul untuk 'database dengan monitoring, alerting, dan backup yang dikonfigurasi' masuk akal. Modul yang hanya merupakan pembungkus tipis di sekitar google_compute_instance tidak — Anda telah menambahkan abstraksi tanpa menambahkan nilai. Struktur modul standar dari HashiCorp jelas: main.tf untuk definisi resource, variables.tf untuk input, outputs.tf untuk nilai yang dibutuhkan pemanggil, dan README.md yang membuat modul dapat dikonsumsi secara eksternal.
Prinsip Don't Repeat Yourself dari rekayasa perangkat lunak berlaku sama untuk kode infrastruktur. Ketika Anda mendefinisikan instance GCP Cloud SQL di main.tf environment dev dan secara terpisah di staging dan prod, Anda memiliki tiga sumber kebenaran — dan mereka akan menyimpang. Sebuah modul menyatukan ketiga definisi tersebut menjadi satu, dengan nilai spesifik environment yang dilewatkan sebagai variabel. Modul memberlakukan batasan — environment prod hanya dapat menggunakan db-custom-4-8192 atau lebih besar, diberlakukan oleh blok validasi di variables.tf. Tidak ada individu yang dapat secara tidak sengaja menyediakan database yang terlalu kecil di produksi.
Kesalahan paling umum dengan modul Terraform adalah scope creep — membuat modul terlalu luas. Modul 'production environment' yang menyediakan VPC, database, instance komputasi, load balancer, dan record DNS dalam satu blok sulit diuji, sulit di-versioning, dan sulit digunakan ulang. Cakupan yang tepat: modul harus mengelola resource yang harus selalu disediakan bersama dan memiliki coupling siklus hidup yang kuat. VPC GCP dan subnetnya sangat terkait erat — sediakan bersama. Instance GCP Cloud SQL dan binding IAM-nya terkait — sediakan bersama. GCP Load Balancer dan layanan backend yang dirutekannya — mungkin modul terpisah yang dikomposisikan di level environment.
Dari pengalaman saya: selalu keluarkan setiap ID dan nama resource dari modul Anda, meskipun Anda tidak menggunakannya segera. Dalam praktiknya, output modul menjadi perekat antar modul — Anda melewatkan VPC ID dari modul networking ke modul compute, dan connection string database dari modul database ke modul aplikasi. Jika Anda tidak mengeluarkannya awalnya, Anda harus memodifikasi modul nanti dan memperbarui semua pemanggil. Over-output dari hari pertama.
Repository modul Terraform yang terstruktur dengan baik memisahkan modul root (definisi environment yang memanggil modul anak) dari modul anak (blok bangunan yang dapat digunakan ulang). Konvensi yang saya gunakan di Commsult Indonesia menempatkan modul anak di bawah modules/ dan konfigurasi root environment di bawah environments/dev, environments/staging, environments/prod. Setiap modul anak memiliki README.md sendiri, variables.tf dengan deskripsi dan blok validasi, outputs.tf, dan main.tf. Modul anak tidak pernah berisi konfigurasi provider — mereka mewarisi dari root. Ini membuat modul agnostik terhadap environment dan benar-benar dapat digunakan ulang.
# modules/cloud-sql/variables.tf
variable "instance_name" {
description = "Cloud SQL instance name"
type = string
}
variable "machine_type" {
description = "Cloud SQL machine type"
type = string
default = "db-custom-2-8192"
validation {
condition = contains(["db-custom-2-8192", "db-custom-4-16384", "db-custom-8-32768"], var.machine_type)
error_message = "Machine type must be one of the approved sizes."
}
}
variable "environment" {
description = "Deployment environment"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
# modules/cloud-sql/outputs.tf
output "instance_connection_name" {
description = "Cloud SQL instance connection name"
value = google_sql_database_instance.this.connection_name
}
output "private_ip_address" {
description = "Cloud SQL private IP address"
value = google_sql_database_instance.this.private_ip_address
}
# environments/prod/main.tf
module "database" {
source = "../../modules/cloud-sql"
instance_name = "prod-db-01"
machine_type = "db-custom-4-16384"
environment = "prod"
}Blok validasi Terraform memungkinkan Anda memberlakukan batasan pada waktu plan daripada waktu apply. Gunakan secara agresif di modul Anda untuk menangkap miskonfigurasi sebelum resource apa pun disentuh. Batasan tipe di variables.tf mendefinisikan apa yang dapat dilewatkan oleh pemanggil — tipe objek dengan kunci yang diperlukan mencegah pemanggil menghilangkan konfigurasi kritis. Gabungkan dengan nilai default hanya untuk pengaturan yang benar-benar opsional — pengaturan keamanan yang diperlukan (enkripsi, retensi backup) tidak boleh memiliki default untuk memaksa keputusan eksplisit per environment.
Di awal adopsi Terraform saya, saya mereferensikan modul internal langsung dari branch Git main: source = 'git::https://github.com/myorg/tf-modules.git//modules/vpc'. Ini tampak nyaman hingga anggota tim melakukan merge perubahan breaking ke main yang langsung merusak apply staging kami. Solusinya: tandai rilis modul (v1.0.0, v1.1.0) dan referensikan versi tertentu: source = 'git::...?ref=v1.1.0'. Perubahan breaking mendapatkan bump versi mayor. Semua environment dikunci ke versi tertentu dan diperbarui secara deliberat, bukan otomatis.
Modul Terraform yang tidak diuji secara diam-diam mengakumulasi bug. Pendekatan pengujian minimum adalah menjalankan terraform validate dan terraform plan terhadap environment nyata (tetapi sementara) untuk setiap pull request. Untuk pengujian yang lebih komprehensif, Terratest (perpustakaan pengujian berbasis Go) memungkinkan Anda menulis tes yang menyediakan infrastruktur nyata, melakukan assertion pada state dan output, dan menghancurkan semuanya setelah pengujian. Untuk modul yang mengelola resource mahal, gunakan localstack atau emulator GCP untuk tes level unit, dan cadangkan Terratest untuk tes integrasi yang berjalan saat merge ke main.
┌─────────────────────────────────────────────────┐
│ Terraform Module Repository Layout │
├─────────────────────────────────────────────────┤
│ tf-modules/ │
│ ├── modules/ (child modules) │
│ │ ├── vpc/ │
│ │ │ ├── main.tf │
│ │ │ ├── variables.tf │
│ │ │ └── outputs.tf │
│ │ ├── cloud-sql/ │
│ │ └── cloud-run/ │
│ └── environments/ (root modules) │
│ ├── dev/ │
│ ├── staging/ │
│ └── prod/ │
└─────────────────────────────────────────────────┘Modul tanpa manajemen state yang tepat menyebabkan race condition dan korupsi. Setiap modul root environment harus menyimpan state dari jarak jauh dengan locking yang diaktifkan. Di GCP, gunakan bucket GCS dengan versioning yang diaktifkan sebagai backend, dan Terraform secara otomatis menangani state locking melalui mekanisme object lock GCS. Setiap environment mendapatkan file state-nya sendiri — state dev, staging, dan prod sepenuhnya terisolasi. Referensi state lintas environment menggunakan sumber data terraform_remote_state (gunakan dengan hemat — ini menciptakan coupling ketat antar environment).
Terraform Registry memiliki ratusan modul komunitas untuk AWS, GCP, dan Azure. Saya menggunakannya untuk pola umum di mana saya tidak memiliki persyaratan khusus — modul Google Network dan modul Google Cloud SQL terawat dengan baik dan menghemat waktu pengembangan yang signifikan. Saya menulis modul internal khusus hanya ketika modul komunitas tidak sesuai dengan persyaratan spesifik kami (biasanya seputar kebijakan IAM, konvensi penamaan, atau konfigurasi multi-region spesifik untuk region Indonesia/Singapura). Aturannya: gunakan modul komunitas sebagai titik awal, fork dan kustomisasi hanya bila perlu, dan kontribusikan peningkatan ke upstream bila memungkinkan.
Sumber & Bacaan Lanjutan