Yeniden Kullanılabilir Terraform Modüllerinin Tasarlanması: Yapı, G/Ç ve Kayıt Defteri
Terraform ile gerçek projeler üzerinde çalışmaya başladığınızda kritik an gelir 500-600 HCL satırında: konfigürasyonun okunması zorlaşmaya başlar, aynı modeller farklı ortamlar için tekrarlanır ve yeniden düzenleme bir işlem haline gelir riskli. Çözüm benim Terraform modülleri: yeniden kullanılabilir soyutlama birimleri karmaşıklığı özetleyen ve temiz bir arayüz ortaya çıkaran.
Bu kılavuz, profesyonel formların tasarlanmasını kapsar: yalnızca bunların nasıl oluşturulacağı değil, ama nasıl düşünmek genel arayüze bağlanma, geriye dönük uyumluluğu yönetme, test edin ve Terraform Registry'de yayınlayın. Kötü tasarlanmış bir modül ve hiç modül olmamasından daha kötü: katı bağımlılıklar yaratır, yükseltmeleri zorlaştırır ve karmaşıklığı yönetmek yerine gizler.
Ne Öğreneceksiniz
- Profesyonel bir Terraform modülünün kanonik yapısı
- Değişken tasarım: türler, doğrulamalar, varsayılan değerler ve karmaşık nesneler
- Standartlaştırılmış çıktılar: neyin görüntüleneceği ve hangi adlandırma kuralıyla gösterileceği
- Alt modüller ve bileşik modüller (bileşim ve kalıtım)
- Anlamsal sürüm oluşturma ve geriye dönük uyumluluk yönetimi
- Terraform Kamu Sicili ve özel sicilde yayın
- Gelişmiş modeller: genel formlar, dinamik for_each, koşullu formlar
Bir Modülün Kanonik Yapısı
Terraform modülünün yapısı, Kayıt Defteri ve topluluk tanır. Bu geleneklerden sapmak, bu biçimin tüketicileri için kafa karışıklığı yaratır.
terraform-aws-networking/ # Naming: terraform-{provider}-{name}
├── main.tf # Logica principale del modulo
├── variables.tf # Input variables (interfaccia pubblica)
├── outputs.tf # Output values (interfaccia pubblica)
├── versions.tf # required_providers e terraform version
├── locals.tf # Valori computati interni
├── README.md # Documentazione (auto-generabile con terraform-docs)
├── CHANGELOG.md # Versioni e breaking changes
├── LICENSE # Apache 2.0 per moduli pubblici
├── examples/ # Esempi di utilizzo del modulo
│ ├── simple/
│ │ ├── main.tf # Uso minimo del modulo
│ │ └── outputs.tf
│ └── complete/
│ ├── main.tf # Uso completo con tutti i parametri
│ ├── variables.tf
│ └── outputs.tf
├── modules/ # Submoduli interni (opzionale)
│ └── subnet/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── test/ # Test del modulo
└── networking_test.go # Terratest
Adlandırma kuralı terraform-{provider}-{name} ve için zorunlu
kamu Terraform Sicilinde yayınlanır ve özel kayıtlar için önerilir.
Gerçek örnekler: terraform-aws-eks, terraform-google-kubernetes-engine,
terraform-azurerm-network.
Değişken Tasarım: Genel Arayüz
Bir modülün değişkenleri onun genel API'sidir. İyi değişken tasarım takip eder minimum şaşkınlık ilkesi: modülün bir tüketicisi Değişkenlerin isimlerini ve açıklamalarını okuyarak ne yapılması gerektiğini anlayabilmeli, Dahili kodu okumadan.
# variables.tf — design professionale delle variabili
# Variabile richiesta (no default): rappresenta un input fondamentale
variable "vpc_cidr" {
description = "CIDR block per la VPC. Deve essere un CIDR /16 privato."
type = string
validation {
condition = can(regex(
"^(10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.)\\d+\\.\\d+/16$",
var.vpc_cidr
))
error_message = "Il CIDR deve essere un blocco privato /16 (RFC 1918)."
}
}
# Variabile con default ragionevole
variable "name" {
description = "Nome base usato per il prefisso di tutte le risorse."
type = string
default = "main"
validation {
condition = can(regex("^[a-z][a-z0-9-]{1,28}[a-z0-9]$", var.name))
error_message = "Il nome deve essere lowercase, alfanumerico con trattini, 3-30 caratteri."
}
}
# Oggetto complesso: meglio di N variabili separate per configurazioni correlate
variable "nat_gateway_config" {
description = <<-EOT
Configurazione del NAT Gateway.
- enabled: crea il NAT Gateway (aggiunge costo ~$32/mese per AZ)
- single_az: usa un solo NAT Gateway (risparmio costi per non-prod)
EOT
type = object({
enabled = bool
single_az = bool
})
default = {
enabled = false
single_az = false
}
}
# Lista di oggetti: per configurazioni ripetute
variable "private_subnets" {
description = "Lista di subnet private da creare."
type = list(object({
cidr = string
availability_zone = string
tags = optional(map(string), {})
}))
default = []
validation {
condition = alltrue([
for s in var.private_subnets :
can(cidrhost(s.cidr, 0))
])
error_message = "Ogni subnet deve avere un CIDR valido."
}
}
# Variabile sensibile: non viene loggata nell'output di plan/apply
variable "database_password" {
description = "Password per il database. Usa Secrets Manager in produzione."
type = string
sensitive = true
validation {
condition = length(var.database_password) >= 16
error_message = "La password deve avere almeno 16 caratteri."
}
}
# Map per tag: pattern universale in Terraform
variable "tags" {
description = "Map di tag aggiuntivi da applicare a tutte le risorse."
type = map(string)
default = {}
}
# Feature flags: booleani che attivano/disattivano funzionalita
variable "enable_flow_logs" {
description = "Abilita VPC Flow Logs per il network monitoring."
type = bool
default = false
}
variable "enable_vpc_endpoints" {
description = "Crea VPC Endpoints per S3 e DynamoDB (riduce costi NAT)."
type = bool
default = true
}
Dahili Mantık: Yereller ve Kaynak Tasarımı
Modülün içinde ben locals onlar tutmak için ana araçtır
KURU kodu. Tüm hesaplama mantığı dağınık değil, yerel düzeyde olmalıdır
kaynak bloklarında.
# locals.tf — logica interna del modulo
locals {
# Naming convention centralizzata
name_prefix = var.name
# Merging tag: tag del modulo + tag dell'utente
# I tag dell'utente sovrascrivono i default del modulo
default_tags = {
ManagedBy = "Terraform"
Module = "terraform-aws-networking"
}
merged_tags = merge(local.default_tags, var.tags)
# Calcola automaticamente le AZ disponibili se non specificate
azs = length(var.private_subnets) > 0 ? [
for s in var.private_subnets : s.availability_zone
] : []
# Decisione sul NAT Gateway: uno per AZ o uno singolo
nat_count = var.nat_gateway_config.enabled ? (
var.nat_gateway_config.single_az ? 1 : length(local.azs)
) : 0
# Mappa per for_each: key univoca -> oggetto configurazione
private_subnets_map = {
for idx, subnet in var.private_subnets :
"${local.name_prefix}-private-${idx + 1}" => subnet
}
}
# main.tf — risorse del modulo
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(local.merged_tags, {
Name = "${local.name_prefix}-vpc"
})
}
# for_each su mappa: crea una risorsa per ogni elemento
resource "aws_subnet" "private" {
for_each = local.private_subnets_map
vpc_id = aws_vpc.this.id
cidr_block = each.value.cidr
availability_zone = each.value.availability_zone
tags = merge(
local.merged_tags,
each.value.tags,
{
Name = each.key
Tier = "Private"
}
)
}
# Risorsa condizionale: created solo se nat_gateway_config.enabled = true
resource "aws_eip" "nat" {
count = local.nat_count
domain = "vpc"
tags = merge(local.merged_tags, {
Name = "${local.name_prefix}-nat-eip-${count.index + 1}"
})
}
resource "aws_nat_gateway" "this" {
count = local.nat_count
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
depends_on = [aws_internet_gateway.this]
tags = merge(local.merged_tags, {
Name = "${local.name_prefix}-nat-${count.index + 1}"
})
}
# Flow Logs condizionali
resource "aws_cloudwatch_log_group" "vpc_flow_logs" {
count = var.enable_flow_logs ? 1 : 0
name = "/aws/vpc-flow-logs/${aws_vpc.this.id}"
retention_in_days = 30
tags = local.merged_tags
}
resource "aws_flow_log" "this" {
count = var.enable_flow_logs ? 1 : 0
vpc_id = aws_vpc.this.id
traffic_type = "ALL"
iam_role_arn = aws_iam_role.flow_logs[0].arn
log_destination = aws_cloudwatch_log_group.vpc_flow_logs[0].arn
}
Çıktı: Ne Sergilenecek ve Nasıl Sergilenecek
Bir modülün çıktıları da değişkenler kadar önemlidir: onlar arayüzdür Ana modüllerin ve diğer modüllerin, oluşturulan kaynaklar hakkında bilgi almak için kullandıkları. Altın kural şudur: Bir tüketicinin ihtiyaç duyabileceği her şeyi ortaya çıkarın, ama artık yok.
# outputs.tf — output standardizzati del modulo
# ID della VPC: sempre necessario per altri moduli
output "vpc_id" {
description = "ID della VPC creata."
value = aws_vpc.this.id
}
output "vpc_cidr" {
description = "CIDR block della VPC."
value = aws_vpc.this.cidr_block
}
# Liste di IDs: convenzione piu comune per subnet
output "private_subnet_ids" {
description = "Lista degli IDs delle subnet private."
value = [for s in aws_subnet.private : s.id]
}
# Map key->id: utile quando il consumatore deve referenziare subnet per nome
output "private_subnet_ids_by_name" {
description = "Map: nome subnet -> ID. Utile per for_each in moduli parent."
value = { for k, v in aws_subnet.private : k => v.id }
}
# ARN per policy IAM
output "vpc_arn" {
description = "ARN della VPC."
value = aws_vpc.this.arn
}
# Output condizionale: null se la risorsa non e stata creata
output "nat_gateway_ids" {
description = "IDs dei NAT Gateway. Lista vuota se nat_gateway_config.enabled = false."
value = aws_nat_gateway.this[*].id
}
# Output di un oggetto completo: utile per passare configurazione a moduli figli
output "vpc_config" {
description = "Configurazione completa della VPC per uso in moduli downstream."
value = {
id = aws_vpc.this.id
arn = aws_vpc.this.arn
cidr_block = aws_vpc.this.cidr_block
private_subnet_ids = [for s in aws_subnet.private : s.id]
nat_gateway_enabled = var.nat_gateway_config.enabled
}
}
Modül Kullanma: Sözdizimi ve Sürüm Oluşturma
Bir modül kullandığınızda Terraform farklı kaynakları destekler: yerel, Git, Terraform Registry. Beklenmeyen gerilemelerden kaçınmak için sürüm yönetimi çok önemlidir.
# Uso del modulo da sorgenti diverse
# 1. Modulo locale (sviluppo e test)
module "networking" {
source = "./modules/networking"
vpc_cidr = "10.0.0.0/16"
name = "dev"
}
# 2. Dal Terraform Registry pubblico con versione pinned
module "networking" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.1" # Permette 5.1.x ma non 5.2.0
name = "dev-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = true # Per dev/staging: risparmio costi
tags = {
Environment = "dev"
ManagedBy = "Terraform"
}
}
# 3. Da repository Git (registry privato aziendale)
module "networking" {
source = "git::https://github.com/myorg/terraform-modules.git//networking?ref=v2.3.1"
vpc_cidr = "10.0.0.0/16"
name = "prod"
nat_gateway_config = {
enabled = true
single_az = false # Multi-AZ in produzione
}
}
# 4. Da Terraform Enterprise o HCP Terraform Registry privato
module "networking" {
source = "app.terraform.io/myorg/networking/aws"
version = "~> 2.0"
vpc_cidr = "10.0.0.0/16"
}
# Accesso agli output del modulo
resource "aws_eks_cluster" "main" {
name = "prod-cluster"
role_arn = aws_iam_role.eks.arn
vpc_config {
# Referenzia l'output del modulo networking
subnet_ids = module.networking.private_subnet_ids
}
}
output "vpc_id" {
value = module.networking.vpc_id
}
Modüller için Anlamsal Sürüm Oluşturma
Terraform modülleri aşağıdakileri takip eder: anlamsal versiyonlama (birkaç):
MAJOR.MINOR.PATCH. Kırıcı değişim ile kırılmayan değişim arasındaki ayrım
Modülü tüketenlere değişim ve eleştiri.
# Cosa costituisce un breaking change (bump MAJOR):
# - Rimozione di una variabile obbligatoria
# - Cambiamento del tipo di una variabile esistente
# - Rimozione di un output
# - Rinomina di un output
# - Cambiamento del nome di una risorsa (causa destroy + recreate)
# - Aggiunta di una variabile obbligatoria senza default
# Non-breaking change (bump MINOR):
# - Aggiunta di una variabile opzionale (con default)
# - Aggiunta di un nuovo output
# - Aggiunta di una nuova funzionalita opzionale (feature flag)
# Patch:
# - Bugfix che non cambia l'interfaccia
# - Aggiornamento di versione di un sub-modulo
# - Miglioramenti alla documentazione
# Convenzioni nei version constraints:
# ~> 2.0 = >= 2.0, < 3.0 (piu usato: permette minor e patch)
# ~> 2.3 = >= 2.3, < 3.0
# ~> 2.3.0 = >= 2.3.0, < 2.4.0 (solo patch)
# >= 2.0, < 3.0 (equivalente a ~> 2.0 ma piu esplicito)
# CHANGELOG.md esempio:
# ## [3.0.0] - 2026-08-01
# ### Breaking Changes
# - Rimossa variabile `legacy_dns_mode` (deprecata dalla v2.5)
# - Output `subnet_id` rinominato in `private_subnet_ids` (lista)
#
# ## [2.5.0] - 2026-07-15
# ### Added
# - Aggiunto supporto VPC Endpoints per S3 e DynamoDB
# - Nuova variabile opzionale `enable_vpc_endpoints` (default: false)
# ### Deprecated
# - Variabile `legacy_dns_mode` sara rimossa nella v3.0
Gelişmiş Desenler: Genel Modüller
En güçlü modüller kullandıkları modüllerdir for_each yaratmak için dinamik
tamamen parametrik konfigürasyonlar. Bu model aşağıdaki modüllerde yaygındır:
benzer kaynak kümelerini yönetin (örneğin, birden çok alt ağ, birden çok güvenlik grubu kuralı).
# Pattern: modulo per Security Groups completamente configurabile
# variables.tf del modulo sg
variable "security_groups" {
description = "Map di security groups da creare."
type = map(object({
description = string
ingress_rules = optional(list(object({
description = string
from_port = number
to_port = number
protocol = string
cidr_blocks = optional(list(string), [])
security_group_ids = optional(list(string), [])
})), [])
egress_rules = optional(list(object({
description = string
from_port = number
to_port = number
protocol = string
cidr_blocks = optional(list(string), ["0.0.0.0/0"])
})), [])
tags = optional(map(string), {})
}))
default = {}
}
# main.tf del modulo sg
resource "aws_security_group" "this" {
for_each = var.security_groups
name = "${var.name_prefix}-${each.key}"
description = each.value.description
vpc_id = var.vpc_id
tags = merge(var.tags, each.value.tags, {
Name = "${var.name_prefix}-${each.key}"
})
lifecycle {
create_before_destroy = true
}
}
# Flatten per le ingress rules: ogni SG puo avere N regole
locals {
ingress_rules = flatten([
for sg_name, sg_config in var.security_groups : [
for idx, rule in sg_config.ingress_rules : {
sg_name = sg_name
rule_idx = idx
rule = rule
}
]
])
}
resource "aws_security_group_rule" "ingress" {
for_each = {
for item in local.ingress_rules :
"${item.sg_name}-ingress-${item.rule_idx}" => item
}
type = "ingress"
security_group_id = aws_security_group.this[each.value.sg_name].id
description = each.value.rule.description
from_port = each.value.rule.from_port
to_port = each.value.rule.to_port
protocol = each.value.rule.protocol
cidr_blocks = each.value.rule.cidr_blocks
}
# Uso del modulo sg:
module "security_groups" {
source = "./modules/security-groups"
name_prefix = local.name_prefix
vpc_id = module.networking.vpc_id
security_groups = {
"web" = {
description = "Security group per istanze web"
ingress_rules = [
{
description = "HTTPS dal pubblico"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
description = "HTTP redirect dal pubblico"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
]
}
"database" = {
description = "Security group per RDS"
ingress_rules = [
{
description = "PostgreSQL solo dal tier web"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_group_ids = [module.security_groups.ids["web"]]
cidr_blocks = []
}
]
}
}
}
Terraform-docs ile otomatik dokümantasyon
Bir modülün değişkenlerini ve çıktılarını manuel olarak belgelemek sıkıcıdır ve çoğu zaman geçerliliğini yitirmiştir. terraform-docs HCL kodundan otomatik olarak belgeler oluşturur.
# Installa terraform-docs
brew install terraform-docs # macOS
# oppure
go install github.com/terraform-docs/terraform-docs@latest
# Genera README.md dal modulo
terraform-docs markdown table . > README.md
# Configurazione in .terraform-docs.yml
# formatter: "markdown table"
# output:
# file: README.md
# mode: inject # Inietta tra i marker nel README esistente
# settings:
# show-all: true
# indent: 2
# Con inject mode nel README.md:
#
# (documentazione auto-generata)
#
# Aggiorna il README automaticamente con pre-commit
# .pre-commit-config.yaml:
# - repo: https://github.com/terraform-docs/terraform-docs
# rev: "v0.19.0"
# hooks:
# - id: terraform-docs-go
# args: ["--output-file", "README.md", "--output-mode", "inject", "."]
Terraform Kayıt Defterinde yayınlayın
Bir modülü genel Terraform Kayıt Defteri'nde yayınlamak için GitHub deposunun belirli kuralları takip edin. Süreç GitHub Etiketleri aracılığıyla neredeyse tamamen otomatikleştirilmiştir.
# Pre-requisiti per la pubblicazione:
# 1. Repository pubblico su GitHub
# 2. Nome repository: terraform-{provider}-{name}
# 3. Tag semver nel formato: v{MAJOR}.{MINOR}.{PATCH}
# Struttura obbligatoria per il Registry:
# main.tf, variables.tf, outputs.tf nella root
# README.md con documentazione
# Almeno un esempio in examples/
# Processo di release:
git tag -a v1.0.0 -m "Release v1.0.0: versione iniziale"
git push origin v1.0.0
# Il Registry riceve una webhook e indicizza automaticamente il modulo
# Per registry privato con HCP Terraform:
# 1. Vai su app.terraform.io -> Registry -> Publish Module
# 2. Connetti il repository GitHub
# 3. I tag vengono sincronizzati automaticamente
# terraform.tfvars per testing locale del modulo
vpc_cidr = "10.0.0.0/16"
name = "test"
nat_gateway_config = {
enabled = false
single_az = false
}
tags = {
Environment = "test"
Owner = "platform-team"
}
AWS için Önerilen Açık Kaynak Modülleri
Sıfırdan bir modül yazmadan önce, Kayıt Defterinde olgun bir sürümün mevcut olup olmadığını kontrol edin. Modülleri terraform-aws-modülleri (Anton Babenko tarafından) fiili standarttır:
terraform-aws-modules/vpc/aws— tam ağ iletişimiterraform-aws-modules/eks/aws— EKS kümesiterraform-aws-modules/rds/aws— Tüm parametrelerle birlikte RDSterraform-aws-modules/s3-bucket/aws— Şifreleme ve politikalara sahip S3terraform-aws-modules/security-group/aws— 100'den fazla önceden yapılandırılmış kural
Sonuçlar ve Sonraki Adımlar
İyi Terraform modülleri tasarlamak, iyi bir API tasarlamakla aynı özeni gerektirir: Tüketicilerinizi düşünün, arayüzü sabit tutun, her parametreyi belgeleyin. İyi tasarlanmış bir modül, tüm ekip tarafından yıllarca hiçbir değişiklik yapılmadan kullanılır.
Terraform olgunluğunuzun bir sonraki adımı durum yönetimini ele almaktır Ekiplerde: Organizasyonların ölçeklendirilmesinde en sık karşılaşılan ve riskli sorun Terraform'u benimsiyorum.
Komple Seri: Terraform ve IaC
- Madde 01 — Sıfırdan Terraform: HCL, Sağlayıcı ve Planla-Uygula-Yok Et
- Madde 02 (bu) — Yeniden Kullanılabilir Terraform Modüllerinin Tasarlanması: Yapı, G/Ç ve Kayıt Defteri
- Madde 03 — Terraform Durumu: S3/GCS ile Uzak Arka Uç, Kilitleme ve İçe Aktarma
- Madde 04 — CI/CD'de Terraform: GitHub Eylemleri, Atlantis ve Çekme İsteği İş Akışı
- Madde 05 — IaC Testi: Terratest, Terraform Yerel Testi ve Sözleşme Testi
- Madde 06 — IaC Güvenliği: Checkov, Trivy ve OPA Kod Olarak Politika
- Madde 07 — Terraform Çoklu Bulut: Paylaşılan Modüllerle AWS + Azure + GCP
- Madde 08 — Terraform için GitOps: Flux TF Denetleyicisi, Spacelift ve Drift Tespiti
- Madde 09 - Terraform, Pulumi ve OpenTofu: Son Karşılaştırma 2026
- Madde 10 - Terraform Kurumsal Kalıpları: Çalışma Alanı, Sentinel ve Ekip Ölçeklendirmesi







