如何构建自定义 Terraform 模块

_ 作者选择了 免费和开源基金作为 写给捐款计划的一部分接受捐款。

介绍

[Terraform] (https://www.terraform.io/) 模块允许您将您基础设施的不同资源组合成单一的统一资源. 以后可以用可能的定制重新使用它们,而无需每次需要时重复资源定义,这对大型和[结构复杂]项目(https://andsky.com/tech/tutorials/how-to-structure-a-terraform-project)有利. 您可以使用您定义的输入变量自定义模块实例, 并使用输出从中提取信息 。 除了创建自定义模块外,您还可以使用在Terraform Register公开发布的预制模块. 开发者可以像您创建的模块一样使用和自定义它们,但他们的源代码被存储在云中并被从云中拉出.

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

前提条件

<$>[注] 注: 本教程已被特定的测试与Terraform 1.1.3

模块结构和优势

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

定制的Terraform模块被创建以封装连接的组件,这些组件在较大的项目中经常被使用并部署在一起,它们是独立的,只结合所需的资源,变量和供应商。

模块通常存储在项目根部的一个中央文件夹中,每个文件夹在其下方的各自子文件夹中。

当您发现自己以罕见的自定义来重复它们时,从资源方案中创建模块是有用的。

对于小型开发和测试项目来说,纳入模块并不必要,因为这些模块不会给这些项目带来很大的改善。 模块具有定制能力,是结构复杂项目的建设要素。 由于避免了代码重复的重大优势,开发者为更大的项目使用模块. 模块还提供的好处是,定义只需在一个地方修改,然后通过其他基础设施加以推广.

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

创建一个模块

在本节中,您将将多个 Droplets 和一个负载平衡器定义为 Terraform 资源,并将其包装成一个模块。

您将该模块存储在名为dropplet-lb的目录中,名为modules的目录中。假设您位于您作为前提条件的一部分创建的terraform-modules目录中,请同时运行:

1mkdir -p modules/droplet-lb

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

导航它:

1cd modules/droplet-lb

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

在模块中定义的变量被暴露为其输入,并可用于资源定义来定制它们。您创建的模块将有两个输入:要创建的Dropplets的数量和组的名称。

1nano variables.tf

添加以下几行:

1[label modules/droplet-lb/variables.tf]
2variable "droplet_count" {}
3variable "group_name" {}

保存并关闭文件。

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

1nano droplets.tf

添加以下几行:

1[label modules/droplet-lb/droplets.tf]
2resource "digitalocean_droplet" "droplets" {
3  count  = var.droplet_count
4  image  = "ubuntu-20-04-x64"
5  name   = "${var.group_name}-${count.index}"
6  region = "fra1"
7  size   = "s-1vcpu-1gb"
8}

对于参数,该参数指定了要创建多少个资源的实例,您将输入dropplet_count变量,其值将在从主要项目代码中调用模块时被指定。 每个部署的Dropplets的名称将是不同的,您通过将当前Dropplets的索引附加到提供的组名称。

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

现在定义了 Droplets,您可以继续创建 Load Balancer. 您将其资源定义存储在名为 lb.tf 的文件中。

1nano lb.tf

添加其资源定义:

 1[label modules/droplet-lb/lb.tf]
 2resource "digitalocean_loadbalancer" "www-lb" {
 3  name   = "lb-${var.group_name}"
 4  region = "fra1"
 5
 6  forwarding_rule {
 7    entry_port     = 80
 8    entry_protocol = "http"
 9
10    target_port     = 80
11    target_protocol = "http"
12  }
13
14  healthcheck {
15    port     = 22
16    protocol = "tcp"
17  }
18
19  droplet_ids = [
20    for droplet in digitalocean_droplet.droplets:
21      droplet.id
22  ]
23}

您将 Load Balancer 定义为其名称中的组名称,以使其可区分。 您将其部署到fra1区域,并与 Droplets 一起使用。

突出的)并采取他们的ID。

保存并关闭文件。

您现在已经为您的模块定义了Droplet、负载平衡器和变量,您需要定义提供商的要求,指定哪些提供商使用该模块,包括它们的版本和位置。

您将存储提供商的要求在一个名为provider.tf的文件中。

1nano provider.tf

添加以下行以要求digitalocean提供商:

1[label modules/droplet-lb/provider.tf]
2terraform {
3  required_providers {
4    digitalocean = {
5      source = "digitalocean/digitalocean"
6      version = "~> 2.0"
7    }
8  }
9}

保存并关闭文件完成后. 该dropplet-lb模块现在需要digitalocean提供商。

模块还支持输出,您可以使用这些输出来提取有关其资源状态的内部信息。您将定义一个输出,该输出暴露了负载平衡器的IP地址,并将其存储在名为outputs.tf的文件中。

1nano outputs.tf

添加以下定义:

1[label modules/droplet-lb/outputs.tf]
2output "lb_ip" {
3  value = digitalocean_loadbalancer.www-lb.ip
4}

此输出检索 Load Balancer 的 IP 地址. 保存并关闭文件。

dropplet-lb模块现在功能齐全,并准备好部署,您将从主要代码中调用,将其存储在项目的根中。

1cd ../..

然后,创建并打开以编辑名为main.tf的文件,您将使用该模块:

1nano main.tf

添加以下几行:

 1[label main.tf]
 2module "groups" {
 3  source = "./modules/droplet-lb"
 4
 5  droplet_count = 3
 6  group_name    = "group1"
 7}
 8
 9output "loadbalancer-ip" {
10  value = module.groups.lb_ip
11}

在此声明中,您呼吁位于指定为的目录中的droplet-lb模块. 您配置其提供的输入,droplet_countgroup_name,设置为group1,以便您以后能够区分实例。

由于 Load Balancer IP 输出在一个模块中定义,因此在应用项目时不会自动显示。

保存并关闭文件,当你完成。

通过运行该模块启动:

1terraform init

结果将是这样的:

 1[secondary_label Output]
 2Initializing modules...
 3- groups in modules/droplet-lb
 4
 5Initializing the backend...
 6
 7Initializing provider plugins...
 8- Finding digitalocean/digitalocean versions matching "~> 2.0"...
 9- Installing digitalocean/digitalocean v2.19.0...
10- Installed digitalocean/digitalocean v2.19.0 (signed by a HashiCorp partner, key ID F82037E524B9C0E8)
11
12...
13
14Terraform has been successfully initialized!
15
16You may now begin working with Terraform. Try running "terraform plan" to see
17any changes that are required for your infrastructure. All Terraform commands
18should now work.
19
20If you ever set or change modules or backend configuration for Terraform,
21rerun this command to reinitialize your working directory. If you forget, other
22commands will detect it and remind you to do so if necessary.

您可以尝试规划项目,看看 Terraform 运行时会采取哪些行动:

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

结果将类似于此:

 1[secondary_label Output]
 2...
 3Terraform used the selected providers to generate the following execution plan. Resource actions are
 4indicated with the following symbols:
 5  + create
 6
 7Terraform will perform the following actions:
 8
 9  # module.groups.digitalocean_droplet.droplets[0] will be created
10  + resource "digitalocean_droplet" "droplets" {
11...
12      + name                 = "group1-0"
13...
14    }
15
16  # module.groups.digitalocean_droplet.droplets[1] will be created
17  + resource "digitalocean_droplet" "droplets" {
18...
19      + name                 = "group1-1"
20...
21    }
22
23  # module.groups.digitalocean_droplet.droplets[2] will be created
24  + resource "digitalocean_droplet" "droplets" {
25...
26      + name                 = "group1-2"
27...
28    }
29
30  # module.groups.digitalocean_loadbalancer.www-lb will be created
31  + resource "digitalocean_loadbalancer" "www-lb" {
32...
33      + name                     = "lb-group1"
34...
35    }
36
37Plan: 4 to add, 0 to change, 0 to destroy.
38...

该输出详细说明了Terraform将创建三个Dropplets,名为group1-0,group1-1group1-2,并将创建一个名为group1-lb的负载平衡器,该系统将管理三个Dropplets的流量。

您可以尝试将项目应用到云中,运行:

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

提示时输入,输出将显示所有操作,并显示负载平衡器的 IP 地址:

 1[secondary_label Output]
 2module.groups.digitalocean_droplet.droplets[1]: Creating...
 3module.groups.digitalocean_droplet.droplets[0]: Creating...
 4module.groups.digitalocean_droplet.droplets[2]: Creating...
 5...
 6Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
 7
 8Outputs:
 9
10loadbalancer-ip = ip_address

您已经创建了一个包含可自定义数量的 Droplets 和负载平衡器的模块,该模块将自动配置以管理其进出和流量。

重命名部署的资源

在上一节中,您部署了您定义的模块,并将其命名为群组。如果您想更改其名称,只需重新命名模块呼叫将不会产生预期的结果。

例如,打开main.tf 以执行编辑:

1nano main.tf

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

 1[label main.tf]
 2module "groups_renamed" {
 3  source = "./modules/droplet-lb"
 4
 5  droplet_count = 3
 6  group_name    = "group1"
 7}
 8
 9output "loadbalancer-ip" {
10  value = module.groups_renamed.lb_ip
11}

保存并关闭文件,然后重新启动项目:

1terraform init

现在你可以计划这个项目:

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

输出将是漫长的,但将看起来像这样:

 1[secondary_label Output]
 2...
 3Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
 4  + create
 5  - destroy
 6
 7Terraform will perform the following actions:
 8     # module.groups.digitalocean_droplet.droplets[0] will be destroyed
 9     ...
10     # module.groups_renamed.digitalocean_droplet.droplets[0] will be created
11     ...

Terraform 会促使您破坏现有实例并创建新的实例,这具有破坏性和不必要性,可能导致不必要的停机时间。

相反,使用移动块,您可以指示Terraform以新的名称移动旧资源。

1moved {
2  from = module.groups
3  to   = module.groups_renamed
4}

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

现在你可以计划这个项目:

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

当您使用 main.tf 中存在的移动块来规划时,Terraform 希望 move 资源,而不是重建它们:

1[secondary_label Output]
2Terraform will perform the following actions:
3
4     # module.groups.digitalocean_droplet.droplets[0] has moved to module.groups_renamed.digitalocean_droplet.droplets[0]
5     ...
6     # module.groups.digitalocean_droplet.droplets[1] has moved to module.groups_renamed.digitalocean_droplet.droplets[1]
7     ...

移动资源在 Terraform 状态中会改变其位置,这意味着实际的云资源不会被修改、破坏或重建。

因为在下一步,您将大大修改配置,通过运行来摧毁部署的资源:

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

当提示时输入时,输出将结束为:

1[secondary_label Output]
2...
3Destroy complete! Resources: 4 destroyed.

在本节中,您将重命名 Terraform 项目中的资源,而不会在进程中摧毁它们. 您现在将使用 for_eachcount 部署来自同一个代码的模块的多个实例。

部署多个模块实例

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

使用计数

同时部署相同模块的多个实例的一种方法是将其数量传递到计数参数中,该参数自动可用于每个模块。

1nano main.tf

更改它看起来像这样,删除现有的输出定义和移动块:

1[label main.tf]
2module "groups" {
3  source = "./modules/droplet-lb"
4
5  count  = 3
6
7  droplet_count = 3
8  group_name    = "group1-${count.index}"
9}

通过将设置为3,您指示Terraform将模块部署三次,每个模块都有不同的组名称。

通过运行计划部署:

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

输出将是漫长的,并将看起来像这样的:

 1[secondary_label Output]
 2...
 3Terraform used the selected providers to generate the following execution plan. Resource actions are
 4indicated with the following symbols:
 5  + create
 6
 7Terraform will perform the following actions:
 8
 9  # module.groups[0].digitalocean_droplet.droplets[0] will be created
10...
11  # module.groups[0].digitalocean_droplet.droplets[1] will be created
12...
13  # module.groups[0].digitalocean_droplet.droplets[2] will be created
14...
15  # module.groups[0].digitalocean_loadbalancer.www-lb will be created
16...
17  # module.groups[1].digitalocean_droplet.droplets[0] will be created
18...
19  # module.groups[1].digitalocean_droplet.droplets[1] will be created
20...
21  # module.groups[1].digitalocean_droplet.droplets[2] will be created
22...
23  # module.groups[1].digitalocean_loadbalancer.www-lb will be created
24...
25  # module.groups[2].digitalocean_droplet.droplets[0] will be created
26...
27  # module.groups[2].digitalocean_droplet.droplets[1] will be created
28...
29  # module.groups[2].digitalocean_droplet.droplets[2] will be created
30...
31  # module.groups[2].digitalocean_loadbalancer.www-lb will be created
32...
33
34Plan: 12 to add, 0 to change, 0 to destroy.
35...

在输出中, Terraform 细节表明,三个模块实例中的每一个都将有三个 Droplets 和一个负载平衡器。

使用「for_each」

您可以在需要更复杂的实例定制时使用)。

现在,您将定义一个将群组名称对比到Droplet计数的地图,并根据此部署droplet-lb的实例。

1nano main.tf

更改文件以使它看起来像这样:

 1[label main.tf]
 2variable "group_counts" {
 3  type    = map
 4  default = {
 5    "group1" = 1
 6    "group2" = 3
 7  }
 8}
 9
10module "groups" {
11  source   = "./modules/droplet-lb"
12  for_each = var.group_counts
13
14  droplet_count = each.value
15  group_name    = each.key
16}

您首先定义一个名为group_counts的地图,该地图包含给定组应该有多少Dropplets。然后,您调用droplet-lb模块,但指定for_each循环应在var.group_counts上运作,您刚刚定义的地图。

保存并关闭文件,当你完成。

尝试通过运行应用配置:

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

输出将详细介绍Terraform将采取的行动,以创建两组他们的Droplets和负载平衡器:

 1[secondary_label Output]
 2...
 3Terraform used the selected providers to generate the following execution plan. Resource actions are
 4indicated with the following symbols:
 5  + create
 6
 7Terraform will perform the following actions:
 8
 9  # module.groups["group1"].digitalocean_droplet.droplets[0] will be created
10...
11  # module.groups["group1"].digitalocean_loadbalancer.www-lb will be created
12...
13  # module.groups["group2"].digitalocean_droplet.droplets[0] will be created
14...
15  # module.groups["group2"].digitalocean_droplet.droplets[1] will be created
16...
17  # module.groups["group2"].digitalocean_droplet.droplets[2] will be created
18...
19  # module.groups["group2"].digitalocean_loadbalancer.www-lb will be created
20...

在此步骤中,您使用for_each部署了来自同一个代码的相同模块的多个自定义实例。

结论

在本教程中,您创建并部署了Terraform模块,您使用模块将逻辑上链接的资源组合在一起,并定制它们,以便从中央代码定义中部署多个不同的实例。

如果您想了解更多关于Terraform的信息,请参阅我们(https://www.digitalocean.com/community/tutorial_series/how-to-manage-infrastructure-with-terraform)系列。

Published At
Categories with 技术
comments powered by Disqus