如何在不重复代码的情况下在Terraform项目中部署多个环境
作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。
介绍
Terraform 提供的高级功能随着项目规模和复杂性的增长而变得越来越有用。 通过构建代码以最大程度地减少重复并引入工具辅助工作流以简化测试和部署,可以降低维护多个环境的复杂基础架构定义的成本。
Terraform 将 state 与后端相关联,后端确定状态存储和检索的位置和方式。 每个州只有一个后端,并与基础设施配置相关联。 某些后端,例如 local 或 s3,可能包含多个状态。 在这种情况下,状态和基础设施与后端的配对描述了一个 工作区 。 工作区允许您部署相同基础架构配置的多个不同实例,而无需将它们存储在单独的后端。
在本教程中,您将首先使用不同的工作区部署多个基础架构实例。 然后,您将部署一个有状态资源,在本教程中,它将是一个 DigitalOcean 卷。 最后,您将参考 Terraform Registry 中的预制模块,您可以使用它来补充您自己的模块。
先决条件
要完成本教程,您需要:
- DigitalOcean 个人访问令牌,您可以通过 DigitalOcean 控制面板创建它。 您可以在 DigitalOcean 产品文档中找到说明,如何创建个人访问令牌。
- Terraform 安装在您的本地计算机上,并使用 DO 提供程序设置了一个项目。 完成 How To Use Terraform with DigitalOcean 教程的 Step 1 和 Step 2,并确保将项目文件夹命名为
terraform-advanced,而不是loadbalance。 在 Step 2 期间,不要包含pvt_key变量和 SSH 密钥资源。
注意: 本教程专门用 Terraform 1.0.2 测试过。
使用工作区部署多个基础设施实例
当您想要部署或测试主基础架构的修改版本而不创建单独的项目并再次设置身份验证密钥时,多个工作区非常有用。 使用单独状态开发和测试功能后,您可以将新代码合并到主工作区中,并可能删除附加状态。 当您 init 一个 Terraform 项目时,无论后端如何,Terraform 都会创建一个名为 default 的工作区。 它始终存在,您永远无法删除它。
但是,多个工作空间不是创建多个环境(例如用于登台和生产)的合适解决方案。 因此,仅跟踪状态的工作区不存储代码或其修改。
由于工作区不跟踪实际代码,因此您应该在版本控制 (VCS) 级别管理多个工作区之间的代码分离,方法是将它们与它们的基础设施变体相匹配。 如何实现这一点取决于 VCS 工具本身; 例如,在 Git 分支 中将是一个合适的抽象。 为了更容易管理多个环境的代码,您可以将它们分解为 可重用模块 ,这样您就可以避免为每个环境重复类似的代码。
在工作区中部署资源
您现在将创建一个部署 Droplet 的项目,您将从多个工作区应用该项目。
您将把 Droplet 定义存储在一个名为 droplets.tf 的文件中。
假设您在 terraform-advanced 目录中,通过运行创建并打开它进行编辑:
nano droplets.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}"
输出将类似于以下内容:
OutputTerraform 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 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.
...
当提示在 default 工作区中部署 Droplet 时,输入 yes。
Droplet 的名称将是 web-default,因为您开始使用的工作空间称为 default。 您可以列出工作区以确认它是唯一可用的:
terraform workspace list
输出将类似于以下内容:
Output* default
星号 (*) 表示您当前选择了该工作区。
通过运行 workspace new 创建并切换到名为 testing 的新工作区,您将使用它来部署不同的 Droplet:
terraform workspace new testing
输出将类似于以下内容:
OutputCreated 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}"
输出将与之前的运行类似:
OutputTerraform 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 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 计划部署一个名为 web-testing 的 Droplet,它的命名与 web-default 不同。 这是因为 default 和 testing 工作区具有不同的状态并且不知道彼此的资源——即使它们源自相同的代码。
要确认您在 testing 工作区中,请使用 workspace show 输出您当前所在的工作区:
terraform workspace show
输出将是当前工作区的名称:
Outputtesting
要删除工作区,您首先需要销毁其所有已部署的资源。 然后,如果它处于活动状态,您需要使用 workspace select 切换到另一个。 由于这里的testing工作区是空的,你可以马上切换到default:
terraform workspace select default
您将收到确认切换的 Terraform 输出:
OutputSwitched to workspace "default".
然后你可以通过运行 workspace delete 来删除它:
terraform workspace delete testing
然后 Terraform 将执行删除:
OutputDeleted workspace "testing"!
您可以通过运行以下命令销毁您在 default 工作区中部署的 Droplet:
terraform destroy -var "do_token=${DO_PAT}"
提示完成该过程时输入 yes。
在本部分中,您已在多个 Terraform 工作区中工作。 在下一部分中,您将部署有状态资源。
部署有状态资源
无状态资源不存储数据,因此您可以快速创建和替换它们,因为它们不是唯一的。 另一方面,有状态资源包含唯一的或不能简单地重新创建的数据; 因此,它们需要持久的数据存储。
由于您最终可能会破坏此类资源,或者多个资源需要它们的数据,因此最好将其存储在单独的实体中,例如 DigitalOcean Volumes。
卷提供额外的存储空间。 它们可以附加到 Droplet(服务器),但与它们是分开的。 在此步骤中,您将定义 Volume 并将其连接到 droplets.tf 中的 Droplet。
打开它进行编辑:
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
}
在这里,您定义了两个新资源,Volume 本身和 Volume 附件。 Volume 为 10GB,格式为 ext4,称为 new-volume,与 Droplet 位于同一区域。 由于 Volume 和 Droplet 是独立的实体,您需要定义一个 Volume 附件对象来连接它们。 volume_attachment 获取 Droplet 和 Volume ID,并指示 DigitalOcean 云将 Volume 作为磁盘设备提供给 Droplet。
完成后,保存并关闭文件。
通过运行计划此配置:
terraform plan -var "do_token=${DO_PAT}"
Terraform 将计划的行动如下:
OutputTerraform 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 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 连接到 Droplet 的 Volume 附件的输出详细信息。
您现在已经定义了一个 Volume(一个有状态的资源)并将其连接到一个 Droplet。 在下一部分中,您将查看可以合并到项目中的公共预制 Terraform 模块。
引用预制模块
除了 为您的项目创建自己的 自定义模块外,您还可以使用来自其他开发人员的预制模块和提供程序,这些模块和提供程序在 Terraform Registry 上公开提供。
在 模块部分 您可以搜索可用模块的数据库并按提供者排序,以找到具有您需要的功能的模块。 找到一个后,您可以阅读它的描述,其中列出了模块提供的输入和输出,以及它的外部模块和提供程序依赖项。
您现在将 DigitalOcean SSH 密钥模块 添加到您的项目中。 您将把代码与现有定义分开存储在一个名为 ssh-key.tf 的文件中。 通过运行创建并打开它进行编辑:
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 密钥添加到您的帐户。
完成后,保存并关闭文件。
在你 plan 这段代码之前,你必须通过运行下载引用的模块:
terraform init
您将收到类似于以下内容的输出:
OutputInitializing 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! ...
您现在可以计划更改的代码:
terraform plan -var "do_token=${DO_PAT}"
您将收到与此类似的输出:
OutputTerraform 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] 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 主题,从首次安装 Terraform 到管理复杂的项目。