Terraform Variables e Locals: Configurazione Dinamica
Terraform variables and locals are fundamental components for creating flexible, reusable, and maintainable infrastructure as code. Understanding how to effectively use these features enables developers and DevOps professionals to build dynamic configurations that adapt to different environments and requirements while maintaining clean, organized code structures.
Understanding Terraform Variables
Variables in Terraform serve as parameters for your modules and configurations, allowing you to customize deployments without modifying the core infrastructure code. They act as input parameters that can be set at runtime or through configuration files, making your Terraform configurations more flexible and reusable across different environments.
Terraform supports several variable types including string, number, bool, list, map, set, object, and tuple. Each type serves specific use cases and provides different levels of structure and validation capabilities.
Variable Declaration Syntax
Variables are declared using the variable block with optional attributes that define their behavior and constraints:
variable "instance_type" {
description = "EC2 instance type for the web server"
type = string
default = "t3.micro"
validation {
condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
error_message = "Instance type must be t3.micro, t3.small, or t3.medium."
}
}
variable "environment" {
description = "Deployment environment"
type = string
validation {
condition = can(regex("^(dev|staging|prod)$", var.environment))
error_message = "Environment must be dev, staging, or prod."
}
}
variable "tags" {
description = "Resource tags"
type = map(string)
default = {}
}
Complex Variable Types
Terraform variables excel when handling complex data structures that represent real-world infrastructure requirements:
variable "vpc_config" {
description = "VPC configuration object"
type = object({
cidr_block = string
enable_dns_hostnames = bool
enable_dns_support = bool
availability_zones = list(string)
public_subnets = list(string)
private_subnets = list(string)
})
default = {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
availability_zones = ["us-west-2a", "us-west-2b"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.3.0/24", "10.0.4.0/24"]
}
}
variable "application_ports" {
description = "List of application ports to expose"
type = list(number)
default = [80, 443, 8080]
}
Variable Input Methods
Terraform provides multiple ways to set variable values, each suited for different scenarios and security requirements. Understanding these methods helps you choose the appropriate approach for your deployment strategy.
Environment-Specific Configuration Files
Using terraform.tfvars files or environment-specific variable files allows you to maintain separate configurations for different deployment environments:
# dev.tfvars
environment = "dev"
instance_type = "t3.micro"
vpc_config = {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
availability_zones = ["us-west-2a", "us-west-2b"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.3.0/24", "10.0.4.0/24"]
}
tags = {
Environment = "development"
Project = "web-application"
Owner = "development-team"
}
# prod.tfvars
environment = "prod"
instance_type = "t3.large"
vpc_config = {
cidr_block = "10.1.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
public_subnets = ["10.1.1.0/24", "10.1.2.0/24", "10.1.3.0/24"]
private_subnets = ["10.1.4.0/24", "10.1.5.0/24", "10.1.6.0/24"]
}
tags = {
Environment = "production"
Project = "web-application"
Owner = "platform-team"
}
Environment Variables and Command Line
For sensitive data or CI/CD integration, environment variables and command-line arguments provide secure and automated ways to set variable values:
# Using environment variables
export TF_VAR_database_password="secure_password_123"
export TF_VAR_api_key="your_api_key_here"
# Command line variable assignment
terraform apply -var="environment=staging" -var="instance_count=3"
# Using variable files
terraform apply -var-file="staging.tfvars"
Local Values: Computed Configuration
Local values, defined using the locals block, are named values that you can use throughout your module. They are particularly useful for avoiding repetition, computing values based on other variables or resources, and creating more readable configurations.
Unlike variables, locals are computed within the module and cannot be overridden from outside. They serve as intermediate values that help organize and simplify complex expressions.
Basic Local Value Usage
locals {
# Simple computed values
region = data.aws_region.current.name
# String interpolation and formatting
name_prefix = "${var.environment}-${var.project_name}"
# Conditional logic
instance_count = var.environment == "prod" ? var.prod_instance_count : var.dev_instance_count
# Complex computations
availability_zones = slice(data.aws_availability_zones.available.names, 0, var.az_count)
# Common tags that combine variable inputs
common_tags = merge(var.tags, {
Environment = var.environment
ManagedBy = "terraform"
DeployedAt = timestamp()
Region = local.region
})
}
Advanced Local Value Patterns
Local values become powerful when handling complex data transformations and conditional logic:
locals {
# Environment-specific configurations
environment_config = {
dev = {
instance_type = "t3.micro"
min_size = 1
max_size = 2
desired_size = 1
}
staging = {
instance_type = "t3.small"
min_size = 2
max_size = 4
desired_size = 2
}
prod = {
instance_type = "t3.large"
min_size = 3
max_size = 10
desired_size = 5
}
}
# Select configuration based on environment
current_config = local.environment_config[var.environment]
# Subnet calculations
subnet_configs = [
for i, cidr in var.vpc_config.public_subnets : {
cidr_block = cidr
availability_zone = local.availability_zones[i % length(local.availability_zones)]
type = "public"
name = "${local.name_prefix}-public-${i + 1}"
}
]
# Security group rules generation
security_group_rules = flatten([
for port in var.application_ports : [
{
type = "ingress"
from_port = port
to_port = port
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "Allow inbound traffic on port ${port}"
}
]
])
}
Combining Variables and Locals Effectively
The true power of Terraform configuration emerges when variables and locals work together to create dynamic, maintainable infrastructure code. Variables provide the input interface, while locals handle the computational logic and data transformation.
Practical Implementation Example
Here's a comprehensive example showing how variables and locals collaborate in a real-world scenario:
# Variables for input parameters
variable "environment" {
description = "Deployment environment"
type = string
}
variable "project_name" {
description = "Name of the project"
type = string
}
variable "application_config" {
description = "Application configuration"
type = object({
ports = list(number)
health_check_path = string
cpu_threshold = number
memory_threshold = number
})
}
variable "scaling_config" {
description = "Auto-scaling configuration per environment"
type = map(object({
min_capacity = number
max_capacity = number
target_cpu = number
}))
}
# Locals for computed values and logic
locals {
# Basic computed values
full_name = "${var.project_name}-${var.environment}"
region = data.aws_region.current.name
# Environment-specific scaling configuration
scaling = var.scaling_config[var.environment]
# Load balancer configuration
lb_config = {
name = "${local.full_name}-alb"
load_balancer_type = "application"
subnets = data.aws_subnets.public.ids
security_groups = [aws_security_group.alb.id]
}
# Target group configurations for each port
target_groups = {
for port in var.application_config.ports : "port-${port}" => {
name = "${local.full_name}-tg-${port}"
port = port
protocol = "HTTP"
health_check = {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 5
interval = 30
path = var.application_config.health_check_path
matcher = "200"
}
}
}
# CloudWatch alarms configuration
cloudwatch_alarms = {
high_cpu = {
alarm_name = "${local.full_name}-high-cpu"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "120"
statistic = "Average"
threshold = var.application_config.cpu_threshold
alarm_description = "This metric monitors ecs cpu utilization"
}
high_memory = {
alarm_name = "${local.full_name}-high-memory"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "MemoryUtilization"
namespace = "AWS/ECS"
period = "120"
statistic = "Average"
threshold = var.application_config.memory_threshold
alarm_description = "This metric monitors ecs memory utilization"
}
}
# Common resource tags
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "terraform"
Region = local.region
}
}
Best Practices for Variables and Locals
Following established best practices ensures your Terraform configurations remain maintainable, secure, and efficient as they scale and evolve over time.
Variable Organization and Documentation
Organize variables logically and provide comprehensive documentation to improve team collaboration and code maintenance:
# variables.tf - Group related variables together
variable "networking" {
description = "Networking configuration for the infrastructure"
type = object({
vpc_cidr = string
availability_zones = list(string)
public_subnet_cidrs = list(string)
private_subnet_cidrs = list(string)
enable_nat_gateway = bool
enable_vpn_gateway = bool
})
validation {
condition = length(var.networking.availability_zones) >= 2
error_message = "At least 2 availability zones must be specified for high availability."
}
validation {
condition = length(var.networking.public_subnet_cidrs) == length(var.networking.private_subnet_cidrs)
error_message = "Number of public and private subnets must match."
}
}
Security Considerations
Handle sensitive data appropriately using Terraform's sensitive attribute and external secret management:
variable "database_password" {
description = "Password for the database instance"
type = string
sensitive = true
validation {
condition = length(var.database_password) >= 12
error_message = "Database password must be at least 12 characters long."
}
}
variable "api_keys" {
description = "API keys for external services"
type = map(string)
sensitive = true
default = {}
}
# Use locals to handle sensitive computations
locals {
# Database connection string with sensitive password
db_connection_string = "postgresql://${var.db_username}:${var.database_password}@${aws_db_instance.main.endpoint}/${var.db_name}"
# This local value will also be marked as sensitive automatically
}
Dynamic Configuration Patterns
Advanced patterns using variables and locals enable sophisticated dynamic configurations that adapt to changing requirements and environments.
Conditional Resource Creation
variable "enable_monitoring" {
description = "Enable CloudWatch monitoring and alerting"
type = bool
default = false
}
variable "enable_backup" {
description = "Enable automated backup for databases"
type = bool
default = true
}
locals {
# Conditional configurations
monitoring_config = var.enable_monitoring ? {
dashboard_name = "${local.full_name}-dashboard"
log_group_name = "/aws/ecs/${local.full_name}"
retention_days = 30
} : null
backup_config = var.enable_backup ? {
backup_window = "03:00-04:00"
backup_retention = 7