7 min readDe Kicked Team
Terraform la scară: cum administrezi infrastructura multi-environment fără să-ți pierzi mințile
Cu Terraform e ușor să începi și greu să scalezi. Iată cum structurăm noi Terraform pentru 50+ environment-uri — gestionarea state-ului, designul modulelor, integrarea CI/CD și pattern-urile care ne țin întregi la minte.
- DevOps
- Terraform
- Infrastructure
- Automation
Primul tău proiect Terraform este magic. Scrii puțin HCL, rulezi terraform apply și infrastructura apare. Al 50-lea proiect este un coșmar de module copiate cu copy-paste, conflicte pe fișierele de state și un pipeline de CI care durează 40 de minute.
Administrăm Terraform în zeci de environment-uri ale clienților. Iată toate pattern-urile pe care le-am învățat pe pielea noastră.
Decizia monorepo vs. polyrepo
Este prima bifurcație a drumului, și majoritatea echipelor aleg greșit.
Monorepo (toată infrastructura într-un singur repo):
- ✅ Ușor de căutat și de refactorizat între environment-uri
- ✅ Un singur pipeline de CI de întreținut
- ❌ Blast radius — un merge greșit poate afecta totul
- ❌ Plan-uri lente pe măsură ce codebase-ul crește
Polyrepo (un repo per environment/proiect):
- ✅ Izolare — schimbările nu se pot scurge între environment-uri
- ✅ Cicluri de release independente
- ❌ Versionarea modulelor devine critică
- ❌ Mai greu de menținut consistența
Noi folosim o abordare hibridă: un repo per graniță logică (de exemplu, per client sau per produs), cu un registry de module partajat. Asta ne dă izolare acolo unde contează și reutilizare acolo unde ajută.
Structura repository-ului
După ani de iterație, acesta este layout-ul nostru standard:
infrastructure/
├── environments/
│ ├── production/
│ │ ├── networking/
│ │ │ ├── main.tf
│ │ │ ├── variables.tf
│ │ │ ├── outputs.tf
│ │ │ └── terraform.tfvars
│ │ ├── compute/
│ │ ├── database/
│ │ └── dns/
│ ├── staging/
│ │ └── ... (mirrors production)
│ └── shared/
│ ├── iam/
│ └── monitoring/
├── modules/
│ ├── vpc/
│ ├── server/
│ ├── database/
│ └── monitoring-stack/
└── terragrunt.hcl
Principii-cheie:
- Un fișier de state per grup de resurse —
networking,compute,databaseau fiecare propriul state. Unterraform applygreșit în compute nu-ți poate distruge rețeaua. - Environment-urile se oglindesc — Staging este structural identic cu production. Dacă merge în staging, merge în production.
- Modulele sunt versionate — Release-uri cu tag, semantic versioning. Production se leagă de versiuni stabile.
Gestionarea state-ului
State-ul remote nu este negociabil. Fișierele de state locale sunt o bombă cu ceas.
# backend.tf
terraform {
backend "s3" {
bucket = "kicked-terraform-state"
key = "production/networking/terraform.tfstate"
region = "eu-central-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
Reguli pe care le impunem:
- State locking — DynamoDB (AWS) sau echivalent. Doi ingineri care rulează
applysimultan îți vor corupe state-ul. - Criptare la repaus — Fișierele de state conțin secrete (parole de baze de date, chei API). Criptează întotdeauna.
- Fără intervenții manuale pe state —
terraform state mvșiterraform importdoar prin CI, cu audit trail. - Fișier de state per grup de resurse — Nu per environment. Un singur fișier de state pentru tot production-ul este rețeta pentru plan-uri de 20 de minute.
Terragrunt: stratul DRY
Terraform-ul brut devine repetitiv rapid. Fiecare environment are nevoie de aceeași configurație de backend, același setup de provider, aceleași pattern-uri de variabile. Terragrunt elimină duplicarea:
# environments/production/networking/terragrunt.hcl
terraform {
source = "../../../modules/vpc"
}
include "root" {
path = find_in_parent_folders()
}
inputs = {
environment = "production"
cidr_block = "10.0.0.0/16"
az_count = 3
}
# root terragrunt.hcl
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "kicked-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "eu-central-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
${path_relative_to_include()} generează automat chei de state unice pe baza căii directorului. Gata cu copy-paste-ul configurațiilor de backend.
Principii de design pentru module
Modulele proaste sunt mai rele decât lipsa modulelor. Iată ce face un modul bun:
1. Default-uri cu opinie, override complet
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.medium" # Sensible default
}
variable "monitoring_enabled" {
description = "Enable detailed monitoring"
type = bool
default = true # Safe default
}
Un modul ar trebui să funcționeze cu zero variabile opționale. Dar fiecare default ar trebui să poată fi suprascris.
2. Output-urile sunt API-ul tău
# Good: outputs everything downstream might need
output "vpc_id" { value = aws_vpc.main.id }
output "private_subnet_ids" { value = aws_subnet.private[*].id }
output "public_subnet_ids" { value = aws_subnet.public[*].id }
output "nat_gateway_ips" { value = aws_eip.nat[*].public_ip }
# Bad: "just look at the state file"
3. Validează devreme
variable "environment" {
type = string
validation {
condition = contains(["production", "staging", "development"], var.environment)
error_message = "Environment must be production, staging, or development."
}
}
Prinde greșelile la plan, nu la apply.
Pipeline-ul CI/CD
Nimeni nu rulează terraform apply local. Pipeline-ul nostru:
# Simplified CI flow
on:
pull_request:
paths: ["environments/**"]
jobs:
plan:
steps:
- name: Detect changed environments
# Only plan what changed
- name: terraform init
- name: terraform validate
- name: terraform plan
# Output saved as artifact
- name: Post plan as PR comment
# Reviewers see exact changes
apply:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: terraform apply
# Uses the saved plan — no drift between plan and apply
Reguli critice:
- Plan pe PR — Fiecare reviewer vede exact schimbările înainte de merge
- Apply la merge — Merge-ul în main declanșează apply-ul. Fără pași manuali.
- Plan-uri salvate — Artefactul de plan din PR este ceea ce se aplică. Fără surprize.
- Doar ce s-a schimbat — Nu face plan pe toate cele 50 de environment-uri când s-a schimbat un singur fișier
Detectarea drift-ului
Infrastructura derivă. Cineva dă click într-o consolă, echipa de securitate adaugă manual o regulă, un auto-scaler schimbă numărul de instanțe. Trebuie să detectezi asta:
# Run nightly via cron
terragrunt run-all plan -detailed-exitcode
# Exit code 0 = no changes (in sync)
# Exit code 1 = error
# Exit code 2 = changes detected (DRIFT!)
Rulăm detectarea drift-ului în fiecare noapte. Orice drift declanșează o alertă pe Slack și un tichet. Rezolvarea este întotdeauna aceeași: fie actualizezi Terraform-ul ca să reflecte realitatea, fie re-aplici ca să aduci realitatea la ce spune Terraform.
Gestionarea secretelor
Nu pune niciodată secrete în fișiere .tfvars. Opțiuni:
- Variabile de mediu —
TF_VAR_db_passwordsetat în CI - Provider-ul Vault — Citește secretele din HashiCorp Vault la momentul plan-ului
- SOPS — Criptează fișierele
.tfvarscu secrete folosind chei age/KMS - 1Password/AWS SSM — Referențiază secretele după cale, rezolvă-le la apply
Noi preferăm provider-ul Vault pentru producție și SOPS pentru setup-uri mai mici.
Greșeli frecvente pe care le reparăm
| Greșeală | Remediu |
|---|---|
| Un singur fișier de state uriaș | Împarte pe grupuri de resurse |
| Copy-paste între environment-uri | Folosește Terragrunt sau workspaces |
| Apply rulat local | Doar CI/CD, cu plan-uri salvate |
| Fără state locking | DynamoDB/echivalent, întotdeauna |
| Valori hardcodate în module | Variabile cu validare |
| Fără detectare de drift | Plan nocturn din cron |
| Secrete în state, necriptate | Backend criptat + output-uri sensitive |
Cum începi
Dacă Terraform-ul tău este deja o harababură, nu încerca să repari totul deodată:
- Săptămâna 1 — Mută state-ul pe un backend remote criptat, cu locking
- Săptămâna 2 — Împarte state-ul monolitic pe grupuri de resurse
- Săptămâna 3 — Extrage primul modul partajat
- Săptămâna 4 — Configurează CI/CD cu plan-on-PR
Sau lasă-ne pe noi. Am migrat echipe de la „un main.tf mare” la setup-uri multi-environment scalabile în mai puțin de o lună.