如何在Terraform项目中部署多个环境而不重复代码

作者选择了自由开源基金作为为捐赠写作计划的一部分来接收捐赠。

简介

Terraform提供了高级功能,随着项目规模和复杂性的增加,这些功能变得越来越有用。通过构建代码以最小化重复并引入工具辅助的工作流程,可以更容易地进行测试和部署,从而减轻为多个环境维护复杂的基础设施定义的成本。

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

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

先决条件

为了完成本教程,您需要:

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

注意:本教程已针对Terraform 1.0.2进行了特别测试。

使用工作区部署多个基础架构实例

当你想要部署或测试对主要基础架构的修改版本,而不创建单独的项目并再次设置认证密钥时,多个工作区非常有用。一旦你在单独的状态下开发和测试了一个功能,你就可以将新代码整合到主工作区中,并可能删除额外的状态。当你init一个Terraform项目时,无论后端如何,Terraform都会创建一个名为default的工作区。它始终存在,你不能删除它。

然而,多个工作区并不适合创建多个环境,例如用于暂存和生产环境。因此,工作区只跟踪状态,不存储代码或其修改。

由于工作区不跟踪实际代码,您应该在版本控制(VCS)层面上通过将它们与基础架构变体匹配来管理多个工作区之间的代码分离。您如何实现这一点取决于VCS工具本身;例如,在Git分支中,这将是一个合适的抽象。为了更方便地管理多个环境中的代码,您可以将它们划分为可重用模块,这样就可以避免为每个环境重复类似的代码。

工作区中部署资源

接下来,您将创建一个项目,它将部署一个Droplet,您将从一个以上的工作区应用它。

您将在一个名为droplets.tf的文件中存储Droplet定义。

假设您位于terraform-advanced目录中,可以通过运行以下命令创建并打开它以进行编辑:

  1. nano droplets.tf

添加以下行:

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

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

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

  1. terraform apply -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: # digitalocean_droplet.web 将被创建 + 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. ...

在提示部署 default 工作区中的 Droplet 时,输入 yes

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

  1. terraform workspace list

输出将类似于这样:

Output
* default

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

通过运行 workspace new 创建并切换到名为 testing 的新工作区,您将使用它来部署不同的 Droplet:

  1. 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,通过运行:

  1. 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: # digitalocean_droplet.web 将被创建 + 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 计划部署名为 web-testing 的 Droplet,这与 web-default 的名称不同。这是因为 defaulttesting 工作区具有单独的状态,并且不知道彼此的资源 —— 尽管它们源自同一代码。

为了确认您位于 testing 工作区,使用 workspace show 输出当前的工作区:

  1. terraform workspace show

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

Output
testing

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

  1. terraform workspace select default

你会收到Terraform确认切换的输出:

Output
Switched to workspace "default".

然后,你可以通过运行 `workspace delete` 来删除它:

  1. terraform workspace delete testing

Terraform将会执行删除操作:

Output
Deleted workspace "testing"!

你可以在 `default` 工作区中销毁你部署的Droplet,通过运行:

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

当提示时输入 `yes` 来完成过程。

在本节中,你在多个Terraform工作区中进行了操作。在下一节中,你将部署一个有状态的资源。

部署有状态资源

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

由于你可能最终会销毁这些资源,或者多个资源需要它们的数据,最好将数据存储在单独的实体中,例如 DigitalOcean卷

卷提供了额外的存储空间。它们可以附加到Droplets(服务器)上,但与Droplets是分开的。在本步中,你将在droplets.tf中定义卷并将其连接到Droplet。

打开它进行编辑:

  1. nano droplets.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相同的区域。由于卷和Droplet是分开的实体,因此你需要定义一个卷附件对象来将它们连接起来。volume_attachment采用Droplet和卷ID,并指示DigitalOcean云将卷作为磁盘设备供Droplet使用。

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

通过运行以下命令来规划此配置:

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

Terraform将计划以下操作:

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: # digitalocean_droplet.web将被创建 + 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将被创建 + 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将被创建 + 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、卷和连接卷与Droplet的卷附件的详细输出。

你现在已经定义并连接了一个卷(一个有状态资源)到一个Droplet。在下一节中,你将回顾公共、预制的Terraform模块,你可以将其纳入你的项目中。

引用预制的模块

除了为您的项目创建自己的自定义模块外,您还可以使用其他开发者在Terraform Registry上公开提供的预制模块和提供商。

模块部分,您可以搜索可用模块的数据库并根据提供商进行排序,以找到具有您需要的功能的模块。找到后,您可以阅读其描述,其中列出了模块提供的输入和输出以及其外部模块和提供商依赖关系。

接下来,您将向项目中添加一个名为DigitalOcean SSH密钥模块。您将在一个名为ssh-key.tf的文件中存储代码,将其与现有定义分开。通过运行以下命令创建并打开它以进行编辑:

  1. nano 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读取来为您的帐户添加一个公共SSH密钥。

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

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

  1. 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... - Reusing previous version of digitalocean/digitalocean from the dependency lock file - Using previously-installed digitalocean/digitalocean v2.10.1 Terraform has been successfully initialized! ...

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

  1. 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.ssh-key.digitalocean_ssh_key.default[0]将被创建 + 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 主题,从第一次安装 Terraform 到管理复杂项目不等。

Source:
https://www.digitalocean.com/community/tutorials/how-to-deploy-multiple-environments-with-workspaces-in-your-terraform-project