如何构建 Terraform 项目

介绍

根据用例和感知的复杂性适当地构建Terraform项目对于确保其在日常操作中的可维护性和可扩展性至关重要。为了确保项目在部署过程中保持可扩展性并可供您和您的团队使用,需要一种系统地正确组织代码文件的方法。

在本教程中,您将了解如何根据 Terraform 项目的一般用途和复杂性来构建它们。然后,您将使用 Terraform 的更常见功能创建一个结构简单的项目:变量、本地变量、数据源和供应商。最后,您的项目将在 DigitalOcean 上部署 Ubuntu 18.04 服务器(Droplet),安装 Apache Web 服务器,并将您的域指向 Web 服务器。

先决条件

注意:本教程已专门使用 Terraform 进行了测试0.13

了解 Terraform 项目的结构

在本节中,您将了解 Terraform 将什么视为项目、如何构建基础架构代码以及何时选择哪种方法。您还将了解 Terraform 工作区、它们的作用以及 Terraform 如何存储状态。

资源是云服务(如DigitalOcean液滴)的实体在被根据指定的和推断的属性创建Terraform代码中声明。多个资源通过它们的相互联系形成基础设施。

Terraform 使用一种专门的编程语言来定义基础设施,称为Hashicorp 配置语言(HCL)。HCL 代码通常存储在以扩展名tf. Terraform 项目是包含tf文件并已使用该init命令初始化的任何目录,命令设置 Terraform 缓存和默认本地状态。

Terraform状态是跟踪实际部署在云中的资源的机制。状态存储在后端 — 本地、磁盘或远程、文件存储云服务或专门的状态管理软件,以实现最佳冗余和可靠性。您可以在Terraform 文档 中阅读有关不同后端的更多信息

项目工作区允许您在同一后端拥有多个状态,并绑定到相同的配置。这允许您部署同一基础架构的多个不同实例。每个项目都以一个名为的工作区开始default——如果您没有明确创建或切换到另一个工作区,则将使用该工作区

Terraform 中的模块(类似于其他编程语言中的库)是包含多个资源声明的参数化代码容器。它们允许您抽象出基础设施的公共部分,并在以后使用不同的输入重新使用它。

Terraform 项目还可以包含用于动态数据输入的外部代码文件,这些文件可以解析CLI 命令JSON 输出并将其提供用于资源声明。在本教程中,您将使用 Python 脚本执行此操作。

现在您知道 Terraform 项目由什么组成,让我们回顾一下 Terraform 项目结构的两种一般方法。

结构简单

适用于具有不同类型和变量的少量资源的小型和测试项目。它有几个配置文件,通常每个资源类型一个(或更多的辅助文件和一个主文件),并且没有自定义模块,因为大多数资源都是独一无二的,并且没有足够的资源可以通用和重用。在此之后,大部分代码都存储在同一目录中,彼此相邻。这些项目通常有一些变量(例如用于访问云的 API 密钥),并且可能使用动态数据输入和其他 Terraform 和 HCL 功能,尽管并不突出。

作为这种方法的文件结构的示例,这就是我们将在本教程中构建的项目最终的样子:

.
└── tf/
    ├── versions.tf
    ├── variables.tf
    ├── provider.tf
    ├── droplets.tf
    ├── dns.tf
    ├── data-sources.tf
    └── external/
        └── name-generator.py

由于该项目将部署 Apache Web 服务器 Droplet 并设置 DNS 记录,因此项目变量的定义、DigitalOcean Terraform 提供程序、Droplet 和 DNS 记录将存储在各自的文件中。所需的最低 Terraform 和 DigitalOcean 提供程序版本将在 中指定versions.tf,而将为 Droplet 生成名称(并用作 中的动态数据源data-sources.tf的 Python 脚本将存储在external文件夹中,以将其与 HCL 代码分开。

复杂结构

与简单的结构相反,这种方法适用于大型项目,除了通常的代码之外,还具有明确定义的子目录结构,其中包含多个复杂程度不同的模块。这些模块可以相互依赖。结合版本控制系统,这些项目可以广泛使用工作区。这种方法适用于管理多个应用程序的大型项目,同时尽可能地重用代码。

开发、登台、质量保证和生产基础设施实例也可以通过依赖公共模块将其置于同一项目的不同目录下,从而消除重复代码并使项目成为事实的中心来源。这是一个结构更复杂的示例项目的文件结构,包含多个部署应用程序、Terraform 模块和目标云环境:

.
└── tf/
    ├── modules/
    │   ├── network/
    │   │   ├── main.tf
    │   │   ├── dns.tf
    │   │   ├── outputs.tf
    │   │   └── variables.tf
    │   └── spaces/
    │       ├── main.tf
    │       ├── outputs.tf
    │       └── variables.tf
    └── applications/
        ├── backend-app/
        │   ├── env/
        │   │   ├── dev.tfvars
        │   │   ├── staging.tfvars
        │   │   ├── qa.tfvars
        │   │   └── production.tfvars
        │   └── main.tf
        └── frontend-app/
            ├── env/
            │   ├── dev.tfvars
            │   ├── staging.tfvars
            │   ├── qa.tfvars
            │   └── production.tfvars
            └── main.tf

本系列稍后将进一步探讨这种方法。

您现在知道什么是 Terraform 项目,如何根据感知的复杂性最好地构建它,以及 Terraform 工作区的作用。在接下来的步骤中,您将创建一个具有简单结构的项目,该项目将配置一个安装了 Apache Web 服务器并为您的域设置 DNS 记录的 Droplet。您将首先使用 DigitalOcean 提供程序和变量初始化您的项目,然后继续定义 Droplet、一个提供其名称的动态数据源和一个用于部署的 DNS 记录。

第 1 步 — 设置您的初始项目

在本节中,您将把 DigitalOcean Terraform 提供程序添加到您的项目中,定义项目变量,并声明一个 DigitalOcean 提供程序实例,以便 Terraform 能够连接到您的帐户。

首先使用以下命令为您的 Terraform 项目创建一个目录:

  • mkdir ~/apache-droplet-terraform

导航到它:

  • cd ~/apache-droplet-terraform

由于该项目将遵循简单的结构化方法,您将根据上一节中的文件结构将提供者、变量、Droplet 和 DNS 记录代码存储在单独的文件中。首先,您需要将 DigitalOcean Terraform 提供程序作为必需的提供程序添加到您的项目中。

创建一个名为的文件versions.tf并通过运行打开它进行编辑:

  • nano versions.tf

添加以下几行:

~/apache-droplet-terraform/versions.tf
terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "1.22.2"
    }
  }
  required_version = ">= 0.13"
}

在此terraform块中,您列出所需的提供程序(DigitalOcean,版本1.22.2)并将所需的最低 Terraform 版本设置为高于或等于0.13完成后,保存并关闭文件。

然后,variables.tf按照将不同资源类型存储在单独的代码文件中的方法,定义您的项目将在文件中公开的变量

  • nano variables.tf

添加以下变量:

~/apache-droplet-terraform/variables.tf
variable "do_token" {}
variable "domain_name" {}

保存并关闭文件。

do_token变量将保存您的 DigitalOcean 个人访问令牌,domain_name并将指定您想要的域名。部署的 Droplet 将自动安装由 SSH 指纹识别的 SSH 密钥。

接下来,让我们为该项目定义 DigitalOcean 提供程序实例。您将把它存储在一个名为provider.tf. 通过运行创建并打开它进行编辑:

  • nano provider.tf

添加提供者:

~/apache-droplet-terraform/provider.tf
provider "digitalocean" {
  token = var.do_token
}

完成后保存并退出。您已经定义了digitalocean提供程序,它对应于您之前在 中指定的所需提供程序provider.tf,并将其标记设置为将在运行时提供的变量值。

在此步骤中,您为项目创建了一个目录,请求 DigitalOcean 提供程序可用,声明项目变量,并设置与 DigitalOcean 提供程序实例的连接以使用稍后将提供的身份验证令牌。您现在将编写一个脚本,为您的项目定义生成动态数据。

第 2 步 – 为动态数据创建 Python 脚本

在继续定义 Droplet 之前,您将创建一个 Python 脚本,该脚本将动态生成 Droplet 的名称并声明一个数据源资源来解析它。该名称将通过将一个常量字符串 ( web) 与本地机器的当前时间连接起来生成,以UNIX 纪元格式表示当根据命名方案生成多个 Droplet 时,命名脚本非常有用,可以轻松区分它们。

您将脚本存储在名为 的文件中name-generator.py,在名为 的目录中external首先,通过运行创建目录:

  • mkdir external

external目录位于项目的根目录中,将存储非 HCL 代码文件,例如您将编写的 Python 脚本。

name-generator.py在下面创建external并打开它进行编辑:

  • nano external/name-generator.py

添加以下代码:

外部/名称生成器.py
import json, time

fixed_name = "web"
result = {
  "name": f"{fixed_name}-{int(time.time())}",
}

print(json.dumps(result))

这个 Python 脚本导入jsontime模块,声明一个名为字典result,并将name的值设置为一个内插字符串,该字符串将fixed_name与运行它的机器的当前 UNIX 时间相结合然后,将result转换为 JSON 并在 上输出stdout每次运行脚本时输出都会不同:

Output
{"name": "web-1597747959"}

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

注意:大型复杂的结构化项目需要更多地考虑如何创建和使用外部数据源,尤其是在可移植性和错误处理方面。Terraform 期望执行的程序将人类可读的错误消息写入stderr并以非零状态正常退出,由于任务的简单性,这在此步骤中未显示。此外,它希望程序没有副作用,因此可以根据需要多次重新运行。

有关 Terraform 期望的更多信息,请访问有关数据源官方文档

现在脚本已准备就绪,您可以定义数据源,该数据源将从脚本中提取数据。您将data-sources.tf按照简单的结构化方法将数据源存储在以项目根目录命名的文件中。

通过运行创建它以进行编辑:

  • nano data-sources.tf

添加以下定义:

~/apache-droplet-terraform/data-sources.tf
data "external" "droplet_name" {
  program = ["python3", "${path.module}/external/name-generator.py"]
}

保存并关闭文件。

使用 Python 3调用此数据源droplet_name并执行name-generator.py脚本,该脚本位于external您刚刚创建目录中。它会自动解析其输出并提供其result属性下的反序列化数据以供在其他资源定义中使用。

现在声明数据源后,您可以定义 Apache 将在其上运行的 Droplet。

第 3 步 – 定义 Droplet

在此步骤中,您将按照简单的结构化方法编写 Droplet 资源的定义并将其存储在专用于 Droplet 的代码文件中。它的名称将来自您刚刚创建的动态数据源,并且每次部署时都会有所不同。

创建并打开droplets.tf文件进行编辑:

  • nano droplets.tf

添加以下 Droplet 资源定义:

~/apache-droplet-terraform/droplets.tf
data "digitalocean_ssh_key" "ssh_key" {
  name = "your_ssh_key_name"
}

resource "digitalocean_droplet" "web" {
  image  = "ubuntu-18-04-x64"
  name   = data.external.droplet_name.result.name
  region = "fra1"
  size   = "s-1vcpu-1gb"
  ssh_keys = [
    data.digitalocean_ssh_key.ssh_key.id
  ]
}

您首先声明一个名为 的 DigitalOcean SSH 密钥资源ssh_key,它将通过其名称从您的帐户中获取一个密钥。确保将突出显示的代码替换为您的 SSH 密钥名称。

然后,您声明一个名为web. 它在云中的实际名称会有所不同,因为它是从droplet_name外部数据源请求的为了在每次部署时使用 SSH 密钥引导 Droplet 资源,将 IDssh_key传递到ssh_keys参数中,以便 DigitalOcean 知道要应用哪个密钥。

现在,这就是您需要进行的与 相关的所有配置droplet.tf,因此请在完成后保存并关闭文件。

您现在将为 DNS 记录编写配置,将您的域指向刚刚声明的 Droplet。

第 4 步 – 定义 DNS 记录

该过程的最后一步是配置指向您域中的 Droplet 的 DNS 记录。

您将 DNS 配置存储在名为 的文件中dns.tf,因为它是与您在前面步骤中创建的其他资源类型不同的资源类型。创建并打开它进行编辑:

  • nano dns.tf

添加以下几行:

~/apache-droplet-terraform/dns.tf
resource "digitalocean_record" "www" {
  domain = var.domain_name
  type   = "A"
  name   = "@"
  value  = digitalocean_droplet.web.ipv4_address
}

此代码在您的域名(使用变量传入)中声明了类型为 的 DigitalOcean DNS 记录A该记录的名称为@,它是到域本身的占位符路由,并将 Droplet IP 地址作为其value. 您可以name用其他内容替换该值,这将导致创建子域。

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

现在您已经配置了 Droplet、名称生成器数据源和 DNS 记录,您将继续在云中部署项目。

步骤 5 — 规划和应用配置

在本节中,您将初始化您的 Terraform 项目,将其部署到云中,并检查是否正确配置了所有内容。

现在项目基础设施已经完全定义,在部署之前要做的就是初始化 Terraform 项目。通过运行以下命令来执行此操作:

  • terraform init

您将收到以下输出:

Output
Initializing the backend... Initializing provider plugins... - Finding digitalocean/digitalocean versions matching "1.22.2"... - Finding latest version of hashicorp/external... - Installing hashicorp/external v1.2.0... - Installed hashicorp/external v1.2.0 (signed by HashiCorp) - Installing digitalocean/digitalocean v1.22.2... - Installed digitalocean/digitalocean v1.22.2 (signed by a HashiCorp partner, key ID F82037E524B9C0E8) Partner and community providers are signed by their developers. If you'd like to know more about provider signing, you can read about it here: https://www.terraform.io/docs/plugins/signing.html The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, we recommend adding version constraints in a required_providers block in your configuration, with the constraint strings suggested below. * hashicorp/external: version = "~> 1.2.0" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.

您现在可以使用动态生成的名称和随附的域将 Droplet 部署到您的 DigitalOcean 帐户。

首先将域名、SSH 密钥指纹和您的个人访问令牌定义为环境变量,这样您就不必在每次运行 Terraform 时复制这些值。运行以下命令,替换突出显示的值:

  • export DO_PAT="your_do_api_token"
  • export DO_DOMAIN_NAME="your_domain"

您可以在 DigitalOcean 控制面板中找到您的 API 令牌。

plan使用传入的变量值运行命令以查看 Terraform 将采取哪些步骤来部署您的项目:

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

输出将类似于以下内容:

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. data.digitalocean_ssh_key.ssh_key: Refreshing state... data.external.droplet_name: Refreshing state... ------------------------------------------------------------------------ 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-1597780013" + price_hourly = (known after apply) + price_monthly = (known after apply) + private_networking = (known after apply) + region = "fra1" + resize_disk = true + size = "s-1vcpu-1gb" + ssh_keys = [ + "...", ] + status = (known after apply) + urn = (known after apply) + vcpus = (known after apply) + volume_ids = (known after apply) + vpc_uuid = (known after apply) } # digitalocean_record.www will be created + resource "digitalocean_record" "www" { + domain = "your_domain" + fqdn = (known after apply) + id = (known after apply) + name = "@" + ttl = (known after apply) + type = "A" + value = (known after apply) } Plan: 2 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run.

以绿色开头的行+表示 Terraform 将创建后面的每个资源 – 这正是应该发生的事情,因此您可以apply进行配置:

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

输出将与之前相同,但这次您将被要求确认:

Output
Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: `yes`

输入yes,Terraform 将提供您的 Droplet 和 DNS 记录:

Output
digitalocean_droplet.web: Creating... ... digitalocean_droplet.web: Creation complete after 33s [id=204432105] digitalocean_record.www: Creating... digitalocean_record.www: Creation complete after 1s [id=110657456] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Terraform 现在已将部署的资源记录在其状态。要确认 DNS 记录和 Droplet 已成功连接,您可以从本地状态中提取 Droplet 的 IP 地址,并检查它是否与您域的公共 DNS 记录匹配。运行以下命令获取IP地址:

  • terraform show | grep "ipv4"

您将收到 Droplet 的 IP 地址:

Output
ipv4_address = "your_Droplet_IP"

您可以通过运行来检查公共 A 记录:

  • nslookup -type=a your_domain | grep "Address" | tail -1

输出将显示 A 记录指向的 IP 地址:

Output
Address: your_Droplet_IP

它们应该是相同的,这意味着 Droplet 和 DNS 记录已成功配置。

为了下一步发生的更改,请通过运行以下命令销毁已部署的资源:

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

出现提示时,输入yes以继续。

在此步骤中,您已经创建了基础架构并将其应用于您的 DigitalOcean 帐户。您现在将修改它以使用 Terraform 配置器在已配置的 Droplet 上自动安装 Apache Web 服务器。

第 6 步 – 使用 Provisioners 运行代码

现在,您将通过使用remote-exec配置器执行自定义命令,在已部署的 Droplet 上设置 Apache Web 服务器的安装

Terraform 供应器可用于在创建的远程资源(remote-exec供应器)或执行代码的本地机器(使用local-exec供应器)上执行特定操作如果供应商失败,该节点将在当前状态下被标记为受污染,这意味着它将在下一次运行期间被删除并重新创建。

要连接到已配置的 Droplet,Terraform 需要在 Droplet 上设置的密钥的私有 SSH 密钥。传递私钥位置的最佳方法是使用变量,因此打开variables.tf进行编辑:

  • nano variables.tf

添加突出显示的行:

~/apache-droplet-terraform/variables.tf
variable "do_token" {}
variable "domain_name" {}
variable "private_key" {}

您现在已将一个名为 的新变量添加private_key到您的项目中。保存并关闭文件。

接下来,您将连接数据和远程供应商声明添加到您的 Droplet 配置中。droplets.tf通过运行打开以进行编辑:

  • nano droplets.tf

使用突出显示的行扩展现有代码:

~/apache-droplet-terraform/droplets.tf
data "digitalocean_ssh_key" "ssh_key" {
  name = "your_ssh_key_name"
}

resource "digitalocean_droplet" "web" {
  image  = "ubuntu-18-04-x64"
  name   = data.external.droplet_name.result.name
  region = "fra1"
  size   = "s-1vcpu-1gb"
  ssh_keys = [
    data.digitalocean_ssh_key.ssh_key.id
  ]

  connection {
    host        = self.ipv4_address
    user        = "root"
    type        = "ssh"
    private_key = file(var.private_key)
    timeout     = "2m"
  }

  provisioner "remote-exec" {
    inline = [
      "export PATH=$PATH:/usr/bin",
      # Install Apache
      "apt update",
      "apt -y install apache2"
    ]
  }
}

connection块指定 Terraform 应如何连接到目标 Droplet。provisioner块包含inline参数中的命令数组,它将在配置后执行。也就是说,更新包管理器缓存并安装 Apache。完成后保存并退出。

您也可以为私钥路径创建一个临时环境变量:

  • export DO_PRIVATE_KEY="private_key_location"

注意:私钥以及您希望从 Terraform 中加载的任何其他文件必须放在项目中。您可以查看如何在 Ubuntu 18.04 上设置 SSH 密钥教程,了解有关在 Ubuntu 18.04 或其他发行版上设置 SSH 密钥的更多信息。

再次尝试应用配置:

  • terraform apply -var "do_token=${DO_PAT}" -var "domain_name=${DO_DOMAIN_NAME}" -var "private_key=${DO_PRIVATE_KEY}"

输入yes提示时。您将收到与之前类似的输出,但随后是来自remote-exec供应商的长输出

Output
digitalocean_droplet.web: Creating... digitalocean_droplet.web: Still creating... [10s elapsed] digitalocean_droplet.web: Still creating... [20s elapsed] digitalocean_droplet.web: Still creating... [30s elapsed] digitalocean_droplet.web: Provisioning with 'remote-exec'... digitalocean_droplet.web (remote-exec): Connecting to remote host via SSH... digitalocean_droplet.web (remote-exec): Host: ... digitalocean_droplet.web (remote-exec): User: root digitalocean_droplet.web (remote-exec): Password: false digitalocean_droplet.web (remote-exec): Private key: true digitalocean_droplet.web (remote-exec): Certificate: false digitalocean_droplet.web (remote-exec): SSH Agent: false digitalocean_droplet.web (remote-exec): Checking Host Key: false digitalocean_droplet.web (remote-exec): Connected! ... digitalocean_droplet.web: Creation complete after 1m5s [id=204442200] digitalocean_record.www: Creating... digitalocean_record.www: Creation complete after 1s [id=110666268] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

您现在可以在 Web 浏览器中导航到您的域。您将看到默认的 Apache 欢迎页面。

Apache Web 服务器 - 默认页面

这意味着 Apache 已成功安装,并且 Terraform 正确配置了所有内容。

要销毁已部署的资源,请运行以下命令并yes在出现提示时输入:

  • terraform destroy -var "do_token=${DO_PAT}" -var "domain_name=${DO_DOMAIN_NAME}" -var "private_key=${DO_PRIVATE_KEY}"

您现在已经完成了一个具有简单结构的小型 Terraform 项目,该项目在 Droplet 上部署 Apache Web 服务器并为所需域设置 DNS 记录。

结论

您已经了解了根据复杂性构建 Terraform 项目的两种一般方法。然后,您已经部署了一个运行 Apache 的 Droplet,其中包含您域的 DNS 记录,遵循简单的结构化方法,并使用remote-exec配置器来执行命令。

作为参考,这里是您在本教程中创建的项目的文件结构:

.
└── tf/
    ├── versions.tf
    ├── variables.tf
    ├── provider.tf
    ├── droplets.tf
    ├── dns.tf
    ├── data-sources.tf
    └── external/
        └── name-generator.py

根据本教程第一部分中概述的简单项目结构,您定义的资源(Droplet、DNS 记录和动态数据源、DigitalOcean 提供程序和变量)每个都存储在自己单独的文件中。

有关 Terraform 配置器及其参数的更多信息,请访问官方文档

觉得文章有用?

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