如何在不重复代码的情况下在 Terraform 项目中部署多个环境

作为Write for DOnations计划的一部分,作者选择了免费和开源基金来接受捐赠

介绍

Terraform 提供的高级功能随着项目规模和复杂性的增长而变得越来越有用。通过构建代码以最大程度地减少重复并引入工具辅助工作流以简化测试和部署,可以降低为多个环境维护复杂基础架构定义的成本。

Terraform 将状态与后端相关联,后端确定存储和检索状态的位置和方式。每个州只有一个后端,并与基础设施配置相关联。某些后端,例如locals3,可能包含多个状态。在这种情况下,状态和基础设施与后端的配对描述了一个工作空间工作区允许您部署同一基础架构配置的多个不同实例,而无需将它们存储在单独的后端中。

在本教程中,您将首先使用不同的工作区部署多个基础设施实例。然后,您将部署一个有状态资源,在本教程中,它将是 DigitalOcean Volume。最后,您将参考Terraform Registry 中的预制模块,您可以使用它们来补充您自己的模块。

先决条件

  • DigitalOcean 个人访问令牌,您可以通过 DigitalOcean 控制面板创建。您可以在如何生成个人访问令牌教程中找到相关说明
  • Terraform 安装在您的本地计算机上,并使用 DO 提供程序设置了一个项目。完成步骤1步骤2中的如何使用Terraform与DigitalOcean教程,并确保该项目命名文件夹terraform-advanced,而不是loadbalance步骤 2 中,不要包含pvt_key变量和 SSH 密钥资源。

注意:我们已经使用 Terraform 专门测试了本教程0.13

使用工作区部署多个基础设施实例

当您想要部署或测试主基础架构的修改版本而不创建单独的项目并再次设置身份验证密钥时,多个工作区非常有用。使用单独状态开发和测试功能后,您可以将新代码合并到主工作区中,并可能删除附加状态。当您init创建 Terraform 项目时,无论后端如何,Terraform 都会创建一个名为default. 它始终存在,您永远无法删除它。

但是,多个工作区不是创建多个环境(例如登台和生产)的合适解决方案。因此,仅跟踪状态的工作区不存储代码或其修改。

由于工作区不跟踪实际代码,因此您应该通过将它们与其基础结构变体进行匹配来在版本控制 (VCS) 级别管理多个工作区之间的代码分离。如何实现这一点取决于 VCS 工具本身;例如,在Git 分支中将是一个合适的抽象。为了更轻松地管理多个环境的代码,您可以将它们分解为可重用的模块,从而避免为每个环境重复类似的代码。

在工作区中部署资源

您现在将创建一个部署 Droplet 的项目,您将从多个工作区应用该项目。

您将把 Droplet 定义存储在一个名为droplets.tf.

假设您在terraform-advanced目录中,通过运行创建并打开它进行编辑:

  • nano droplets.tf

添加以下几行:

液滴.tf
resource "digitalocean_droplet" "web" {
  image  = "ubuntu-18-04-x64"
  name   = "web-${terraform.workspace}"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

此定义将创建一个运行 Ubuntu 18.04 的 Droplet,在该fra1区域中具有一个 CPU 内核和 1 GB RAM 它的名称将包含部署它的当前工作区的名称。完成后,保存并关闭文件。

应用 Terraform 的项目以运行其操作:

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

您的输出将类似于以下内容:

Output
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # digitalocean_droplet.web will be created + resource "digitalocean_droplet" "web" { + backups = false + created_at = (known after apply) + disk = (known after apply) + id = (known after apply) + image = "ubuntu-18-04-x64" + ipv4_address = (known after apply) + ipv4_address_private = (known after apply) + ipv6 = false + ipv6_address = (known after apply) + ipv6_address_private = (known after apply) + locked = (known after apply) + memory = (known after apply) + monitoring = false + name = "web-default" + price_hourly = (known after apply) + price_monthly = (known after apply) + private_networking = (known after apply) + region = "fra1" + resize_disk = true + size = "s-1vcpu-1gb" + status = (known after apply) + urn = (known after apply) + vcpus = (known after apply) + volume_ids = (known after apply) + vpc_uuid = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. ...

输入yes提示的部署墨滴时default的工作空间。

Droplet 的名称将是web-default,因为您开始的工作区称为default您可以列出工作区以确认它是唯一可用的:

  • terraform workspace list

您将收到以下输出:

Output
* default

星号 ( *) 表示您当前已选择该工作区。

testing通过运行workspace new以下命令创建并切换到一个名为 的新工作区,您将使用该工作区部署不同的 Droplet

  • terraform workspace new testing

您将获得类似于以下内容的输出:

Output
Created and switched to workspace "testing"! You're now on a new, empty workspace. Workspaces isolate their state, so if you run "terraform plan" Terraform will not see any existing state for this configuration.

您可以通过运行以下命令再次计划 Droplet 的部署:

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

输出将类似于上一次运行:

Output
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # digitalocean_droplet.web will be created + resource "digitalocean_droplet" "web" { + backups = false + created_at = (known after apply) + disk = (known after apply) + id = (known after apply) + image = "ubuntu-18-04-x64" + ipv4_address = (known after apply) + ipv4_address_private = (known after apply) + ipv6 = false + ipv6_address = (known after apply) + ipv6_address_private = (known after apply) + locked = (known after apply) + memory = (known after apply) + monitoring = false + name = "web-testing" + price_hourly = (known after apply) + price_monthly = (known after apply) + private_networking = (known after apply) + region = "fra1" + resize_disk = true + size = "s-1vcpu-1gb" + status = (known after apply) + urn = (known after apply) + vcpus = (known after apply) + volume_ids = (known after apply) + vpc_uuid = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. ...

请注意,Terraform 计划部署一个名为 的 Droplet web-testing,它的名称与web-default. 这是因为defaulttesting工作区具有不同的状态并且不知道彼此的资源——即使它们源自相同的代码。

要确认您在testing工作区中,请输出您所在的当前工作区workspace show

  • terraform workspace show

输出将是当前工作区的名称:

Output
testing

要删除工作区,首先需要销毁其所有部署的资源。然后,如果它处于活动状态,您需要使用workspace select. 由于testing这里工作区是空的,您可以立即切换到default

  • terraform workspace select default

您将收到 Terraform 的输出确认切换:

Output
Switched to workspace "default".

然后,您可以通过运行来删除它workspace delete

  • terraform workspace delete testing

然后 Terraform 将执行删除操作:

Output
Deleted workspace "testing"!

您可以default通过运行以下命令销毁您在工作区中部署的 Droplet

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

输入yes提示完成该过程时。

在本节中,您在多个 Terraform 工作区中工作过。在下一部分中,您将部署有状态资源。

部署有状态资源

无状态资源不存储数据,因此您可以快速创建和替换它们,因为它们不是唯一的。另一方面,有状态资源包含独特的或不能简单地重新创建的数据;因此,它们需要持久的数据存储。

由于您最终可能会破坏此类资源,或者多个资源需要它们的数据,因此最好将其存储在单独的实体中,例如DigitalOcean Volumes

卷是可以附加到 Droplet(服务器)的对象,但与它们分开,并提供额外的存储空间。在此步骤中,您将定义 Volume 并将其连接到droplets.tf.

打开它进行编辑:

  • nano droplets.tf

添加以下几行:

液滴.tf
resource "digitalocean_droplet" "web" {
  image  = "ubuntu-18-04-x64"
  name   = "web-${terraform.workspace}"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

resource "digitalocean_volume" "volume" {
  region                  = "fra1"
  name                    = "new-volume"
  size                    = 10
  initial_filesystem_type = "ext4"
  description             = "New Volume for Droplet"
}

resource "digitalocean_volume_attachment" "volume_attachment" {
  droplet_id = digitalocean_droplet.web.id
  volume_id  = digitalocean_volume.volume.id
}

在这里定义两个新资源,卷本身和卷附件。卷将为 10GB,格式为ext4,称为new-volume,并与 Droplet 位于同一区域。要将 Volume 连接到 Droplet,因为它们是独立的实体,您需要定义一个 Volume 附件对象。volume_attachment获取 Droplet 和 Volume ID 并指示 DigitalOcean 云将 Volume 作为磁盘设备提供给 Droplet。

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

通过运行来规划此配置:

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

Terraform 将计划的操作如下:

Output
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # digitalocean_droplet.web will be created + resource "digitalocean_droplet" "web" { + backups = false + created_at = (known after apply) + disk = (known after apply) + id = (known after apply) + image = "ubuntu-18-04-x64" + ipv4_address = (known after apply) + ipv4_address_private = (known after apply) + ipv6 = false + ipv6_address = (known after apply) + ipv6_address_private = (known after apply) + locked = (known after apply) + memory = (known after apply) + monitoring = false + name = "web-default" + price_hourly = (known after apply) + price_monthly = (known after apply) + private_networking = (known after apply) + region = "fra1" + resize_disk = true + size = "s-1vcpu-1gb" + status = (known after apply) + urn = (known after apply) + vcpus = (known after apply) + volume_ids = (known after apply) + vpc_uuid = (known after apply) } # digitalocean_volume.volume will be created + resource "digitalocean_volume" "volume" { + description = "New Volume for Droplet" + droplet_ids = (known after apply) + filesystem_label = (known after apply) + filesystem_type = (known after apply) + id = (known after apply) + initial_filesystem_type = "ext4" + name = "new-volume" + region = "fra1" + size = 10 + urn = (known after apply) } # digitalocean_volume_attachment.volume_attachment will be created + resource "digitalocean_volume_attachment" "volume_attachment" { + droplet_id = (known after apply) + id = (known after apply) + volume_id = (known after apply) } Plan: 3 to add, 0 to change, 0 to destroy. ...

Terraform 将创建一个 Droplet、一个 Volume 和一个 Volume 附件的输出详细信息,该附件将 Volume 连接到 Droplet。

您现在已经定义了一个卷(一个有状态的资源)并将其连接到一个 Droplet。在下一部分中,您将查看可以合并到项目中的公共预制 Terraform 模块。

引用预制模块

除了为您的项目创建您自己的自定义模块之外,您还可以使用其他开发人员提供的预制模块和提供程序,它们在Terraform Registry上公开可用

模块部分,您可以搜索可用模块的数据库并按提供商排序,以便找到具有所需功能的模块。找到后,您可以阅读它的描述,其中列出了模块提供的输入和输出,以及它的外部模块和提供者依赖项。

Terraform Registry - SSH 密钥模块

您现在将把DigitalOcean SSH 密钥模块添加到您的项目中。您将代码与现有定义分开存储在一个名为ssh-key.tf. 通过运行创建并打开它进行编辑:

  • nano ssh-key.tf

添加以下几行:

ssh-key.tf
module "ssh-key" {
  source         = "clouddrove/ssh-key/digitalocean"
  key_path       = "~/.ssh/id_rsa.pub"
  key_name       = "new-ssh-key"
  enable_ssh_key = true
}

这段代码clouddrove/droplet/digitalocean从注册表中定义了一个模块实例,并设置了它提供的一些参数。它应该通过从~/.ssh/id_rsa.pub.

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

在执行plan此代码之前,您必须通过运行以下命令下载引用的模块:

  • terraform init

您将收到类似于以下内容的输出:

Output
Initializing modules... Downloading clouddrove/ssh-key/digitalocean 0.13.0 for ssh-key... - ssh-key in .terraform/modules/ssh-key Initializing the backend... Initializing provider plugins... - Using previously-installed digitalocean/digitalocean v1.22.2 Terraform has been successfully initialized! ...

您现在可以为更改计划代码:

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

您将收到类似于以下内容的输出:

Output
Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: ... # module.ssh-key.digitalocean_ssh_key.default[0] will be created + resource "digitalocean_ssh_key" "default" { + fingerprint = (known after apply) + id = (known after apply) + name = "devops" + public_key = "ssh-rsa ... demo@clouddrove" } Plan: 4 to add, 0 to change, 0 to destroy. ...

输出显示您将创建 SSH 密钥资源,这意味着您从代码下载并调用了该模块。

结论

更大的项目可以利用 Terraform 提供的一些高级功能来帮助降低复杂性并使维护更容易。工作区允许您测试新添加的代码,而无需触及稳定的主要部署。您还可以将工作区与版本控制系统结合起来以跟踪代码更改。使用预制模块还可以缩短开发时间,但如果模块过时,将来可能会产生额外的费用或时间。

有关使用 Terraform 的更多资源,请查看我们的如何使用 Terraform 系列管理基础设施

觉得文章有用?

点个广告表达一下你的爱意吧 !😁