Terraform的模块及依赖引用

1. 模块(Modules)

1.1. module简介

Terraform 模块是一组位于单个目录中的 Terraform 配置文件,即使是一个包含了一个或多个 .tf 文件的单一目录的简单配置也被视为一个模块。模块是 Terraform 中代码复用的主要方法,它们通过指定可以检索代码的源来重复使用。

模块可以多层嵌套,类似代码中的函数。

Module 就是一组 .tf 文件的集合,用来创建一组相关的基础设施资源。

比如:

  • 一个 vpc 模块:创建 VPC、子网、路由表
  • 一个 rds 模块:创建数据库实例、安全组
  • 一个 eks 模块:创建 Kubernetes 集群

1.2. Module 的基本结构

modules/
└── vpc/
    ├── main.tf          # 资源定义
    ├── variables.tf     # 输入参数
    └── outputs.tf       # 输出结果

modules/vpc/main.tf

resource "aws_vpc" "main" {
  cidr_block = var.cidr_block

  tags = {
    Name = var.name
  }
}

resource "aws_subnet" "private" {
  count = length(var.private_subnets)

  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnets[count.index]
  availability_zone = element(var.azs, count.index)

  tags = {
    Name = "private-${element(var.azs, count.index)}"
  }
}

variables.tf

variable "cidr_block" {
  type = string
}

variable "name" {
  type = string
}

variable "private_subnets" {
  type = list(string)
}

variable "azs" {
  type = list(string)
}

outputs.tf

output "vpc_id" {
  value = aws_vpc.main.id
}

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

1.3. 在根模块中使用 Module

方法 1:本地模块

module "prod_vpc" {
  source = "./modules/vpc"

  cidr_block = "10.0.0.0/16"
  name       = "prod-vpc"

  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  azs             = ["us-west-2a", "us-west-2b", "us-west-2c"]
}

方法 2:远程模块

module "vpc" {
  source = "git::https://github.com/your-org/terraform-modules.git//vpc?ref=v1.0.0"

  cidr_block = "10.10.0.0/16"
  name       = "dev-vpc"
  # ... 其他参数
}

支持来源类型:

  • ./path/to/module — 本地
  • github.com/org/repo//dir
  • git::https://...
  • terraform-registry-mirror/internal/modules/vpc

1.4. Module 的输入与输出

1. 输入(Input Variables)

通过 variables.tf 定义参数,在调用时传入。

module "xxx" {
  source = "..."
  param1 = "value1"
  param2 = true
}

2. 输出(Outputs)

其他模块或根配置可以通过 module.<name>.<output> 引用输出值。

# 假设 module.vpc 输出了 vpc_id 和 subnet_ids
resource "aws_instance" "app" {
  subnet_id = module.prod_vpc.private_subnet_ids[0]
  # ...
}

1.5. 实际使用场景示例

VPC 模块创建网络,RDS 模块使用它的输出来部署数据库

# main.tf
module "network" {
  source = "./modules/vpc"

  cidr_block = "10.0.0.0/16"
  name       = "my-app-network"
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  azs             = ["us-west-2a", "us-west-2b"]
}

module "database" {
  source = "./modules/rds"

  vpc_id         = module.network.vpc_id
  subnet_ids     = module.network.private_subnet_ids
  instance_class = "db.t3.micro"
}

2. 资源依赖

Terraform 有两种依赖关系:隐式依赖(Implicit Dependence)和`显式依赖``(Explicit Dependence)。隐式依赖是 Terraform 已知的,而显式依赖是未知的。

2.1. 隐式依赖(Implicit Dependence)

隐式依赖简单来讲就是对于变量引用的依赖。当一个资源的创建依赖于另一个资源创建后的信息时,需要用到隐式依赖来让 Terraform 知晓依赖关系。

针对隐式依赖关系,Terraform 通过属性值引用赋值的方式来知晓。

2.2. 显式依赖(Explicit Dependence)

显式依赖就是Terraform无法通过变量之间的引用关系推断出来,需要用户明确指出的依赖。可以使用 depends_on 来显式声明依赖关系。无论资源类型是什么,depends_on 可以在模块内使用,该值可以是指向资源的表达式。

2.3. 平级module的变量引用

Terraform 中:Module 之间不能直接访问对方的内部资源或输出,除非通过根模块中显式传递。

但你可以通过 根模块作为“中介”,将一个 module 的输出传给另一个 module 作为输入。

示例:两个平级模块通信

假设我们有两个模块:

  • vpc:创建 VPC 和子网
  • eks:创建 EKS 集群,需要使用 VPC 创建的子网 ID
.
├── main.tf
├── variables.tf
├── outputs.tf
└── modules/
    ├── vpc/
    │   ├── main.tf
    │   └── outputs.tf
    └── eks/
        ├── main.tf
        └── variables.tf

步骤 1:为 vpc 模块定义输出(modules/vpc/outputs.tf

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

output "vpc_id" {
  value = aws_vpc.main.id
}

步骤 2:为 eks 模块定义输入变量(modules/eks/variables.tf

variable "vpc_id" {
  type = string
}

variable "subnet_ids" {
  type = list(string)
}

步骤 3:在根模块中调用并连接两个模块(main.tf

# 创建 VPC 模块
module "network" {
  source = "./modules/vpc"
  # 输入变量...
}

# 创建 EKS 模块,使用 network 模块的输出作为输入
module "cluster" {
  source = "./modules/eks"

  vpc_id     = module.network.vpc_id
  subnet_ids = module.network.private_subnet_ids
}

这样就实现了:eks 模块使用了平级 vpc 模块的输出值

2.4. 父级模块的变量引用

核心原则:Terraform 模块的引用方向是 单向向下的。

🔹 Terraform 只能引用当前模块直接调用的“子模块”的 output 字段。
🔹 不能向上引用父级、也不能横向引用兄弟模块。
🔹 所有跨模块通信都必须通过“调用者”显式传递(通常是根模块)。

依赖必须由“上层模块”显式传递,通过 根模块中转

module "A" {
  source = "./modules/a"
}

module "B" {
  source = "./modules/b"

  # 正确!根模块把 A 的输出传给 B 的输入
  vpc_id = module.A.vpc_id
}

2.5. 推荐架构设计原则

  1. 扁平化优于深层嵌套

    • 尽量让多个模块由根模块统一调用
    • 减少层级,便于管理依赖
  2. 接口清晰化

    • 每个模块通过 outputs.tf 明确暴露哪些值
    • 通过 variables.tf 明确需要哪些输入
  3. 避免循环依赖

    • A → B → C → A 是非法的
    • 如果出现,说明模块职责不清,需重构
  4. 用 locals 或 variables 统一配置

locals {
  common_tags = { Environment = "prod" }
}

module "vpc" {
  source = "./modules/vpc"
  tags   = local.common_tags
}

module "rds" {
  source = "./modules/rds"
  tags   = local.common_tags
}

参考: