Terraform Variables e Locals: Configurazione Dinamica

Le variabili e i locals di Terraform sono elementi fondamentali per creare configurazioni flessibili, riutilizzabili e facilmente gestibili. Scopri come utilizzarli efficacemente per ottimizzare la tua infrastruttura come codice.

Quando si lavora con Terraform, la capacità di creare configurazioni dinamiche e parametrizzate rappresenta la differenza tra un codice rigido e monolitico e un'infrastruttura modulare e scalabile. Le variabili e i locals sono gli strumenti che permettono di raggiungere questo obiettivo, offrendo flessibilità nella gestione dei valori e nella creazione di logiche complesse.

Comprendere le Terraform Variables

Le variabili in Terraform fungono da parametri di input per i moduli e le configurazioni, permettendo di personalizzare il comportamento senza modificare il codice principale. Questo approccio favorisce la riusabilità e semplifica la gestione di ambienti multipli.

Tipi di Variabili Supportate

Terraform supporta diversi tipi di variabili, ciascuno adatto a specifici casi d'uso:

variable "instance_count" {
  description = "Numero di istanze da creare"
  type        = number
  default     = 1
}

variable "environment" {
  description = "Ambiente di deployment"
  type        = string
  default     = "development"
}

variable "availability_zones" {
  description = "Zone di disponibilità"
  type        = list(string)
  default     = ["us-west-2a", "us-west-2b"]
}

variable "tags" {
  description = "Tag per le risorse"
  type        = map(string)
  default     = {
    Project = "MyApp"
    Team    = "DevOps"
  }
}

variable "database_config" {
  description = "Configurazione database"
  type = object({
    engine         = string
    engine_version = string
    instance_class = string
    allocated_storage = number
  })
}

Definizione e Validazione delle Variabili

Le variabili possono essere arricchite con validazioni personalizzate per garantire che i valori forniti rispettino specifici criteri:

variable "instance_type" {
  description = "Tipo di istanza EC2"
  type        = string
  default     = "t3.micro"
  
  validation {
    condition     = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
    error_message = "Il tipo di istanza deve essere t3.micro, t3.small o t3.medium."
  }
}

variable "cidr_block" {
  description = "CIDR block per la VPC"
  type        = string
  
  validation {
    condition     = can(cidrhost(var.cidr_block, 0))
    error_message = "Il CIDR block deve essere un formato valido (es. 10.0.0.0/16)."
  }
}

Variabili Sensibili

Per gestire informazioni sensibili, Terraform offre l'attributo sensitive:

variable "database_password" {
  description = "Password del database"
  type        = string
  sensitive   = true
}

variable "api_keys" {
  description = "Chiavi API"
  type        = map(string)
  sensitive   = true
  default     = {}
}

Metodi di Assegnazione delle Variabili

Terraform offre multiple modalità per assegnare valori alle variabili, seguendo un ordine di precedenza specifico.

File terraform.tfvars

Il metodo più comune per definire i valori delle variabili:

# terraform.tfvars
environment = "production"
instance_count = 3
availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]

database_config = {
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = "db.t3.medium"
  allocated_storage = 100
}

tags = {
  Project     = "WebApp"
  Team        = "Backend"
  Environment = "production"
}

Variabili d'Ambiente

Per valori dinamici o sensibili, è possibile utilizzare variabili d'ambiente:

# Esportazione variabili d'ambiente
export TF_VAR_database_password="super_secret_password"
export TF_VAR_api_key="my-api-key-123"
export TF_VAR_environment="staging"

Passaggio via Command Line

Per override rapidi durante l'esecuzione:

terraform apply -var="instance_count=5" -var="environment=testing"

Locals: Valori Calcolati e Derivati

I locals permettono di definire valori calcolati che possono essere riutilizzati in tutta la configurazione, riducendo la duplicazione e migliorando la leggibilità.

Definizione dei Locals

locals {
  # Concatenazione di stringhe
  name_prefix = "${var.environment}-${var.project_name}"
  
  # Logica condizionale
  instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
  
  # Manipolazione di liste
  private_subnets = [for i, az in var.availability_zones : cidrsubnet(var.vpc_cidr, 8, i)]
  public_subnets  = [for i, az in var.availability_zones : cidrsubnet(var.vpc_cidr, 8, i + 10)]
  
  # Tag comuni
  common_tags = merge(var.tags, {
    Environment = var.environment
    ManagedBy   = "terraform"
    CreatedAt   = timestamp()
  })
  
  # Configurazioni complesse
  database_settings = {
    backup_retention = var.environment == "production" ? 30 : 7
    multi_az         = var.environment == "production" ? true : false
    storage_encrypted = true
  }
}

Locals Avanzati con Funzioni

I locals possono utilizzare le funzioni built-in di Terraform per elaborazioni complesse:

locals {
  # Filtraggio e trasformazione
  production_subnets = [
    for subnet in var.subnets : subnet
    if subnet.environment == "production"
  ]
  
  # Grouping per region
  instances_by_region = {
    for instance in var.instances : instance.region => instance...
  }
  
  # Calcoli matematici
  total_storage = sum([for db in var.databases : db.storage_size])
  
  # Validazioni condizionali
  valid_config = length(var.availability_zones) >= 2 && var.instance_count > 0
  
  # Manipolazione JSON
  config_json = jsonencode({
    environment = var.environment
    services    = var.services
    timestamp   = timestamp()
  })
}

Pattern e Best Practices

Organizzazione delle Variabili

Una struttura ben organizzata facilita la manutenzione e la comprensione:

# variables.tf
# ========================================
# CONFIGURAZIONE GENERALE
# ========================================
variable "project_name" {
  description = "Nome del progetto"
  type        = string
}

variable "environment" {
  description = "Ambiente (dev, staging, prod)"
  type        = string
  
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment deve essere dev, staging o prod."
  }
}

# ========================================
# CONFIGURAZIONE RETE
# ========================================
variable "vpc_cidr" {
  description = "CIDR block per la VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "availability_zones" {
  description = "Zone di disponibilità"
  type        = list(string)
}

# ========================================
# CONFIGURAZIONE COMPUTE
# ========================================
variable "instance_configs" {
  description = "Configurazioni delle istanze"
  type = map(object({
    instance_type = string
    min_size     = number
    max_size     = number
    desired_size = number
  }))
}

Utilizzo Combinato di Variables e Locals

Un esempio pratico di come combinare variabili e locals per una configurazione flessibile:

# main.tf
locals {
  # Configurazione ambiente-specifica
  env_config = {
    dev = {
      instance_type = "t3.micro"
      min_size     = 1
      max_size     = 2
      db_instance  = "db.t3.micro"
    }
    staging = {
      instance_type = "t3.small"
      min_size     = 1
      max_size     = 3
      db_instance  = "db.t3.small"
    }
    prod = {
      instance_type = "t3.medium"
      min_size     = 2
      max_size     = 10
      db_instance  = "db.r5.large"
    }
  }
  
  # Configurazione selezionata
  selected_config = local.env_config[var.environment]
  
  # Naming convention
  resource_prefix = "${var.project_name}-${var.environment}"
  
  # Tag standardizzati
  standard_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "terraform"
    Team        = var.team_name
  }
}

# Utilizzo nelle risorse
resource "aws_launch_template" "app" {
  name_prefix   = "${local.resource_prefix}-app-"
  image_id      = var.ami_id
  instance_type = local.selected_config.instance_type
  
  tag_specifications {
    resource_type = "instance"
    tags = merge(local.standard_tags, {
      Name = "${local.resource_prefix}-app-instance"
      Type = "application"
    })
  }
}

Gestione Avanzata delle Configurazioni

Configurazioni Multi-Ambiente

Per gestire efficacemente più ambienti, è possibile strutturare le variabili in modo gerarchico:

# environments/prod.tfvars
environment = "prod"
instance_count = 5

database_config = {
  instance_class    = "db.r5.xlarge"
  allocated_storage = 1000
  backup_retention  = 30
  multi_az         = true
}

monitoring = {
  detailed_monitoring = true
  log_retention_days = 90
  alerts_enabled     = true
}

# environments/dev.tfvars
environment = "dev"
instance_count = 1

database_config = {
  instance_class    = "db.t3.micro"
  allocated_storage = 20
  backup_retention  = 1
  multi_az         = false
}

monitoring = {
  detailed_monitoring = false
  log_retention_days = 7
  alerts_enabled     = false
}

Validazione e Controlli di Coerenza

Implementare controlli per garantire configurazioni valide:

locals {
  # Validazioni
  validation_errors = [
    for error in [
      length(var.availability_zones) < 2 ? "Almeno 2 AZ richieste" : null,
      var.instance_count > 10 && var.environment != "prod" ? "Max 10 istanze per non-prod" : null,
      var.database_config.allocated_storage < 20 ? "Storage minimo 20GB" : null
    ] : error if error != null
  ]
  
  # Stop execution if validation fails
  validated_config = length(local.validation_errors) > 0 ? tobool("Errori di validazione: ${join(", ", local.validation_errors)}") : true
}

Debugging e Troubleshooting

Per debugging delle configurazioni complesse, utilizzare output per visualizzare i valori calcolati:

output "debug_info" {
  description = "Informazioni di debug"
  value = {
    environment      = var.environment
    selected_config  = local.selected_config
    calculated_tags  = local.standard_tags
    subnet_cidrs     = local.private_subnets
    validation_status = local.validation_errors
  }
}

# Utilizzo di terraform console per testing
# terraform console
# > local.selected_config
# > var.environment
# > local.standard_tags

Conclusioni

L'utilizzo efficace delle terraform variables e dei locals è fondamentale per creare infrastrutture robuste e maintainabili. Le variabili forniscono la flessibilità necessaria per parametrizzare le configurazioni, mentre i locals permettono di implementare logiche complesse e calcoli derivati.

La chiave del successo risiede nell'organizzazione strutturata del codice, nell'implementazione di validazioni appropriate e nell'adozione di naming convention coerenti. Questi elementi, combinati con pattern di configurazione multi-ambiente, permettono di gestire infrastrutture complesse mantenendo la chiarezza e la riusabilità del codice.

Ricorda che una configurazione ben progettata oggi semplificherà significativamente la manutenzione e l'evoluzione della tua infrastruttura domani. Investire tempo nella progettazione di variabili e locals efficaci è sempre un investimento che ripaga nel lungo termine.