

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 了解 Terraform 模块
<a name="modules"></a>

在基础设施即代码 (IaC) 领域，*模块*是一个独立的代码块，它被隔离并打包在一起以供重复使用。模块的概念是 Terraform 开发中不可避免的方面。有关更多信息，请参阅 Terraform 文档中的[模块](https://developer.hashicorp.com/terraform/language/modules)。 AWS CloudFormation 还支持模块。有关更多信息，请参阅 AWS 云运营和迁移博客中的[AWS CloudFormation 模块简介](https://aws.amazon.com/blogs/mt/introducing-aws-cloudformation-modules/)。

Terraform 和 Terraform 中模块的主要区别在 CloudFormation 于， CloudFormation 模块是通过使用特殊的资源类型 () `AWS::CloudFormation::ModuleVersion` 导入的。在 Terraform 中，每个配置都至少有一个模块，称为[根](https://developer.hashicorp.com/terraform/language/modules#the-root-module)模块。**主.tf** 文件或 Terraform 配置文件中的文件中的 Terraform 资源被视为位于根模块中。然后，根模块可以调用其他模块以包含在堆栈中。[以下示例显示了一个根模块使用开源 eks 模块配置亚马逊 Elastic Kubernetes Service (Amazon EKS) 集群。](https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest)

```
terraform {
  required_providers {
    helm = {
      source  = "hashicorp/helm"
      version = "2.12.1"
    }
  }
  required_version = ">= 1.2.0"
}

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "20.2.1"
  vpc_id  = var.vpc_id
}

provider "helm" {
  kubernetes {
    host                   = module.eks.cluster_endpoint
    cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
  }
}
```

您可能已经注意到，上面的配置文件不包括 AWS 提供程序。这是因为模块是独立的，可以包含自己的提供者。由于 Terraform 提供程序是全球性的，因此可以在根模块中使用子模块中的提供程序。但是，并非所有模块值都是如此。默认情况下，模块中的其他内部值仅限于该模块，需要声明为输出才能在根模块中访问。您可以利用开源模块来简化堆栈中的资源创建。例如，eks 模块所做的不仅仅是配置 EKS 集群，它还提供了一个功能齐全的 Kubernetes 环境。只要eks模块配置符合您的需求，使用它可以省去编写数十行额外代码的麻烦。

## 调用模块
<a name="calling-modules"></a>

[你在 Terraform 部署期间运行的两个主要 Terraform CLI 命令是 terraform init 和 terra [form apply](https://developer.hashicorp.com/terraform/cli/commands/init)。](https://developer.hashicorp.com/terraform/cli/commands/apply)该`terraform init`命令执行的默认步骤之一是找到所有子模块，并将它们作为依赖项导入到`.terraform/modules`目录中。在开发过程中，无论何时添加新的外部源模块，都必须在使用命令之前重新初始化。`apply`当你听到有人提及 Terraform *模块*时，它指的是这个目录中的软件包。严格来说，你在代码中声明的模块就是*调用模块*，所以在实践中，module 关键字调用实际的模块，该模块存储为依赖项。

这样，调用模块可以更简洁地代表部署时要替换的完整模块。你可以利用这个想法，在堆栈中创建自己的模块，使用任何你想要的标准来强制资源的逻辑分离。请记住，这样做的最终目标应该是降低堆栈的复杂性。由于在模块之间共享数据需要您从模块内部输出数据，因此有时过于依赖模块会使事情变得过于复杂。

## 根模块
<a name="the-root-module"></a>

由于每个 Terraform 配置都至少有一个模块，因此检查你最常处理的模块（根模块）的模块属性会有所帮助。每当你处理 Terraform 项目时，根模块都包含顶级目录中的所有`.tf`（或`.tf.json`）文件。当你在该顶级目录`terraform apply`中运行时，Terraform 会尝试运行它在那里找到的所有`.tf`文件。除非在其中一个顶级配置文件中调用子目录中的任何文件，否则这些文件都将被忽略。

这为你的代码结构提供了一定的灵活性。这也是为什么将 Terraform 部署称为模块而不是文件更准确的原因，因为单个进程中可能涉及多个文件。对于最佳实践，Terraform 推荐了一种[标准模块结构](https://developer.hashicorp.com/terraform/language/modules/develop/structure)。但是，如果您要将任何`.tf`文件放在顶层目录中，它将与其余文件一起运行。实际上，模块中的所有顶级`.tf`文件都是在你运行时部署的`terraform apply`。那么 Terraform 首先运行哪个文件呢？ 这个问题的答案非常重要。

在初始化之后和堆栈部署之前，Terraform 会执行一系列步骤。首先，分析现有配置，然后创建[依赖关系图](https://developer.hashicorp.com/terraform/internals/graph)。依赖关系图决定了需要哪些资源以及应按什么顺序分配这些资源。例如，包含在其他资源中引用的属性的资源将在其依赖资源之前进行处理。同样，使用`depends_on`参数显式声明依赖关系的资源将在它们指定的资源之后进行处理。在可能的情况下，Terraform 可以实现并行性并同时处理非依赖资源。在部署之前，你可以使用 [terraform graph 命令查看依赖关系图](https://developer.hashicorp.com/terraform/cli/commands/graph)。

创建依赖关系图后，Terraform 会确定部署期间需要做什么。它将依赖关系图与最新的状态文件进行比较。此过程的结果称为*计划*，它非常像 CloudFormation[变更集](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets-create.html)。你可以使用 [terraform plan 命令查看当前的计划](https://developer.hashicorp.com/terraform/cli/commands/plan)。

作为最佳实践，建议尽可能接近标准模块结构。如果您的配置文件变得太长而无法有效管理，而逻辑分隔可以简化管理，则可以将代码分散到多个文件中。请记住依赖关系图和计划流程的工作原理，以使您的堆栈尽可能高效地运行。