Stare Terraform: backend la distanță cu S3/GCS, blocare și import
Dacă există un singur aspect al Terraform care provoacă cele mai multe accidente în producție, acesta
și conducerea statului. Am văzut echipe care pierd ore reparând state corupte,
configurații divergente deoarece doi dezvoltatori au făcut-o apply in paralel,
resurse „fantomă” care există în cloud, dar nu și în stat. Toate aceste probleme
au o cauză comună: stat local fără blocare.
Acest ghid este tehnic și practic: vom configura un backend S3 cu blocare DynamoDB pentru AWS,
backend-ul GCS pentru Google Cloud, vom vorbi despre spații de lucru pentru separarea mediilor
si vom vedea cum importa resursele existente fără timp de nefuncţionare
blocul import introdus în Terraform 1.5 (în final declarativ și sigur).
Ce vei învăța
- De ce statul local este periculos în echipe și cum să migrați la backend la distanță
- Configurați backend S3 cu blocarea DynamoDB pe AWS (gata pentru producție)
- Configurați backend-ul GCS pe Google Cloud Platform
- Workspace Terraform: model pentru separarea mediilor
- Importați activele existente cu blocare
import(Terraform 1.5+) - Operațiuni de urgență: manipulare de stare, deblocare forțată, backup
- Configurare parțială a backend: gestionați acreditările fără codificare
De ce backend-ul la distanță este fundamental
Dosarul terraform.tfstate localul are patru probleme fundamentale într-o echipă:
-
Fără blocare: dacă doi dezvoltatori execută
terraform applyîn același timp, ambele citesc aceeași stare și scriu modificări parțiale, conducând la stat corupt și la resurse duplicate. - Nu este distribuit: fiecare dezvoltator are propria copie locală. Cine are cele mai recente versiune? Cine a făcut ultima aplicație? Imposibil de știut.
- Conține secrete: starea include adesea valori sensibile (parola RDS, chei API). Nu intră niciodată în Git.
- Fără istoric: nu există nicio pistă de audit despre cine a făcut ce și când.
Backend-ul la distanță rezolvă toate aceste probleme: stare centralizată, blocare atomică, criptare în repaus și versiunea pentru rollback.
Bootstrap: Crearea de resurse de backend cu Terraform
Paradoxul backend-ului Terraform este că trebuie să creați resurse AWS (bucket S3 + tabel DynamoDB) Înainte pentru a putea configura backend-ul. Soluția este să bootstrap cu un mini-proiect separat care utilizează backend-ul local.
# bootstrap/main.tf — crea le risorse per il backend remoto
# Questo progetto usa il backend locale (terraform.tfstate nella directory)
# Committalo nel repo come "infra/bootstrap/"
terraform {
required_version = ">= 1.8.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# NOTA: qui non c'e backend block — usa il file locale
}
provider "aws" {
region = "eu-west-1"
}
locals {
name = "acme"
environment = "global"
}
# S3 Bucket per lo state
resource "aws_s3_bucket" "terraform_state" {
bucket = "${local.name}-terraform-state"
# Protezione contro cancellazione accidentale
lifecycle {
prevent_destroy = true
}
tags = {
Name = "${local.name}-terraform-state"
ManagedBy = "Terraform"
Environment = local.environment
}
}
# Versioning: ogni apply crea una nuova versione del file di state
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
# Encryption at rest: obbligatorio per state con segreti
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
bucket_key_enabled = true # Riduce i costi KMS del 99%
}
}
# Blocca accesso pubblico: lo state NON deve essere pubblico
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Policy del bucket: nega qualsiasi accesso non-TLS
resource "aws_s3_bucket_policy" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DenyNonTLS"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.terraform_state.arn,
"${aws_s3_bucket.terraform_state.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
}
]
})
}
# DynamoDB table per il locking
resource "aws_dynamodb_table" "terraform_locks" {
name = "${local.name}-terraform-locks"
billing_mode = "PAY_PER_REQUEST" # Nessun costo fisso
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
# Protezione contro cancellazione
lifecycle {
prevent_destroy = true
}
tags = {
Name = "${local.name}-terraform-locks"
ManagedBy = "Terraform"
Environment = local.environment
}
}
# Output: valori da copiare nel backend block dei progetti
output "state_bucket_name" {
value = aws_s3_bucket.terraform_state.id
description = "Nome del bucket S3 per lo state Terraform"
}
output "dynamodb_table_name" {
value = aws_dynamodb_table.terraform_locks.name
description = "Nome della tabella DynamoDB per il locking"
}
output "aws_region" {
value = "eu-west-1"
description = "Region AWS dove sono create le risorse del backend"
}
Configurați backend-ul S3
După ce ați creat resursele bootstrap, configurați backend-ul în proiecte.
Blocul backend nu acceptă variabile Terraform (și o limitare cunoscută),
deci folosiți configurație backend parțială pentru a separa acreditările.
# versions.tf — configurazione backend S3
terraform {
required_version = ">= 1.8.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# Backend con partial configuration (valori statici non-sensibili)
backend "s3" {
bucket = "acme-terraform-state"
key = "environments/prod/networking/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "acme-terraform-locks"
encrypt = true
# NON mettere access_key e secret_key qui!
# Usa variabili di ambiente o IAM Role
}
}
# La struttura raccomandata per il "key" (path del file):
# {account}/{environment}/{stack}/terraform.tfstate
#
# Esempi:
# "123456789/dev/networking/terraform.tfstate"
# "123456789/prod/eks-cluster/terraform.tfstate"
# "123456789/shared/monitoring/terraform.tfstate"
# Inizializzazione con partial backend config
# (passa i valori mancanti come -backend-config)
terraform init \
-backend-config="bucket=acme-terraform-state" \
-backend-config="key=environments/dev/networking/terraform.tfstate" \
-backend-config="region=eu-west-1" \
-backend-config="dynamodb_table=acme-terraform-locks"
# Oppure con file di backend config
# backend.hcl (NON committare se contiene credenziali)
# bucket = "acme-terraform-state"
# key = "environments/dev/networking/terraform.tfstate"
# region = "eu-west-1"
# dynamodb_table = "acme-terraform-locks"
terraform init -backend-config=backend.hcl
# Migra da backend locale a remoto
terraform init -migrate-state
# Terraform chiede conferma prima di copiare il state locale su S3
Blocarea DynamoDB
Când terraform apply și rulează, un rând este creat în DynamoDB
cu LockID = {bucket}/{key}. Dacă procesul este oprit brusc
(ucidere, prăbușire), blocarea poate să nu fie eliberată. Pentru a forța deblocarea:
terraform force-unlock {LOCK_ID}
# Il LOCK_ID e visibile nel messaggio di errore quando Terraform trova il lock
STATELE UNITE ALE AMERICII force-unlock numai dacă ești sigur că nu este nimeni altcineva
aplica in curs de desfasurare. Deblocarea în timp ce o aplicație este în curs poate deteriora starea.
GCS Backend pentru Google Cloud Platform
# versions.tf — backend GCS
terraform {
required_version = ">= 1.8.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
backend "gcs" {
bucket = "acme-terraform-state-eu"
prefix = "environments/prod/networking"
# prefix = "environments/{environment}/{stack}"
# Il file effettivo sara: {prefix}/default.tfstate
}
}
# Crea il bucket GCS per lo state (bootstrap)
resource "google_storage_bucket" "terraform_state" {
name = "acme-terraform-state-eu"
location = "EU"
force_destroy = false
# Versioning per rollback
versioning {
enabled = true
}
# Encryption con CMEK (opzionale ma consigliato)
# encryption {
# default_kms_key_name = google_kms_crypto_key.terraform_state.id
# }
# Policy di retention: mantieni le versioni per 30 giorni
lifecycle_rule {
condition {
num_newer_versions = 5
with_state = "ARCHIVED"
}
action {
type = "Delete"
}
}
}
# Blocca l'accesso pubblico al bucket
resource "google_storage_bucket_iam_binding" "terraform_state_private" {
bucket = google_storage_bucket.terraform_state.name
role = "roles/storage.admin"
members = [
"serviceAccount:terraform@acme-project.iam.gserviceaccount.com"
]
}
Spațiul de lucru Terraform: Separarea mediului
Spațiile de lucru Terraform vă permit să mențineți fișiere de stare separate de la
aceeași configurație HCL. Fiecare spațiu de lucru are propriul fișier de stare în backend:
env:/{workspace_name}/terraform.tfstate.
# Gestione dei workspace
terraform workspace list
# * default
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
terraform workspace list
# default
# * dev
# staging
# prod
terraform workspace select prod
terraform workspace show # stampa il nome del workspace corrente
# Cancella un workspace (deve essere vuoto)
terraform workspace delete staging
# Uso del workspace nella configurazione HCL
locals {
# Recupera il nome del workspace corrente
workspace = terraform.workspace
# Configurazioni specifiche per workspace
workspace_config = {
dev = {
instance_type = "t3.micro"
min_instances = 1
max_instances = 2
enable_monitoring = false
rds_class = "db.t3.micro"
multi_az = false
}
staging = {
instance_type = "t3.small"
min_instances = 1
max_instances = 3
enable_monitoring = true
rds_class = "db.t3.small"
multi_az = false
}
prod = {
instance_type = "t3.medium"
min_instances = 2
max_instances = 6
enable_monitoring = true
rds_class = "db.t3.medium"
multi_az = true
}
}
# Accede alla configurazione del workspace corrente
# con fallback a "dev" se il workspace non e nella mappa
current_config = lookup(local.workspace_config, local.workspace, local.workspace_config["dev"])
# Naming con workspace nel prefisso
name_prefix = "${local.workspace}-${var.project_name}"
}
# Usa i valori dalla configurazione del workspace
resource "aws_autoscaling_group" "web" {
min_size = local.current_config.min_instances
max_size = local.current_config.max_instances
desired_capacity = local.current_config.min_instances
# ...
}
resource "aws_db_instance" "main" {
instance_class = local.current_config.rds_class
multi_az = local.current_config.multi_az
# ...
}
Spațiul de lucru vs directoare separate: când să folosiți ce
Spațiile de lucru sunt potrivite pentru medii identice din punct de vedere structural care diferă numai pentru scalare (dev/staging/prod). Dacă mediile au arhitecturi diferite (de exemplu, prod are un VPN, dev nu), folosește directoare separate care reutiliza modulele. Multe echipe cu infrastructuri complexe preferă întotdeauna directoare separate pentru o claritate maximă.
Importați resursele existente cu Blocul de import
Unul dintre cele mai frecvente cazuri când se adoptă Terraform într-o organizație existentă este nevoia
gestionați resursele create manual sau cu alte instrumente. Înainte de Terraform 1.5, importul era
numai imperativ (terraform import CLI), riscant și neverificabil.
The bloc import (GA în 1.5, îmbunătățit în 1.7 cu
import generate) și declarativ și încrezător.
# main.tf — import dichiarativo (Terraform 1.5+)
# Step 1: definisci il blocco import
import {
# ID della risorsa nel cloud provider
id = "vpc-0123456789abcdef0"
# Punta alla risorsa HCL che vuoi associare
to = aws_vpc.main
}
# Step 2: scrivi la configurazione HCL della risorsa
# (puoi usare "terraform plan -generate-config-out=generated.tf" per auto-generarla)
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "prod-main-vpc"
Environment = "prod"
ManagedBy = "Terraform" # Aggiunta da noi
}
}
# Step 3: terraform plan verifica che lo state da importare
# corrisponda alla configurazione HCL.
# Se ci sono differenze, Terraform le evidenzia come "~ to change"
# Import multiplo: importa un intero gruppo di risorse
import {
id = "subnet-0abc123"
to = aws_subnet.public[0]
}
import {
id = "subnet-0def456"
to = aws_subnet.public[1]
}
# Import con for_each (Terraform 1.7+)
locals {
existing_subnets = {
"public-1" = "subnet-0abc123"
"public-2" = "subnet-0def456"
}
}
import {
for_each = local.existing_subnets
id = each.value
to = aws_subnet.public[each.key]
}
# Workflow completo di import
# 1. Genera automaticamente la configurazione HCL dalla risorsa esistente
# (Terraform 1.5+ con -generate-config-out)
terraform plan -generate-config-out=generated_imports.tf
# 2. Rivedi il file generato: contiene la configurazione della risorsa
# come Terraform la vede nel cloud provider
cat generated_imports.tf
# 3. Copia e adatta la configurazione nel tuo main.tf
# (il file generated non e perfetto, va revisionato)
# 4. Esegui plan per verificare che l'import sia corretto
terraform plan
# Se il plan mostra "Plan: 0 to add, 0 to change, 0 to destroy"
# con note "Will import", l'import e perfetto
# 5. Apply: esegue l'import effettivo e aggiorna lo state
terraform apply
# 6. Rimuovi i blocchi import dal codice dopo l'apply
# (non sono piu necessari una volta che le risorse sono nello state)
Manipulare de stat: operațiuni de urgență
Sunt situații în care este necesară manipularea directă a statului. Aceste operațiuni sunt puternice și riscante: efectuați-le întotdeauna cu o copie de rezervă preventivă.
# SEMPRE fare backup prima di operazioni sullo state
terraform state pull > backup-$(date +%Y%m%d-%H%M%S).tfstate
# Rimuove una risorsa dallo state (la risorsa rimane nel cloud)
# Uso: quando vuoi che Terraform "dimentichi" una risorsa
# senza cancellarla (es. passi a gestirla con un altro tool)
terraform state rm aws_instance.legacy_server
# Sposta una risorsa da un indirizzo a un altro nello state
# Uso: refactoring del codice senza distruggere le risorse
terraform state mv aws_instance.web aws_instance.web_new
terraform state mv 'aws_subnet.public[0]' 'aws_subnet.public["eu-west-1a"]'
# Mostra i dettagli JSON di una risorsa nello state
terraform state show aws_vpc.main
# Pull dello state remoto in locale (utile per ispezione)
terraform state pull > current.tfstate
# Push di uno state locale sul backend remoto
# PERICOLOSO: sovrascrive lo state remoto senza conferma
# Usare solo per recovery da state corrotto
terraform state push recovered.tfstate
# Refresh: aggiorna lo state leggendo lo stato reale del cloud
# (ora deprecato a favore di terraform apply -refresh-only)
terraform apply -refresh-only
Stat Partitioning: Strategii pentru echipe mari
În organizațiile cu infrastructuri mari, a avea o singură stare monolitică devine
o problemă: fiecare plan trebuie să controleze sute de resurse, blochează blocarea
întreaga echipă, iar o eroare într-un modul blochează întreaga desfășurare. Soluția este împărțirea
tu stai in el stive independente.
# Struttura raccomandata per infrastrutture large-scale
# Ogni directory e un progetto Terraform indipendente con il suo state
environments/
├── prod/
│ ├── networking/ # State: prod/networking/terraform.tfstate
│ │ ├── main.tf
│ │ └── versions.tf
│ ├── eks-cluster/ # State: prod/eks-cluster/terraform.tfstate
│ │ ├── main.tf
│ │ └── versions.tf
│ ├── databases/ # State: prod/databases/terraform.tfstate
│ │ ├── main.tf
│ │ └── versions.tf
│ └── monitoring/ # State: prod/monitoring/terraform.tfstate
│ ├── main.tf
│ └── versions.tf
└── dev/
└── ...
# Per passare informazioni tra stack: terraform_remote_state data source
# stack eks-cluster recupera gli output da networking
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "acme-terraform-state"
key = "environments/prod/networking/terraform.tfstate"
region = "eu-west-1"
}
}
resource "aws_eks_cluster" "main" {
name = "prod-cluster"
role_arn = aws_iam_role.eks.arn
vpc_config {
# Usa gli output dallo stack networking
subnet_ids = data.terraform_remote_state.networking.outputs.private_subnet_ids
endpoint_private_access = true
endpoint_public_access = false
}
}
# Alternativa moderna: usare variabili con file .tfvars invece di
# terraform_remote_state. Piu sicuro (evita dipendenze tra state),
# meno conveniente (richiede aggiornamento manuale dei valori)
Politica IAM pentru backend-ul S3
În producție, rolul IAM utilizat de Terraform (dezvoltatori sau CI/CD) trebuie să aibă permisiunile minime necesare pentru backend. Iată politica completă:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TerraformStateBucketAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:GetBucketVersioning",
"s3:GetEncryptionConfiguration"
],
"Resource": [
"arn:aws:s3:::acme-terraform-state",
"arn:aws:s3:::acme-terraform-state/*"
]
},
{
"Sid": "TerraformStateLocking",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:DescribeTable"
],
"Resource": "arn:aws:dynamodb:eu-west-1:*:table/acme-terraform-locks"
},
{
"Sid": "TerraformKMSDecryptState",
"Effect": "Allow",
"Action": [
"kms:GenerateDataKey",
"kms:Decrypt"
],
"Resource": "arn:aws:kms:eu-west-1:*:key/*"
}
]
}
Concluzii și pașii următori
Managementul de stat este fundația pe care construiești orice altceva în configurația ta Terraform. Un backend la distanță cu blocare adecvată vă protejează de o întreagă clasă a accidentelor operaționale. Fă-ți timp pentru a-l configura corect de la început: migrați de la stat local la distanță pe un proiect existent și fezabil, dar laborios.
Următorul pas este să integrați Terraform într-o conductă profesională CI/CD:
nu executa doar plan e apply automat, dar gestionați
fluxul de lucru de revizuire a planului în Pull Requests, blocarea aplicațiilor neautorizate
și notificarea derivelor detectate.
Seria completă: Terraform și IaC
- Articolul 01 — Terraform de la zero: HCL, Provider și Plan-Apply-Destroy
- Articolul 02 — Proiectarea modulelor Terraform reutilizabile: Structură, I/O și Registry
- Articolul 03 (acest) — Stare Terraform: Backend la distanță cu S3/GCS, Blocare și Import
- Articolul 04 — Terraform în CI/CD: Flux de lucru GitHub Actions, Atlantis și Pull Request
- Articolul 05 — Testarea IaC: Terratest, Terraform Native Test și Contract Testing
- Articolul 06 — Securitatea IaC: Politica Checkov, Trivy și OPA ca cod
- Articolul 07 — Terraform Multi-Cloud: AWS + Azure + GCP cu module partajate
- Articolul 08 — GitOps pentru Terraform: Controler Flux TF, Spacelift și Detecție Drift
- Articolul 09 – Terraform vs Pulumi vs OpenTofu: Comparație finală 2026
- Articolul 10 — Modele Terraform Enterprise: Spațiu de lucru, Sentinel și scalarea echipei







