HCL 语法基础

HCL 简介

HCL(HashiCorp Configuration Language)是 Terraform 使用的配置语言。它是一种声明式语言,专门为定义基础设施而设计。

HCL 特点

  • 人类可读:类似 JSON,但更简洁
  • 声明式:描述想要的状态,而非步骤
  • 支持注释:单行和多行注释
  • 类型安全:支持字符串、数字、布尔、列表、映射等类型

基本语法

块(Block)

块是 HCL 的基本结构单元:

# 基本块语法
<BLOCK_TYPE> "<BLOCK_LABEL>" "<BLOCK_LABEL>" {
  # 块体
  <IDENTIFIER> = <EXPRESSION>
}

# 示例
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
}

参数(Argument)

参数是块内的键值对:

resource "aws_instance" "web" {
  # 参数名 = 参数值
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  # 参数值可以是表达式
  count = var.instance_count
}

注释

# 单行注释

// 也可以使用双斜杠

/*
  多行注释
  可以跨越多行
*/

resource "aws_instance" "web" {
  ami = "ami-xxx"  # 行尾注释
}

数据类型

基本类型

字符串(String)

variable "region" {
  default = "us-east-1"
}

# 多行字符串
variable "user_data" {
  default = <<-EOT
    #!/bin/bash
    echo "Hello World"
    apt-get update
  EOT
}

# 字符串插值
locals {
  instance_name = "web-${var.environment}"
  message       = "Instance type is ${var.instance_type}"
}

数字(Number)

variable "instance_count" {
  default = 3
}

variable "disk_size" {
  default = 100.5
}

布尔(Bool)

variable "enable_monitoring" {
  default = true
}

variable "is_production" {
  default = false
}

null

variable "optional_setting" {
  default = null
}

集合类型

列表(List)

variable "availability_zones" {
  type = list(string)
  default = [
    "us-east-1a",
    "us-east-1b",
    "us-east-1c"
  ]
}

# 访问列表元素
locals {
  first_az  = var.availability_zones[0]
  second_az = var.availability_zones[1]
}

# 列表可以包含不同类型
variable "mixed_list" {
  default = ["string", 123, true]
}

集合(Set)

variable "allowed_ips" {
  type = set(string)
  default = [
    "10.0.1.0/24",
    "10.0.2.0/24"
  ]
}

映射(Map)

variable "instance_types" {
  type = map(string)
  default = {
    dev  = "t2.micro"
    prod = "t2.large"
  }
}

# 访问映射值
locals {
  dev_type  = var.instance_types["dev"]
  prod_type = var.instance_types["prod"]
}

# 带类型的映射
variable "tags" {
  type = map(string)
  default = {
    Environment = "Production"
    Project     = "MyApp"
  }
}

对象(Object)

variable "server_config" {
  type = object({
    instance_type = string
    disk_size     = number
    enable_backup = bool
  })
  
  default = {
    instance_type = "t2.micro"
    disk_size     = 100
    enable_backup = true
  }
}

# 访问对象属性
locals {
  instance_type = var.server_config.instance_type
}

元组(Tuple)

variable "connection_info" {
  type = tuple([string, number, bool])
  default = ["localhost", 8080, true]
}

表达式

算术运算

locals {
  # 基本运算
  sum        = 2 + 3          # 5
  difference = 10 - 5         # 5
  product    = 4 * 5          # 20
  quotient   = 20 / 4         # 5
  remainder  = 10 % 3         # 1
  
  # 负数
  negative   = -5
  
  # 复杂表达式
  result     = (10 + 5) * 2   # 30
}

比较运算

locals {
  equal        = 5 == 5      # true
  not_equal    = 5 != 3      # true
  greater      = 10 > 5      # true
  greater_eq   = 10 >= 10    # true
  less         = 5 < 10      # true
  less_eq      = 5 <= 5      # true
}

逻辑运算

locals {
  and_result = true && false  # false
  or_result  = true || false  # true
  not_result = !true          # false
  
  # 复杂条件
  is_valid = var.count > 0 && var.count < 100
}

条件表达式

# 三元运算符
locals {
  instance_type = var.environment == "prod" ? "t2.large" : "t2.micro"
  
  # 嵌套条件
  size = var.environment == "prod" ? 100 : (
    var.environment == "staging" ? 50 : 10
  )
}

resource "aws_instance" "web" {
  instance_type = var.is_production ? "t2.large" : "t2.micro"
  
  tags = {
    Environment = var.environment == "prod" ? "Production" : "Development"
  }
}

函数

Terraform 提供了许多内置函数。

字符串函数

locals {
  # 连接字符串
  full_name = join("-", ["web", "server", "01"])  # "web-server-01"
  
  # 分割字符串
  parts = split("-", "web-server-01")  # ["web", "server", "01"]
  
  # 大小写转换
  upper_text = upper("hello")  # "HELLO"
  lower_text = lower("WORLD")  # "world"
  title_text = title("hello world")  # "Hello World"
  
  # 替换
  replaced = replace("hello world", "world", "terraform")  # "hello terraform"
  
  # 去除空格
  trimmed = trim("  hello  ")  # "hello"
  
  # 格式化
  formatted = format("Hello %s", "World")  # "Hello World"
  
  # 子字符串
  substring = substr("hello world", 0, 5)  # "hello"
}

数值函数

locals {
  # 最大最小值
  maximum = max(5, 12, 9)     # 12
  minimum = min(5, 12, 9)     # 5
  
  # 绝对值
  abs_value = abs(-5)         # 5
  
  # 向上取整
  ceil_value = ceil(5.2)      # 6
  
  # 向下取整
  floor_value = floor(5.9)    # 5
  
  # 对数
  log_value = log(8, 2)       # 3
  
  # 幂运算
  pow_value = pow(2, 3)       # 8
}

集合函数

locals {
  # 列表长度
  list_length = length(["a", "b", "c"])  # 3
  
  # 合并列表
  merged = concat(["a", "b"], ["c", "d"])  # ["a", "b", "c", "d"]
  
  # 去重
  unique_list = distinct(["a", "b", "a", "c"])  # ["a", "b", "c"]
  
  # 包含检查
  contains_item = contains(["a", "b", "c"], "b")  # true
  
  # 索引
  index_of = index(["a", "b", "c"], "b")  # 1
  
  # 切片
  sliced = slice(["a", "b", "c", "d"], 1, 3)  # ["b", "c"]
  
  # 反转
  reversed = reverse(["a", "b", "c"])  # ["c", "b", "a"]
  
  # 排序
  sorted = sort(["c", "a", "b"])  # ["a", "b", "c"]
}

映射函数

locals {
  map1 = { a = 1, b = 2 }
  map2 = { b = 3, c = 4 }
  
  # 合并映射
  merged_map = merge(map1, map2)  # { a = 1, b = 3, c = 4 }
  
  # 获取键列表
  keys_list = keys(map1)  # ["a", "b"]
  
  # 获取值列表
  values_list = values(map1)  # [1, 2]
  
  # 查找值
  lookup_value = lookup(map1, "a", "default")  # 1
  lookup_missing = lookup(map1, "z", "default")  # "default"
}

时间函数

locals {
  # 当前时间戳
  current_time = timestamp()  # "2024-01-09T10:00:00Z"
  
  # 格式化时间
  formatted_time = formatdate("YYYY-MM-DD", timestamp())
}

编码函数

locals {
  # Base64 编码
  encoded = base64encode("hello")
  decoded = base64decode(encoded)
  
  # JSON 编码
  json_string = jsonencode({
    name = "example"
    age  = 30
  })
  
  # JSON 解码
  json_object = jsondecode(json_string)
  
  # URL 编码
  url_encoded = urlencode("hello world")
}

文件系统函数

locals {
  # 读取文件
  file_content = file("${path.module}/config.txt")
  
  # 读取文件列表
  file_list = fileset("${path.module}", "*.tf")
  
  # 路径处理
  abs_path = abspath("./main.tf")
  dir_name = dirname("/path/to/file.txt")  # "/path/to"
  base_name = basename("/path/to/file.txt")  # "file.txt"
}

变量引用

输入变量引用

variable "instance_type" {
  default = "t2.micro"
}

resource "aws_instance" "web" {
  instance_type = var.instance_type
}

本地变量引用

locals {
  common_tags = {
    Environment = "Production"
    ManagedBy   = "Terraform"
  }
}

resource "aws_instance" "web" {
  tags = local.common_tags
}

资源属性引用

resource "aws_instance" "web" {
  ami = "ami-xxx"
  # ...
}

resource "aws_eip" "web_ip" {
  instance = aws_instance.web.id
}

output "instance_ip" {
  value = aws_instance.web.public_ip
}

数据源引用

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
}

resource "aws_instance" "web" {
  ami = data.aws_ami.ubuntu.id
}

循环和条件

for 表达式(列表)

variable "users" {
  default = ["alice", "bob", "charlie"]
}

locals {
  # 转换为大写
  upper_users = [for u in var.users : upper(u)]
  # ["ALICE", "BOB", "CHARLIE"]
  
  # 带条件的过滤
  long_names = [for u in var.users : u if length(u) > 4]
  # ["alice", "charlie"]
  
  # 带索引
  indexed = [for i, u in var.users : "${i}-${u}"]
  # ["0-alice", "1-bob", "2-charlie"]
}

for 表达式(映射)

variable "instance_types" {
  default = {
    web = "t2.micro"
    db  = "t2.small"
  }
}

locals {
  # 转换映射
  upper_keys = {
    for k, v in var.instance_types : upper(k) => v
  }
  # { WEB = "t2.micro", DB = "t2.small" }
  
  # 过滤映射
  filtered = {
    for k, v in var.instance_types : k => v if v == "t2.micro"
  }
  # { web = "t2.micro" }
}

count 元参数

variable "instance_count" {
  default = 3
}

resource "aws_instance" "web" {
  count = var.instance_count
  
  ami           = "ami-xxx"
  instance_type = "t2.micro"
  
  tags = {
    Name = "web-${count.index}"
  }
}

# 访问资源
output "instance_ids" {
  value = aws_instance.web[*].id
}

for_each 元参数

variable "instances" {
  default = {
    web = "t2.micro"
    db  = "t2.small"
  }
}

resource "aws_instance" "servers" {
  for_each = var.instances
  
  ami           = "ami-xxx"
  instance_type = each.value
  
  tags = {
    Name = each.key
  }
}

# 访问资源
output "instance_ips" {
  value = {
    for k, instance in aws_instance.servers : k => instance.public_ip
  }
}

dynamic 块

variable "ingress_rules" {
  default = [
    { port = 80, protocol = "tcp" },
    { port = 443, protocol = "tcp" },
  ]
}

resource "aws_security_group" "web" {
  name = "web-sg"
  
  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = ingress.value.protocol
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

最佳实践

1. 使用有意义的命名

# ❌ 不好
resource "aws_instance" "i" {
  ami = "ami-xxx"
}

# ✅ 好
resource "aws_instance" "web_server" {
  ami = "ami-xxx"
}

2. 使用注释

# Production web server configuration
resource "aws_instance" "web" {
  ami           = "ami-xxx"  # Ubuntu 20.04 LTS
  instance_type = "t2.micro" # Free tier eligible
}

3. 格式化代码

# 自动格式化
terraform fmt

# 递归格式化
terraform fmt -recursive

4. 验证语法

# 验证配置
terraform validate

5. 使用本地变量简化

# ❌ 重复代码
resource "aws_instance" "web" {
  tags = {
    Environment = "Production"
    ManagedBy   = "Terraform"
    Project     = "MyApp"
  }
}

resource "aws_instance" "db" {
  tags = {
    Environment = "Production"
    ManagedBy   = "Terraform"
    Project     = "MyApp"
  }
}

# ✅ 使用本地变量
locals {
  common_tags = {
    Environment = "Production"
    ManagedBy   = "Terraform"
    Project     = "MyApp"
  }
}

resource "aws_instance" "web" {
  tags = merge(local.common_tags, {
    Name = "Web Server"
  })
}

resource "aws_instance" "db" {
  tags = merge(local.common_tags, {
    Name = "Database"
  })
}

总结

HCL 语法要点:

  • 块结构:资源、变量、输出等都是块
  • 数据类型:字符串、数字、布尔、列表、映射、对象
  • 表达式:算术、比较、逻辑、条件
  • 函数:字符串、数值、集合、编码等内置函数
  • 引用:变量、资源、数据源引用
  • 循环:for 表达式、count、for_each
  • 最佳实践:命名规范、注释、格式化

掌握 HCL 语法是编写高质量 Terraform 代码的基础!


下一章Provider 和资源管理