如何构建自定义Terraform模块

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。

介绍

Terraform 模块允许您将基础架构的不同资源组合成一个统一的资源。 您可以稍后通过可能的自定义重用它们,而无需在每次需要它们时重复资源定义,这对于大型和 复杂结构的项目 是有益的。 您可以使用您定义的输入变量自定义模块实例,也可以使用输出从中提取信息。 除了创建自己的自定义模块外,您还可以使用在 Terraform Registry 上公开发布的预制模块。 开发人员可以使用您创建的模块等输入来使用和自定义它们,但它们的源代码存储在云中并从云中提取。

在本教程中,您将创建一个 Terraform 模块,该模块将在负载均衡器后面设置多个 Droplet 以实现冗余。 您还将使用 Hashicorp 配置语言 (HCL) 的 for_eachcount 循环功能同时部署模块的多个自定义实例。

先决条件

注意: 本教程专门用 Terraform 1.1.3 测试过。


模块结构和优点

在本节中,您将了解模块带来的好处、它们通常放置在项目中的什么位置以及应该如何构建它们。

创建自定义 Terraform 模块以封装在较大项目中经常一起使用和部署的连接组件。 它们是自包含的,只捆绑了他们需要的资源、变量和提供者。

模块通常存储在项目根目录的中央文件夹中,每个模块都位于其各自的子文件夹中。 为了保持模块之间的清晰分离,始终将它们构建为具有单一目的,并确保它们从不包含子模块。

当您发现自己重复使用不常见的自定义模块时,从资源方案中创建模块很有用。 将单个资源打包为一个模块可能是多余的,并且会逐渐消除整体架构的简单性。

对于小型开发和测试项目,不需要合并模块,因为在这些情况下它们不会带来太大的改进。 凭借其定制能力,模块是复杂结构项目的构建元素。 开发人员将模块用于大型项目,因为在避免代码重复方面具有显着优势。 模块还提供了一个好处,即定义只需要在一个地方进行修改,然后将通过基础设施的其余部分进行传播。

接下来,您将在 Terraform 项目中定义、使用和自定义模块。

创建模块

在本节中,您将定义多个 Droplet 和一个负载均衡器作为 Terraform 资源并将它们打包到一个模块中。 您还将使用模块输入自定义生成的模块。

您将把模块存储在名为 droplet-lb 的目录中,该目录位于名为 modules 的目录下。 假设您位于作为先决条件的一部分创建的 terraform-modules 目录中,请通过运行以下命令同时创建两个目录:

mkdir -p modules/droplet-lb

-p 参数指示 mkdir 在提供的路径中创建所有目录。

导航到它:

cd modules/droplet-lb

如上一节所述,模块包含它们使用的资源和变量。 从 Terraform 0.13 开始,它们还必须包含它们使用的提供程序的定义。 模块不需要任何特殊配置即可注意代码代表一个模块,因为 Terraform 将包含 HCL 代码的每个目录都视为一个模块,甚至是项目的根目录。

模块中定义的变量作为其输入公开,并且可以在资源定义中用于自定义它们。 您将创建的模块将有两个输入:要创建的 Droplet 的数量及其组的名称。 创建并打开以编辑名为 variables.tf 的文件,您将在其中存储变量:

nano variables.tf

添加以下行:

模块/droplet-lb/variables.tf

variable "droplet_count" {}
variable "group_name" {}

保存并关闭文件。

您将 Droplet 定义存储在名为 droplets.tf 的文件中。 创建并打开它进行编辑:

nano droplets.tf

添加以下行:

模块/droplet-lb/droplets.tf

resource "digitalocean_droplet" "droplets" {
  count  = var.droplet_count
  image  = "ubuntu-20-04-x64"
  name   = "${var.group_name}-${count.index}"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

对于指定要创建多少个资源实例的 count 参数,您传入 droplet_count 变量。 当从主项目代码调用模块时,将指定其值。 每个部署的 Droplet 的名称都不同,您可以通过将当前 Droplet 的索引附加到提供的组名称来实现。 Droplet 的部署将在 fra1 区域中,它们将运行 Ubuntu 20.04。

完成后,保存并关闭文件。

现在定义了 Droplet,您可以继续创建负载均衡器。 您将其资源定义存储在名为 lb.tf 的文件中。 通过运行创建并打开它进行编辑:

nano lb.tf

添加其资源定义:

模块/droplet-lb/lb.tf

resource "digitalocean_loadbalancer" "www-lb" {
  name   = "lb-${var.group_name}"
  region = "fra1"

  forwarding_rule {
    entry_port     = 80
    entry_protocol = "http"

    target_port     = 80
    target_protocol = "http"
  }

  healthcheck {
    port     = 22
    protocol = "tcp"
  }

  droplet_ids = [
    for droplet in digitalocean_droplet.droplets:
      droplet.id
  ]
}

您可以在名称中使用组名称来定义负载均衡器,以使其可区分。 您将它与 Droplets 一起部署在 fra1 区域中。 接下来的两节指定目标和监控端口和协议。

突出显示的 droplet_ids 块包含 Droplet 的 ID,应由负载均衡器管理。 由于有多个 Droplet,并且事先不知道它们的数量,因此您使用 for 循环遍历 Droplet 的集合 (digitalocean_droplet.droplets) 并获取它们的 ID。 用括号 ([]) 将 for 循环括起来,这样生成的集合将是一个列表。

保存并关闭文件。

您现在已经为您的模块定义了 Droplet、负载均衡器和变量。 您需要定义提供者要求,指定模块使用哪些提供者,包括它们的版本和它们所在的位置。 由于 Terraform 0.13,模块必须明确定义它们使用的非 Hashicorp 维护的提供程序的来源; 这是因为它们没有从父项目继承它们。

您将提供者要求存储在一个名为 provider.tf 的文件中。 通过运行创建它以进行编辑:

nano provider.tf

添加以下行以要求 digitalocean 提供程序:

模块/droplet-lb/provider.tf

terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

完成后保存并关闭文件。 droplet-lb 模块现在需要 digitalocean 提供程序。

模块还支持输出,您可以使用它来提取有关其资源状态的内部信息。 您将定义一个公开负载均衡器 IP 地址的输出,并将其存储在名为 outputs.tf 的文件中。 创建它以进行编辑:

nano outputs.tf

添加以下定义:

模块/droplet-lb/outputs.tf

output "lb_ip" {
  value = digitalocean_loadbalancer.www-lb.ip
}

此输出检索负载均衡器的 IP 地址。 保存并关闭文件。

droplet-lb 模块现在功能完整,可以部署了。 您将从主代码中调用它,并将其存储在项目的根目录中。 首先,通过向上浏览文件目录两次导航到它:

cd ../..

然后,创建并打开一个名为 main.tf 的文件进行编辑,您将在其中使用该模块:

nano main.tf

添加以下行:

主文件

module "groups" {
  source = "./modules/droplet-lb"

  droplet_count = 3
  group_name    = "group1"
}

output "loadbalancer-ip" {
  value = module.groups.lb_ip
}

在此声明中,您调用位于指定为 source 的目录中的 droplet-lb 模块。 您可以配置它提供的输入,droplet_countgroup_name,将其设置为 group1,以便您稍后能够辨别实例。

由于负载均衡器 IP 输出是在模块中定义的,因此在应用项目时不会自动显示。 解决方案是创建另一个输出来检索其值 (loadbalancer_ip)。

完成后保存并关闭文件。

通过运行初始化模块:

terraform init

输出将如下所示:

OutputInitializing modules...
- groups in modules/droplet-lb

Initializing the backend...

Initializing provider plugins...
- Finding digitalocean/digitalocean versions matching "~> 2.0"...
- Installing digitalocean/digitalocean v2.19.0...
- Installed digitalocean/digitalocean v2.19.0 (signed by a HashiCorp partner, key ID F82037E524B9C0E8)

...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

您可以尝试规划项目,以查看 Terraform 将通过运行执行哪些操作:

terraform plan -var "do_token=${DO_PAT}"

输出将与此类似:

Output...
Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.groups.digitalocean_droplet.droplets[0] will be created
  + resource "digitalocean_droplet" "droplets" {
...
      + name                 = "group1-0"
...
    }

  # module.groups.digitalocean_droplet.droplets[1] will be created
  + resource "digitalocean_droplet" "droplets" {
...
      + name                 = "group1-1"
...
    }

  # module.groups.digitalocean_droplet.droplets[2] will be created
  + resource "digitalocean_droplet" "droplets" {
...
      + name                 = "group1-2"
...
    }

  # module.groups.digitalocean_loadbalancer.www-lb will be created
  + resource "digitalocean_loadbalancer" "www-lb" {
...
      + name                     = "lb-group1"
...
    }

Plan: 4 to add, 0 to change, 0 to destroy.
...

此输出详细说明 Terraform 将创建三个 Droplet,名为 group1-0group1-1group1-2,还将创建一个名为 group1-lb 的负载均衡器,它将管理进出三个 Droplet 的流量。

您可以尝试通过运行将项目应用到云端:

terraform apply -var "do_token=${DO_PAT}"

出现提示时输入 yes。 输出将显示所有操作,负载均衡器的 IP 地址也将显示:

Outputmodule.groups.digitalocean_droplet.droplets[1]: Creating...
module.groups.digitalocean_droplet.droplets[0]: Creating...
module.groups.digitalocean_droplet.droplets[2]: Creating...
...
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

loadbalancer-ip = ip_address

您已经创建了一个模块,其中包含可自定义数量的 Droplet 和一个负载均衡器,该模块将自动配置为管理其传入和传出流量。

重命名部署的资源

在上一节中,您部署了您定义的模块并将其命名为 groups。 如果您希望更改其名称,仅重命名模块调用不会产生预期的结果。 重命名调用会提示 Terraform 销毁并重新创建资源,导致停机时间过长。

例如,通过运行打开 main.tf 进行编辑:

nano main.tf

groups 模块重命名为 groups_renamed,如下所示:

主文件

module "groups_renamed" {
  source = "./modules/droplet-lb"

  droplet_count = 3
  group_name    = "group1"
}

output "loadbalancer-ip" {
  value = module.groups_renamed.lb_ip
}

保存并关闭文件。 然后,再次初始化项目:

terraform init

您现在可以计划项目:

terraform plan -var "do_token=${DO_PAT}"

输出会很长,但看起来类似于:

Output...
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
  - destroy

Terraform will perform the following actions:
     # module.groups.digitalocean_droplet.droplets[0] will be destroyed
     ...
     # module.groups_renamed.digitalocean_droplet.droplets[0] will be created
     ...

Terraform 将提示您销毁现有实例并创建新实例。 这是破坏性的和不必要的,并且可能导致不必要的停机时间。

相反,使用 moved 块,您可以指示 Terraform 以新名称移动旧资源。 打开 main.tf 进行编辑,并在文件末尾添加以下行:

moved {
  from = module.groups
  to   = module.groups_renamed
}

完成后,保存并关闭文件。

您现在可以计划项目:

terraform plan -var "do_token=${DO_PAT}"

当您计划使用 main.tf 中存在的 moved 块时,Terraform 想要 移动 资源,而不是重新创建它们:

OutputTerraform will perform the following actions:

     # module.groups.digitalocean_droplet.droplets[0] has moved to module.groups_renamed.digitalocean_droplet.droplets[0]
     ...
     # module.groups.digitalocean_droplet.droplets[1] has moved to module.groups_renamed.digitalocean_droplet.droplets[1]
     ...

移动资源会改变它们在 Terraform 状态中的位置,这意味着不会修改、销毁或重新创建实际的云资源。

因为您将在下一步中显着修改配置,所以通过运行以下命令销毁已部署的资源:

terraform destroy -var "do_token=${DO_PAT}"

出现提示时输入 yes。 输出将结束于:

Output...
Destroy complete! Resources: 4 destroyed.

在本节中,您重命名了 Terraform 项目中的资源,而不会在此过程中破坏它们。 您现在将使用 for_eachcount 从同一代码部署模块的多个实例。

部署多个模块实例

在本节中,您将使用 countfor_each 多次自定义部署 droplet-lb 模块。

使用 count

一次部署同一模块的多个实例的一种方法是将多少个实例传递给 count 参数,该参数自动可用于每个模块。 打开main.tf进行编辑:

nano main.tf

将其修改为如下所示,删除现有的输出定义和 moved 块:

主文件

module "groups" {
  source = "./modules/droplet-lb"

  count  = 3

  droplet_count = 3
  group_name    = "group1-${count.index}"
}

通过将 count 设置为 3,您可以指示 Terraform 部署模块三次,每次使用不同的组名。 完成后,保存并关闭文件。

通过运行计划部署:

terraform plan -var "do_token=${DO_PAT}"

输出会很长,看起来像这样:

Output...
Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.groups[0].digitalocean_droplet.droplets[0] will be created
...
  # module.groups[0].digitalocean_droplet.droplets[1] will be created
...
  # module.groups[0].digitalocean_droplet.droplets[2] will be created
...
  # module.groups[0].digitalocean_loadbalancer.www-lb will be created
...
  # module.groups[1].digitalocean_droplet.droplets[0] will be created
...
  # module.groups[1].digitalocean_droplet.droplets[1] will be created
...
  # module.groups[1].digitalocean_droplet.droplets[2] will be created
...
  # module.groups[1].digitalocean_loadbalancer.www-lb will be created
...
  # module.groups[2].digitalocean_droplet.droplets[0] will be created
...
  # module.groups[2].digitalocean_droplet.droplets[1] will be created
...
  # module.groups[2].digitalocean_droplet.droplets[2] will be created
...
  # module.groups[2].digitalocean_loadbalancer.www-lb will be created
...

Plan: 12 to add, 0 to change, 0 to destroy.
...

Terraform 在输出中详细说明了三个模块实例中的每一个都将具有三个 Droplet 和一个与之关联的负载均衡器。

使用 for_each

当您需要更复杂的实例定制时,或者当实例的数量取决于编写代码时未知的第三方数据(通常显示为地图)时,您可以将 for_each 用于模块。

您现在将定义一个映射,将组名称与 Droplet 计数配对,并根据它部署 droplet-lb 的实例。 通过运行打开 main.tf 进行编辑:

nano main.tf

修改文件,使其看起来像这样:

主文件

variable "group_counts" {
  type    = map
  default = {
    "group1" = 1
    "group2" = 3
  }
}

module "groups" {
  source   = "./modules/droplet-lb"
  for_each = var.group_counts

  droplet_count = each.value
  group_name    = each.key
}

您首先定义一个名为 group_counts 的映射,其中包含给定组应具有的液滴数。 然后,您调用模块 droplet-lb,但指定 for_each 循环应在 var.group_counts 上运行,即您之前定义的映射。 droplet_count 采用 each.value,即当前对的值,即当前组的液滴数。 group_name 接收组的名称。

完成后保存并关闭文件。

尝试通过运行应用配置:

terraform plan -var "do_token=${DO_PAT}"

输出将详细说明 Terraform 将采取哪些操作来创建两个组及其 Droplet 和负载均衡器:

Output...
Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.groups["group1"].digitalocean_droplet.droplets[0] will be created
...
  # module.groups["group1"].digitalocean_loadbalancer.www-lb will be created
...
  # module.groups["group2"].digitalocean_droplet.droplets[0] will be created
...
  # module.groups["group2"].digitalocean_droplet.droplets[1] will be created
...
  # module.groups["group2"].digitalocean_droplet.droplets[2] will be created
...
  # module.groups["group2"].digitalocean_loadbalancer.www-lb will be created
...

在此步骤中,您已使用 countfor_each 从同一代码部署同一模块的多个自定义实例。

结论

在本教程中,您创建并部署了 Terraform 模块。 您使用模块将逻辑链接的资源组合在一起并对其进行自定义,以便从中央代码定义部署多个不同的实例。 您还使用输出来显示模块中包含的资源的属性。

如果您想了解有关 Terraform 的更多信息,请查看我们的 如何使用 Terraform 管理基础架构系列。