Stan Terraform: Zdalny backend z S3/GCS, blokowaniem i importem
Jeśli istnieje jeden aspekt Terraformu, który powoduje najwięcej wypadków w produkcji, jest to właśnie ten
i zarządzanie państwem. Widziałem zespoły marnujące godziny na naprawianie skorumpowanych państw,
rozbieżne konfiguracje, ponieważ zrobiło to dwóch programistów apply równolegle,
Zasoby „duchowe”, które istnieją w chmurze, ale nie w stanie. Wszystkie te problemy
mają wspólną przyczynę: stan lokalny bez blokowania.
Ten przewodnik ma charakter techniczny i praktyczny: skonfigurujemy backend S3 z blokadą DynamoDB dla AWS,
backend GCS dla Google Cloud, porozmawiamy o obszarach roboczych do oddzielania środowisk
i zobaczymy jak importować istniejące zasoby bez przestojów w użyciu
blok import wprowadzone w Terraform 1.5 (wreszcie deklaratywne i bezpieczne).
Czego się nauczysz
- Dlaczego stan lokalny jest niebezpieczny w zespołach i jak przeprowadzić migrację do zdalnego backendu
- Skonfiguruj backend S3 z blokowaniem DynamoDB na AWS (gotowy do produkcji)
- Skonfiguruj backend GCS na Google Cloud Platform
- Workspace Terraform: wzór oddzielania środowisk
- Importuj istniejące zasoby z blokadą
import(Terraforma 1.5+) - Operacje awaryjne: manipulacja stanem, wymuszone odblokowanie, kopia zapasowa
- Częściowa konfiguracja zaplecza: zarządzaj poświadczeniami bez konieczności kodowania
Dlaczego zdalny backend jest fundamentalny
Plik terraform.tfstate local ma cztery podstawowe problemy w zespole:
-
Brak blokady: jeśli dwóch programistów wykona
terraform applyjednocześnie odczytują ten sam stan i zapisują częściowe zmiany, co prowadzi do skorumpowania państwa i zduplikowania zasobów. - Nieudostępnione: każdy programista ma swoją własną kopię lokalną. Kto ma najnowsze wersja? Kto złożył ostatni wniosek? Niemożliwe, żeby wiedzieć.
- Zawiera sekrety: stan często zawiera wrażliwe wartości (hasło RDS, klucze API). Nigdy nie trafia do Gita.
- Brak historii: nie ma ścieżki audytu, kto co zrobił i kiedy.
Zdalny backend rozwiązuje wszystkie te problemy: stan scentralizowany, blokowanie atomowe, szyfrowanie w stanie spoczynku i wersjonowanie w celu wycofania zmian.
Bootstrap: Tworzenie zasobów backendu za pomocą Terraform
Paradoks backendu Terraform polega na tym, że trzeba stworzyć zasoby AWS (wiadro S3 + tabela DynamoDB) Zanim aby móc skonfigurować backend. Rozwiązaniem jest bootstrap za pomocą miniprojektu oddzielny, który korzysta z lokalnego zaplecza.
# 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"
}
Skonfiguruj backend S3
Po utworzeniu zasobów bootstrap skonfiguruj backend w swoich projektach.
Blok backend nie obsługuje zmiennych Terraform (i ma znane ograniczenie),
więc użyj częściowa konfiguracja backendu do oddzielenia poświadczeń.
# 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
Blokada DynamoDB
Gdy terraform apply i działa, w DynamoDB tworzony jest wiersz
z LockID = {bucket}/{key}. Jeśli proces zostanie nagle zatrzymany
(zabić, rozbić), blokada może nie zostać zwolniona. Aby wymusić odblokowanie:
terraform force-unlock {LOCK_ID}
# Il LOCK_ID e visibile nel messaggio di errore quando Terraform trova il lock
USA force-unlock tylko jeśli jesteś pewien że nie ma nikogo innego
aplikowanie w toku. Odblokowanie w trakcie stosowania może spowodować uszkodzenie stanu.
Backend GCS dla platformy Google Cloud
# 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"
]
}
Terraform obszaru roboczego: oddzielenie środowisk
Obszary robocze Terraform umożliwiają przechowywanie oddzielnych plików stanu
ta sama konfiguracja HCL. Każdy obszar roboczy ma swój własny plik stanu w zapleczu:
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
# ...
}
Obszar roboczy a oddzielne katalogi: kiedy czego używać
Przestrzenie robocze są odpowiednie dla środowisk strukturalnie identyczne które się różnią tylko do skalowania (dev/staging/prod). Jeśli środowiska mają różne architektury (np. prod ma VPN, deweloper nie), użyj osobne katalogi którzy ponownie wykorzystują moduły. Wiele zespołów o złożonej infrastrukturze zawsze preferuje osobne katalogi dla maksymalnej przejrzystości.
Importuj istniejące zasoby za pomocą bloku importu
Jednym z najczęstszych przypadków wdrożenia Terraform w istniejącej organizacji jest konieczność
zarządzaj zasobami utworzonymi ręcznie lub za pomocą innych narzędzi. Przed Terraformem 1.5 import był
tylko konieczne (terraform import CLI), ryzykowne i nieweryfikowalne.
The blok import (GA w 1.5, ulepszone w 1.7 z
import generate) oraz deklaratywny i pewny siebie.
# 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)
Manipulacja państwem: operacje awaryjne
Są sytuacje, w których konieczne jest bezpośrednie manipulowanie państwem. Operacje te są potężne i ryzykowne: zawsze wykonuj je z zapobiegawczą kopią zapasową.
# 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
Podział stanu: strategie dla dużych zespołów
W organizacjach z dużą infrastrukturą posiadanie jednego państwa monolitycznego staje się koniecznością
problem: każdy plan musi kontrolować setki zasobów, blokuje blok
całego zespołu, a błąd w jednym module blokuje całe wdrożenie. Rozwiązaniem jest podzielenie
zostajesz w nim niezależne stosy.
# 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)
Polityka uprawnień dla backendu S3
W środowisku produkcyjnym musi mieć rolę IAM używaną przez Terraform (programiści lub CI/CD). minimalne uprawnienia potrzebne dla backendu. Oto pełna polityka:
{
"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/*"
}
]
}
Wnioski i dalsze kroki
Zarządzanie stanem to podstawa, na której budujesz wszystko inne w swojej konfiguracji Terraforma. Zdalny backend z odpowiednim blokowaniem chroni Cię przed całą klasą wypadków operacyjnych. Poświęć trochę czasu na prawidłowe skonfigurowanie go od początku: migrować ze stanu lokalnego do zdalnego w ramach istniejącego i wykonalnego, ale pracochłonnego projektu.
Następnym krokiem jest integracja Terraform z profesjonalnym potokiem CI/CD:
nie tylko wykonuj plan e apply automatycznie, ale zarządzaj
przepływ pracy przeglądu planu w Pull Requests, blokowanie nieautoryzowanych aplikacji
i powiadamianie o wykrytych dryfach.
Kompletna seria: Terraform i IaC
- Artykuł 01 — Terraformuj od podstaw: HCL, dostawca i planuj-zastosuj-zniszcz
- Artykuł 02 — Projektowanie modułów Terraform wielokrotnego użytku: struktura, wejścia/wyjścia i rejestr
- Artykuł 03 (ten) — Stan Terraform: zdalny backend z S3/GCS, blokowaniem i importem
- Artykuł 04 — Terraform w CI/CD: GitHub Actions, Atlantis i przepływ pracy dotyczący żądania ściągnięcia
- Artykuł 05 – Testowanie IaC: test terenu, test rodzimy dla terenu i testowanie kontraktowe
- Artykuł 06 — Bezpieczeństwo IaC: Checkov, Trivy i OPA Policy-as-Code
- Artykuł 07 — Terraform Multi-Cloud: AWS + Azure + GCP z modułami współdzielonymi
- Artykuł 08 — GitOps dla Terraform: kontroler Flux TF, wykrywanie lotów kosmicznych i dryfu
- Artykuł 09 — Terraform vs Pulumi vs OpenTofu: ostateczne porównanie 2026
- Artykuł 10 — Wzorce Terraform Enterprise: przestrzeń robocza, strażnik i skalowanie zespołu







