Terraform nel 2026: Novita e Best Practices
Terraform di HashiCorp rimane lo strumento di Infrastructure as Code (IaC) piu utilizzato al mondo. Con oltre il 70% di market share nel settore, e diventato uno standard de facto per gestire infrastrutture cloud. Nel 2026, Terraform ha raggiunto una maturita notevole con nuove funzionalita che semplificano la gestione di infrastrutture complesse.
Novita Terraform 1.7/1.8
Removed Block (1.7)
Finalmente un modo pulito per rimuovere risorse dallo state senza distruggerle:
# Rimuove la risorsa dallo state senza eliminarla nel cloud
removed {
from = aws_instance.legacy_server
lifecycle {
destroy = false
}
}
Prima dovevi usare terraform state rm, ora e dichiarativo e versionabile.
Import Block Migliorato (1.7+)
Import risorse esistenti direttamente nel codice:
import {
to = aws_instance.web_server
id = "i-0123456789abcdef0"
}
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "WebServer"
}
}
# Genera configurazione automatica
terraform plan -generate-config-out=generated.tf
Test Framework Nativo (1.6+)
Testing integrato nel linguaggio:
# tests/vpc.tftest.hcl
run "create_vpc" {
command = apply
assert {
condition = aws_vpc.main.cidr_block == "10.0.0.0/16"
error_message = "VPC CIDR block is incorrect"
}
}
run "check_subnets" {
command = plan
assert {
condition = length(aws_subnet.private) == 3
error_message = "Expected 3 private subnets"
}
}
terraform test
Provider-Defined Functions (1.8)
I provider possono ora definire funzioni custom:
# Funzione definita dal provider AWS
locals {
arn_parts = provider::aws::arn_parse(aws_s3_bucket.main.arn)
account_id = local.arn_parts.account
}
Struttura Progetti Enterprise
Layout Consigliato
terraform/
├── modules/ # Moduli riutilizzabili
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── compute/
│ ├── database/
│ └── monitoring/
├── environments/ # Configurazioni per ambiente
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── production/
├── tests/ # Test Terraform
│ ├── networking.tftest.hcl
│ └── compute.tftest.hcl
└── .github/
└── workflows/
└── terraform.yml
Moduli Riutilizzabili
modules/networking/main.tf:
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, {
Name = "${var.environment}-vpc"
})
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.environment}-private-${count.index + 1}"
Type = "private"
})
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.environment}-public-${count.index + 1}"
Type = "public"
})
}
modules/networking/variables.tf:
variable "environment" {
description = "Environment name"
type = string
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "private_subnet_cidrs" {
description = "CIDR blocks for private subnets"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
variable "public_subnet_cidrs" {
description = "CIDR blocks for public subnets"
type = list(string)
default = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
}
variable "availability_zones" {
description = "Availability zones"
type = list(string)
}
variable "tags" {
description = "Tags to apply to all resources"
type = map(string)
default = {}
}
Uso dei Moduli
environments/production/main.tf:
module "networking" {
source = "../../modules/networking"
environment = "production"
vpc_cidr = "10.0.0.0/16"
availability_zones = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
private_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
tags = {
Environment = "production"
ManagedBy = "terraform"
Project = "my-project"
}
}
module "compute" {
source = "../../modules/compute"
environment = "production"
vpc_id = module.networking.vpc_id
private_subnet_ids = module.networking.private_subnet_ids
instance_type = "t3.large"
min_size = 3
max_size = 10
}
State Management Best Practices
Remote State con Locking
backend.tf:
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "production/terraform.tfstate"
region = "eu-west-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
Setup DynamoDB per locking:
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform Lock Table"
}
}
State Isolation
Separa lo state per ambiente e componente:
s3://terraform-state/
├── networking/
│ ├── dev/terraform.tfstate
│ ├── staging/terraform.tfstate
│ └── production/terraform.tfstate
├── compute/
│ ├── dev/terraform.tfstate
│ └── production/terraform.tfstate
└── database/
└── production/terraform.tfstate
Data Sources per Cross-State
# Leggi output da altro state
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "my-company-terraform-state"
key = "networking/production/terraform.tfstate"
region = "eu-west-1"
}
}
# Usa i valori
resource "aws_instance" "web" {
subnet_id = data.terraform_remote_state.networking.outputs.private_subnet_ids[0]
}
Terraform Cloud/Enterprise
Quando Usarlo
| Scenario | Terraform OSS | Terraform Cloud |
|---|---|---|
| Team piccolo (1-3) | Si | Opzionale |
| Team medio (4-10) | Difficile | Consigliato |
| Enterprise (10+) | No | Necessario |
| Compliance/Audit | Limitato | Si |
| Policy as Code | No | Si (Sentinel) |
Setup Terraform Cloud
terraform {
cloud {
organization = "my-organization"
workspaces {
name = "my-app-production"
}
}
}
Sentinel Policies
Policy as Code per governance:
# policies/enforce-tags.sentinel
import "tfplan/v2" as tfplan
mandatory_tags = ["Environment", "Project", "Owner"]
aws_resources = filter tfplan.resource_changes as _, rc {
rc.provider_name matches "(.*)aws$" and
rc.mode is "managed" and
(rc.change.actions contains "create" or rc.change.actions contains "update")
}
tags_contain_mandatory = rule {
all aws_resources as _, resource {
all mandatory_tags as tag {
resource.change.after.tags contains tag
}
}
}
main = rule {
tags_contain_mandatory
}
CI/CD con Terraform
GitHub Actions
.github/workflows/terraform.yml:
name: Terraform
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
TF_VERSION: 1.8.0
AWS_REGION: eu-west-1
jobs:
terraform:
runs-on: ubuntu-latest
defaults:
run:
working-directory: environments/production
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: $
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: $
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
id: plan
run: terraform plan -no-color -out=tfplan
continue-on-error: true
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplan
Security Best Practices
Secrets Management
MAI committare segreti. Usa:
# AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "production/database/password"
}
resource "aws_db_instance" "main" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
# HashiCorp Vault
data "vault_generic_secret" "db" {
path = "secret/database"
}
resource "aws_db_instance" "main" {
password = data.vault_generic_secret.db.data["password"]
}
Least Privilege IAM
# Policy minima per Terraform
data "aws_iam_policy_document" "terraform" {
statement {
effect = "Allow"
actions = [
"ec2:*",
"rds:*",
"s3:*"
]
resources = ["*"]
condition {
test = "StringEquals"
variable = "aws:RequestedRegion"
values = ["eu-west-1"]
}
}
}
State Encryption
# S3 backend con encryption
terraform {
backend "s3" {
bucket = "terraform-state"
key = "production/terraform.tfstate"
region = "eu-west-1"
encrypt = true
kms_key_id = "alias/terraform-state-key"
dynamodb_table = "terraform-locks"
}
}
Performance e Ottimizzazione
Parallelismo
# Aumenta parallelismo (default: 10)
terraform apply -parallelism=20
# Riduci per rate limiting
terraform apply -parallelism=5
Target Specifici
# Apply solo risorse specifiche
terraform apply -target=module.networking
terraform apply -target=aws_instance.web[0]
Refresh Selettivo
# Skip refresh (piu veloce, meno sicuro)
terraform plan -refresh=false
# Refresh solo specifiche risorse
terraform apply -refresh-only -target=aws_instance.web
Checklist Best Practices
Codice
- Usa moduli per codice riutilizzabile
- Variabili con tipi e validazione
- Output documentati
- README per ogni modulo
- Formatting consistente (
terraform fmt)
State
- Remote state con locking
- State isolation per ambiente
- Encryption at rest
- Backup automatici
CI/CD
- Plan su ogni PR
- Apply solo da main
- Review obbligatoria
- Test automatizzati
Security
- No secrets in code
- Least privilege IAM
- State encryption
- Audit logging
Conclusioni
Terraform nel 2026 e piu maturo e potente che mai. Le novita come il test framework nativo, l'import dichiarativo e le provider-defined functions rendono lo sviluppo IaC piu produttivo.
Raccomandazioni chiave:
- Struttura modulare fin dall'inizio
- Remote state con locking sempre
- CI/CD per ogni ambiente
- Test per moduli critici
- Terraform Cloud per team > 3 persone