如何利用 Terraform 模块和模板创建可重用的基础设施

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

介绍

基础设施作为代码(IAC)的主要好处之一是重新使用所定义的基础设施的一部分。 在Terraform中,您可以使用模块将逻辑连接的组件封装到一个实体中,并使用您定义的输入变量自定义. 通过使用模块在高水平上定义您的基础设施,您可以将开发,中转和生产环境分开,只能以不同的值传递到相同的模块,这样可以最大限度地减少代码重复,并最大限度地做到简洁.

您不限于只使用您的自定义模块 。 [Terraform Registration] (https://registry.terraform.io/) 已纳入Terraform,并列出模块和提供者,通过在`必需的提供方 ' 一节中界定这些模块和提供者,您可立即将其纳入您的项目。 引用公共模块可以加快您的工作流程并减少代码重复. 如果你有一个有用的模块,并且想与世界分享,你可以考虑在登记册上发表,供其他开发者使用.

在本教程中,您将探索在Terraform项目中定义和重复使用代码的一些方法,您将参考Terraform注册表中的模块,使用模块分离开发和生产环境,了解模板及其使用方式,并使用depends_on元论据明确指定资源依赖。

前提条件

  • DigitalOcean个人访问托肯,您可以通过DigitalOcean控制面板创建. 您可以在 DigitalOcean 产品文档中找到指令,如何创建个人访问Token.
  • 安装在您的本地机器上,并与数字Ocean供应商建立的项目。 完成[如何使用有数字海洋的地平面(https://andsky.com/tech/tutorials/how-to-use-terraform-with-digitalocean)的 步骤1** 和** 步骤2** ,并确保为项目文件夹取名terraform-reusable',而不是负载平衡'。 在** 步骤2** 中,不包括pvt_key变量和SSH密钥资源。
  • 联合国 在Serraform-reusable'中的模块'下提供的 " droplet-lb " 模块。 遵循如何构建自定义模块的教程,并通过它工作,直到"滴放-lb"模块功能完成. (即,直到 创建模块** 部分的 " cd./. " 命令为止。 )
  • 了解Terraform项目的结构方法。 更多信息见我们的教程如何构建一个地表工程
  • (可选) 两个独立的域名服务员在您的登记员面前被指向数字海洋。 您的域尚未添加到您的数字海洋账户中 。 参考如何从共同域注册员中指向数字海洋命名员设置此功能的教程. 请注意,如果不计划部署您将通过这个教程创建的项目,您就不需要这么做. .

<$>[注] 注: 本教程已使用Terraform 1.0.2进行测试。

二、分离发展和生产环境

在这一节中,您将使用模块来区分您的目标部署环境. 你将按一个比较[复杂工程]的结构来安排这些(https://andsky.com/tech/tutorials/how-to-structure-a-terraform-project# understanding-a-terraform-project%E2%80%99s-structure). 您将创建一个包含两个模块的项目:一个将定义 Droplets 和 Load Balancers,另一个将设置 DNS 域名记录. 之后,你会为两个不同的环境('dev'和'prod')编写配置,这两个环境将称为相同的模块.

创建dns-records模块

作为先决条件的一部分,您在terraform-reusability下设置了初始项目,并在modules下创建了droplet-lb模块,从terraform-reusability目录中创建第二个模块,称为dns-records,包含变量、输出和资源定义。

1mkdir modules/dns-records

导航它:

1cd modules/dns-records

此模块将包含您的域和 DNS 记录的定义,您将后来指向负载平衡器。您将首先定义变量,这些变量将成为此模块将暴露的输入。

1nano variables.tf

添加以下变量定义:

1[label terraform-reusability/modules/dns-records/variables.tf]
2variable "domain_name" {}
3variable "ipv4_address" {}

现在,您将定义域和附带的ACNAME记录在名为records.tf的文件中。

1nano records.tf

添加以下资源定义:

 1[label terraform-reusability/modules/dns-records/records.tf]
 2resource "digitalocean_domain" "domain" {
 3  name = var.domain_name
 4}
 5
 6resource "digitalocean_record" "domain_A" {
 7  domain = digitalocean_domain.domain.name
 8  type   = "A"
 9  name   = "@"
10  value  = var.ipv4_address
11}
12
13resource "digitalocean_record" "domain_CNAME" {
14  domain = digitalocean_domain.domain.name
15  type   = "CNAME"
16  name   = "www"
17  value  = "@"
18}

首先,您将域名添加到您的 DigitalOcean 帐户中,云将自动将三个 DigitalOcean 名称服务器添加为NS记录,您向 Terraform 提供的域名可能已经不在您的 DigitalOcean 帐户中,否则在创建基础设施时, Terraform 会显示错误。

然后,您为您的域名定义一个)到提供的IP地址为变量ipv4_address。当您初始化模块的实例时,实际的IP地址将被传输。

接下来,您将为此模块定义输出。输出将显示所创建的记录的FQDN(完全合格的域名)。

1nano outputs.tf

添加以下几行:

1[label terraform-reusability/modules/dns-records/outputs.tf]
2output "A_fqdn" {
3  value = digitalocean_record.domain_A.fqdn
4}
5
6output "CNAME_fqdn" {
7  value = digitalocean_record.domain_CNAME.fqdn
8}

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

随着变量,DNS记录和输出被定义,您需要指定的最后一件事是该模块的供应商要求. 您将指定dns-records模块需要digitalocean供应商在名为provider.tf的文件中。

1nano provider.tf

添加以下几行:

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

完成后,保存并关闭文件. 现在已定义了digitalocean提供商,dns-records模块在功能上已经完成。

创造不同的环境

目前,地形再利用性项目的结构将类似于以下:

 1terraform-reusability/
 2├─ modules/
 3  ├─ dns-records/
 4    ├─ outputs.tf
 5    ├─ provider.tf
 6    ├─ records.tf
 7    ├─ variables.tf
 8  ├─ droplet-lb/
 9    ├─ droplets.tf
10    ├─ lb.tf
11    ├─ outputs.tf
12    ├─ provider.tf
13    ├─ variables.tf
14├─ provider.tf

到目前为止,您在项目中有两个模块:您刚刚创建的模块(‘dns-records’)和您创建的模块(‘droplet-lb’)。

为了方便不同的环境,你会将devprod环境配置文件存储在一个名为environments的目录中,该目录将位于项目的根部。 两个环境将呼叫相同的两个模块,但具有不同的参数值。

首先,通过运行导航到项目的根:

1cd ../..

然后,在环境下同时创建devprod目录:

1mkdir -p environments/dev && mkdir environments/prod

-p参数命令mkdir在给定路径中创建所有目录。

导航到dev目录,因为您首先将配置该环境:

1cd environments/dev

您将将代码存储在名为main.tf 的文件中,因此创建用于编辑:

1nano main.tf

添加以下几行:

 1[label terraform-reusability/environments/dev/main.tf]
 2module "droplets" {
 3  source   = "../../modules/droplet-lb"
 4
 5  droplet_count = 2
 6  group_name    = "dev"
 7}
 8
 9module "dns" {
10  source   = "../../modules/dns-records"
11
12  domain_name   = "your_dev_domain"
13  ipv4_address  = module.droplets.lb_ip
14}

在这里,您将调用和配置两个模块,dropplet-lbdns-records,这将导致两个Dropplets的创建。它们由负载平衡器组成,并为所提供的域的DNS记录设置为指向该负载平衡器。 请记住,用您想要的dev环境的域名替换your_dev_domain,然后保存并关闭文件。

接下来,您将配置 DigitalOcean 提供商并创建一个变量,以便它能够接受您创建的个人访问代币作为前提的一部分。

1nano provider.tf

添加以下几行:

 1[label terraform-reusability/environments/dev/provider.tf]
 2terraform {
 3  required_providers {
 4    digitalocean = {
 5      source = "digitalocean/digitalocean"
 6      version = "~> 2.0"
 7    }
 8  }
 9}
10
11variable "do_token" {}
12
13provider "digitalocean" {
14  token = var.do_token
15}

在此代码中,您需要digitalocean提供商可用并将do_token变量传输到其实例中。

通过运行启动配置:

1terraform init

您将收到以下输出:

 1[secondary_label Output]
 2Initializing modules...
 3- dns in ../../modules/dns-records
 4- droplets in ../../modules/droplet-lb
 5
 6Initializing the backend...
 7
 8Initializing provider plugins...
 9- Finding digitalocean/digitalocean versions matching "~> 2.0"...
10- Installing digitalocean/digitalocean v2.10.1...
11- Installed digitalocean/digitalocean v2.10.1 (signed by a HashiCorp partner, key ID F82037E524B9C0E8)
12
13Partner and community providers are signed by their developers.
14If you'd like to know more about provider signing, you can read about it here:
15https://www.terraform.io/docs/cli/plugins/signing.html
16
17Terraform has created a lock file .terraform.lock.hcl to record the provider
18selections it made above. Include this file in your version control repository
19so that Terraform can guarantee to make the same selections by default when
20you run "terraform init" in the future.
21
22Terraform has been successfully initialized!
23
24You may now begin working with Terraform. Try running "terraform plan" to see
25any changes that are required for your infrastructure. All Terraform commands
26should now work.
27
28If you ever set or change modules or backend configuration for Terraform,
29rerun this command to reinitialize your working directory. If you forget, other
30commands will detect it and remind you to do so if necessary.

prod环境的配置类似,通过运行导航到其目录:

1cd ../prod

创建并打开main.tf 来编辑:

1nano main.tf

添加以下几行:

 1[label terraform-reusability/environments/prod/main.tf]
 2module "droplets" {
 3  source   = "../../modules/droplet-lb"
 4
 5  droplet_count = 5
 6  group_name    = "prod"
 7}
 8
 9module "dns" {
10  source   = "../../modules/dns-records"
11
12  domain_name   = "your_prod_domain"
13  ipv4_address  = module.droplets.lb_ip
14}

这和你的dev代码的区别在于,将部署5个Dropplets。此外,域名,你应该用你的prod域名取代,将是不同的。

然后,从dev复制提供商配置:

1cp ../dev/provider.tf .

还可以初始化此配置:

1terraform init

此命令的输出将与您上次运行该命令相同。

您可以尝试规划配置,看看 Terraform 将通过运行创建哪些资源:

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

prod的输出将如下:

 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.dns.digitalocean_domain.domain will be created
10  + resource "digitalocean_domain" "domain" {
11      + id   = (known after apply)
12      + name = "your_prod_domain"
13      + urn  = (known after apply)
14    }
15
16  # module.dns.digitalocean_record.domain_A will be created
17  + resource "digitalocean_record" "domain_A" {
18      + domain = "your_prod_domain"
19      + fqdn   = (known after apply)
20      + id     = (known after apply)
21      + name   = "@"
22      + ttl    = (known after apply)
23      + type   = "A"
24      + value  = (known after apply)
25    }
26
27  # module.dns.digitalocean_record.domain_CNAME will be created
28  + resource "digitalocean_record" "domain_CNAME" {
29      + domain = "your_prod_domain"
30      + fqdn   = (known after apply)
31      + id     = (known after apply)
32      + name   = "www"
33      + ttl    = (known after apply)
34      + type   = "CNAME"
35      + value  = "@"
36    }
37
38  # module.droplets.digitalocean_droplet.droplets[0] will be created
39  + resource "digitalocean_droplet" "droplets" {
40...
41      + name                 = "prod-0"
42...
43    }
44
45  # module.droplets.digitalocean_droplet.droplets[1] will be created
46  + resource "digitalocean_droplet" "droplets" {
47...
48      + name                 = "prod-1"
49...
50    }
51
52  # module.droplets.digitalocean_droplet.droplets[2] will be created
53  + resource "digitalocean_droplet" "droplets" {
54...
55      + name                 = "prod-2"
56...
57    }
58
59  # module.droplets.digitalocean_droplet.droplets[3] will be created
60  + resource "digitalocean_droplet" "droplets" {
61...
62      + name                 = "prod-3"
63...
64    }
65
66  # module.droplets.digitalocean_droplet.droplets[4] will be created
67  + resource "digitalocean_droplet" "droplets" {
68...
69      + name                 = "prod-4"
70...
71    }
72
73  # module.droplets.digitalocean_loadbalancer.www-lb will be created
74  + resource "digitalocean_loadbalancer" "www-lb" {
75...
76      + name                     = "lb-prod"
77...
78
79Plan: 9 to add, 0 to change, 0 to destroy.
80...

这将部署五个Dropplets与负载平衡器。它还将创建您指定的prod域,其中两个DNS记录指向负载平衡器。

注意:**您可以使用以下命令将此配置应用于devprod环境:

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

要摧毁它,运行以下命令并在提示时输入:

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

美元

以下展示了您如何构建这个项目:

 1terraform-reusability/
 2├─ environments/
 3  ├─ dev/
 4    ├─ main.tf
 5    ├─ provider.tf
 6  ├─ prod/
 7    ├─ main.tf
 8    ├─ provider.tf
 9├─ modules/
10  ├─ dns-records/
11    ├─ outputs.tf
12    ├─ provider.tf
13    ├─ records.tf
14    ├─ variables.tf
15  ├─ droplet-lb/
16    ├─ droplets.tf
17    ├─ lb.tf
18    ├─ outputs.tf
19    ├─ provider.tf
20    ├─ variables.tf
21├─ provider.tf

添加是环境目录,其中包含devprod环境的代码。

这种方法的好处在于,模块的进一步更改会自动传播到您的项目的所有领域,而不允许对模块输入进行任何可能的自定义,这种方法不会重复,而且能够尽可能多地促进重复使用,即使在部署环境中。

在本教程的最后两个部分中,您将审查depends_on元参数和templatefile函数。

声明依赖以建立基础设施顺序

在策划操作时,Terraform 会自动尝试识别现有依赖性,并将其构建到其依赖性图表中,它可以检测到的主要依赖性是清晰的参考;例如,当一个模块的输出值传递到另一个资源上的参数时,该模块必须先完成部署以提供输出值。

Terraform无法发现的依赖性是隐藏的——它们具有副作用和相互参照,不能从代码中推断出. 这方面的一个例子是,一个对象并不依赖于存在,而是依赖于另一个对象的行为,并且不从代码获取它的属性. 为了克服这种情况,您可以使用依赖性-on手动明确指定依赖性。 自Terraform " 0.13 " 以来,您还可以使用模块上的 " 依赖性 " 来强迫在部署模块本身之前充分部署所列资源。 可以使用dependers_on元参数与每一种资源类型。 `依赖'还将接受其特定资源所依赖的其他资源清单.

depends_on 接受其他资源的引用列表,其语法看起来如下:

1resource "resource_type" "res" {
2  depends_on = [...] # List of resources
3
4  # Parameters...
5}

请记住,您只应该使用depends_on作为最后的解决方案选项,如果使用,则应该保持良好的记录,因为资源依赖的行为可能不会立即显而易见。

在本教程的上一步中,您没有使用depends_on指定任何明确的依赖性,因为您创建的资源没有副作用,无法从代码中推断。

使用模板进行定制

在Terraform中,模板是在适当的地方取代表达式的结果,例如在资源中设置属性值或构建字符串时。

在代替字符串中的值时,这些值被指定并被${}所包围。 模板代替通常用于循环,以方便创建的资源的自定义。

Terraform 提供「templatefile」函数,它接受两个参数:从磁盘中读取的文件和与其值相配的变量图. 返回的值是用表达式代替的文件的内容,就像 Terraform 通常在计划或应用项目时一样。

假设名为droplets.tmpl的模板文件的内容如下:

1%{ for address in addresses ~}
2${address}:80
3%{ endfor ~}

较长的声明必须被%{}包围,就像forendfor声明一样,分别表示for循环的开始和结束。

1templatefile("${path.module}/droplets.tmpl", { addresses = ["192.168.0.1", "192.168.1.1"] })

templatefile调用将返回以下值:

1[secondary_label Output]
2192.168.0.1:80
3192.168.1.1:80

例如,您可以使用此函数,当配置的一部分必须存在于专有格式时,但它取决于其余的值,并且必须以动态方式生成。

结论

在本文中,您已经最大限度地利用了 Terraform 项目中的代码,主要的方法是将常用的功能和配置包装成可自定义的模块,并在需要时使用它。

正如您所看到的那样, Terraform Registry提供第三方模块和提供商,您可以将其纳入您的项目。

本教程是如何管理 Terraform 基础设施的一部分(https://www.digitalocean.com/community/tutorial_series/how-to-manage-infrastructure-with-terraform)系列。 该系列涵盖了许多 Terraform 主题,从首次安装 Terraform 到管理复杂的项目。

Published At
Categories with 技术
comments powered by Disqus