变量与输出深入

深入学习 Terraform 的变量系统和输出管理。

一、输入变量详解

1.1 变量类型

基础类型:

# 字符串
variable "instance_type" {
  type    = string
  default = "t2.micro"
}

# 数字
variable "instance_count" {
  type    = number
  default = 2
}

# 布尔值
variable "enable_monitoring" {
  type    = bool
  default = true
}

集合类型:

# 列表
variable "availability_zones" {
  type    = list(string)
  default = ["us-west-2a", "us-west-2b"]
}

# 集合(唯一值)
variable "allowed_ports" {
  type    = set(number)
  default = [80, 443, 22]
}

# 映射
variable "instance_tags" {
  type = map(string)
  default = {
    Environment = "Dev"
    Team        = "Platform"
  }
}

结构化类型:

# 对象
variable "database_config" {
  type = object({
    engine         = string
    engine_version = string
    instance_class = string
    allocated_storage = number
  })
  
  default = {
    engine         = "postgres"
    engine_version = "14.7"
    instance_class = "db.t3.micro"
    allocated_storage = 20
  }
}

# 元组(固定长度和类型的列表)
variable "subnet_config" {
  type = tuple([string, number, bool])
  default = ["10.0.0.0", 24, true]
}

1.2 变量验证

variable "environment" {
  type        = string
  description = "部署环境"
  
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "环境必须是 dev、staging 或 prod。"
  }
}

variable "instance_type" {
  type = string
  
  validation {
    condition     = can(regex("^t[2-3]\\.", var.instance_type))
    error_message = "实例类型必须是 t2 或 t3 系列。"
  }
}

variable "vpc_cidr" {
  type = string
  
  validation {
    condition     = can(cidrhost(var.vpc_cidr, 0))
    error_message = "必须是有效的 CIDR 块。"
  }
}

1.3 敏感变量

variable "database_password" {
  type      = string
  sensitive = true
  
  validation {
    condition     = length(var.database_password) >= 8
    error_message = "密码长度至少为 8 个字符。"
  }
}

# 使用敏感变量
resource "aws_db_instance" "main" {
  password = var.database_password  # 在日志中被隐藏
}

1.4 变量优先级

优先级从高到低:

  1. 命令行参数 -var
  2. 命令行变量文件 -var-file
  3. *自动加载的 .auto.tfvars
  4. terraform.tfvars
  5. 环境变量 TF_VAR_xxx
  6. 默认值 default
# 示例
export TF_VAR_environment=dev
terraform apply \
  -var-file="common.tfvars" \
  -var-file="prod.tfvars" \
  -var="instance_count=5"

二、输出值详解

2.1 基础输出

output "instance_id" {
  description = "EC2 实例 ID"
  value       = aws_instance.web.id
}

output "instance_public_ip" {
  description = "EC2 实例公网 IP"
  value       = aws_instance.web.public_ip
}

output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}

2.2 敏感输出

output "database_password" {
  description = "数据库密码"
  value       = random_password.db_password.result
  sensitive   = true
}

output "private_key" {
  description = "SSH 私钥"
  value       = tls_private_key.ssh.private_key_pem
  sensitive   = true
}

2.3 结构化输出

output "vpc_details" {
  description = "VPC 完整信息"
  value = {
    id              = aws_vpc.main.id
    cidr_block      = aws_vpc.main.cidr_block
    public_subnets  = aws_subnet.public[*].id
    private_subnets = aws_subnet.private[*].id
    nat_gateway_ips = aws_eip.nat[*].public_ip
  }
}

output "instance_summary" {
  description = "所有实例的摘要信息"
  value = [
    for instance in aws_instance.app : {
      id        = instance.id
      private_ip = instance.private_ip
      public_ip  = instance.public_ip
      az        = instance.availability_zone
    }
  ]
}

2.4 条件输出

output "load_balancer_dns" {
  description = "负载均衡器 DNS(如果启用)"
  value       = var.enable_lb ? aws_lb.main[0].dns_name : null
}

output "database_endpoint" {
  description = "数据库端点"
  value       = var.create_database ? aws_db_instance.main[0].endpoint : "Database not created"
}

2.5 输出依赖

output "connection_string" {
  description = "数据库连接字符串"
  value       = "postgresql://${var.db_username}:${var.db_password}@${aws_db_instance.main.endpoint}/${var.db_name}"
  sensitive   = true
  
  depends_on = [
    aws_db_instance.main,
    aws_security_group.db
  ]
}

三、Local Values(本地值)

3.1 基础用法

locals {
  # 组合变量
  environment_name = "${var.project_name}-${var.environment}"
  
  # 通用标签
  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "Terraform"
    Owner       = var.team_name
  }
  
  # 条件值
  instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
  
  # 计算值
  total_instances = var.instance_count * var.replica_count
}

3.2 使用本地值

resource "aws_instance" "app" {
  count         = local.total_instances
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.instance_type
  
  tags = merge(
    local.common_tags,
    {
      Name = "${local.environment_name}-app-${count.index + 1}"
    }
  )
}

3.3 复杂计算

locals {
  # 从 CIDR 计算子网
  vpc_cidr_parts = split("/", var.vpc_cidr)
  vpc_prefix     = local.vpc_cidr_parts[0]
  vpc_size       = local.vpc_cidr_parts[1]
  
  # 为每个 AZ 创建子网 CIDR
  subnet_cidrs = [
    for i, az in var.availability_zones :
    cidrsubnet(var.vpc_cidr, 8, i)
  ]
  
  # 构建资源映射
  instances_by_az = {
    for az in var.availability_zones :
    az => [
      for instance in aws_instance.app :
      instance.id if instance.availability_zone == az
    ]
  }
}

四、变量文件最佳实践

4.1 文件组织

project/
├── variables.tf          # 变量声明
├── terraform.tfvars      # 默认值(不提交)
├── dev.tfvars           # 开发环境
├── staging.tfvars       # 预发布环境
├── prod.tfvars          # 生产环境
└── example.tfvars       # 示例配置(提交到 Git)

4.2 variables.tf

# ========================================
# 通用配置
# ========================================
variable "project_name" {
  description = "项目名称"
  type        = string
}

variable "environment" {
  description = "环境名称"
  type        = string
  
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "环境必须是 dev、staging 或 prod。"
  }
}

# ========================================
# 网络配置
# ========================================
variable "vpc_cidr" {
  description = "VPC CIDR 块"
  type        = string
  default     = "10.0.0.0/16"
}

variable "availability_zones" {
  description = "可用区列表"
  type        = list(string)
}

# ========================================
# 计算资源配置
# ========================================
variable "instance_type" {
  description = "EC2 实例类型"
  type        = string
  default     = "t3.micro"
}

variable "instance_count" {
  description = "实例数量"
  type        = number
  default     = 2
  
  validation {
    condition     = var.instance_count > 0 && var.instance_count <= 10
    error_message = "实例数量必须在 1-10 之间。"
  }
}

4.3 dev.tfvars

project_name = "myapp"
environment  = "dev"

vpc_cidr            = "10.0.0.0/16"
availability_zones  = ["us-west-2a", "us-west-2b"]

instance_type  = "t3.micro"
instance_count = 1

enable_monitoring = false
enable_backup     = false

4.4 prod.tfvars

project_name = "myapp"
environment  = "prod"

vpc_cidr            = "10.1.0.0/16"
availability_zones  = ["us-west-2a", "us-west-2b", "us-west-2c"]

instance_type  = "t3.large"
instance_count = 3

enable_monitoring = true
enable_backup     = true
backup_retention  = 30

五、动态变量技巧

5.1 使用 for 表达式

# 创建映射
locals {
  subnet_tags = {
    for idx, cidr in local.subnet_cidrs :
    "subnet-${idx}" => {
      Name = "subnet-${idx}"
      CIDR = cidr
    }
  }
}

# 过滤列表
locals {
  prod_instances = [
    for instance in var.instances :
    instance if instance.environment == "prod"
  ]
}

5.2 条件表达式

locals {
  # 三元运算符
  instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
  
  # 默认值处理
  vpc_cidr = var.vpc_cidr != "" ? var.vpc_cidr : "10.0.0.0/16"
  
  # 复杂条件
  db_instance_class = (
    var.environment == "prod" ? "db.r5.2xlarge" :
    var.environment == "staging" ? "db.t3.large" :
    "db.t3.micro"
  )
}

5.3 合并策略

locals {
  # 合并默认标签和自定义标签
  instance_tags = merge(
    var.common_tags,
    var.custom_tags,
    {
      Name = "${var.project_name}-${var.environment}"
    }
  )
  
  # 深度合并配置
  full_config = merge(
    var.default_config,
    var.environment_config,
    {
      updated_at = timestamp()
    }
  )
}

六、输出使用场景

6.1 模块间通信

# 网络模块输出
output "vpc_id" {
  value = aws_vpc.main.id
}

output "private_subnet_ids" {
  value = aws_subnet.private[*].id
}

# 应用模块使用
module "network" {
  source = "./modules/network"
}

module "app" {
  source     = "./modules/app"
  vpc_id     = module.network.vpc_id
  subnet_ids = module.network.private_subnet_ids
}

6.2 生成配置文件

output "kubeconfig" {
  description = "Kubernetes 配置文件内容"
  value = templatefile("${path.module}/kubeconfig.tpl", {
    cluster_name     = aws_eks_cluster.main.name
    cluster_endpoint = aws_eks_cluster.main.endpoint
    cluster_ca       = aws_eks_cluster.main.certificate_authority[0].data
  })
  sensitive = true
}

6.3 CI/CD 集成

output "deployment_info" {
  description = "部署信息(供 CI/CD 使用)"
  value = jsonencode({
    load_balancer_url = "https://${aws_lb.main.dns_name}"
    docker_registry   = aws_ecr_repository.app.repository_url
    deploy_timestamp  = timestamp()
    environment       = var.environment
  })
}

七、最佳实践

7.1 变量命名规范

# ✅ 推荐:清晰、描述性强
variable "vpc_cidr_block" {}
variable "enable_nat_gateway" {}
variable "database_instance_class" {}

# ❌ 避免:模糊、缩写过度
variable "cidr" {}
variable "nat" {}
variable "db_class" {}

7.2 提供充分的文档

variable "instance_type" {
  description = <<-EOT
    EC2 实例类型。
    - dev: t3.micro
    - staging: t3.small
    - prod: t3.large 或更大
  EOT
  type        = string
  default     = "t3.micro"
}

7.3 使用类型约束

# ✅ 推荐:明确类型
variable "instance_count" {
  type    = number
  default = 2
}

# ❌ 避免:使用 any
variable "instance_count" {
  type = any
}

小结

掌握变量和输出系统的关键点:

  • 变量类型: 选择合适的类型确保数据正确性
  • 验证规则: 提前捕获配置错误
  • 敏感数据: 正确标记和处理敏感信息
  • 本地值: 减少重复和提高可读性
  • 输出值: 有效传递信息和集成

下一章我们将学习如何使用表达式和函数来增强配置的灵活性。