Terraform 管理基础设施的利器

大型应用程序通常是团队编写的代码和第三方应用程序的混合体。这些第三方应用程序可能是 AWS 或 Docker 之类的东西。而Terraform就是管理这些基础设施的利器。

Terraform 管理基础设施的利器

大型应用程序通常是团队编写的代码和第三方应用程序的混合体。这些第三方应用程序可能是 AWS 或 Docker 之类的东西。这些服务的配置可能不会像应用程序代码那样经常更改,但是一旦发生更改,该过程就充满了潜在的错误。这是因为没有办法为更改编写测试或回滚它们——作为开发人员在交付应用程序代码时所依赖的东西。

使用 Terraform 允许用户在没有责任、测试或历史版本的 GUI 中实施工程最佳实践来改进您的基础架构管理。

以建立商家和客户的客服中心项目为例,我们的应用程序套件包含我们内部构建的应用程序和第三方工具,这些工具之一是 Twilio TaskRouter。

TaskRouter 是一个基于技能的多通道任务路由 API。它根据我们配置的一组路由规则和代理技能来处理创建任务(语音、聊天等)并将它们路由到最合适的代理。

使用 Terraform 配置基础架构

Terraform 是一个开源工具,用于将基础设施配置为代码。

它是基础架构的状态机,为团队提供了上面列出的工程最佳实践的所有好处,这些好处以前只能通过 GUI 管理。

Terraform 需要三件事才能工作:

  1. 可用的API。 我们需要一个API才能让Terraform工作。使用Terraform时,我们将停止使用 GUI,并依靠Terraform通过API为我们进行更改。无法使用API进行更改的内容,都无法使用 Terraform进行管理。
  2. Go客户端库。 Terraform是用Go编写的,并且需要一个客户端库,用于您使用Go编写的目标 API。 客户端库对您的目标应用程序通过HTTP(S)调用。
  3. Terraform提供者。 Terraform核心功能使用程序提供者与目标API进行交互。程序提供者用Go编写并且使用了Terraform的插件SDK。

有了这三个部分,您几乎可以使用 Terraform 管理任何应用程序!

Terraform 提供者添加了一组 Terraform 可以管理的资源。 提供者不是 Terraform 代码的一部分。 它们是单独创建的以管理特定的应用程序。 当我们开始这个项目时,Twilio 没有提供者,所以我们自己做了。

自启动此项目以来,Twilio开发了自己的 Terraform 提供程序,可以在此处找到。

核心功能中,提供者使 Terraform 能够对一组资源执行 CRUD 操作。 有了提供者,Terraform 可以管理应用程序的状态。

创建提供者

以下是 Terraform 提供者的基本结构:

├── Makefile
├── examples
├── go.mod
├── go.sum
├── main.go
├── scripts
├── twilio

│   ├── helpers.go
│   ├── helpers_test.go
│   ├── provider.go
│   ├── provider_test.go
│   ├── resource_twilio_activity.go
│   ├── resource_twilio_activity_test.go
└── vendor

此文件夹结构包含Go依赖项、用于运行命令的 Makefile、用于本地开发的示例文件和名为 twilio 的目录。

提供者必须包含您要管理的每种资源文件。 每个资源文件都包含一组供 Terraform 遵循的 CRUD 指令——基本上是在告诉 Terraform 如何管理此资源。

这是定义活动资源的函数:

func resourceTwilioActivity() *schema.Resource {
	return &schema.Resource{
		CreateContext: resourceActivityCreate,
		ReadContext:   resourceActivityRead,
		UpdateContext: resourceActivityUpdate,
		DeleteContext: resourceActivityDelete,
		Importer: &schema.ResourceImporter{
			State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {

				attribute1, attribute2, err := ParseWorkspaceResourceID(d.Id())

				if err != nil {
					return nil, err
				}

				setErr := d.Set("workspace_sid", attribute1)
				d.SetId(attribute2)

				if setErr != nil {
					return nil, setErr
				}

				return []*schema.ResourceData{d}, nil
			},
		},
		Schema: map[string]*schema.Schema{
			"friendly_name": {
				Type:     schema.TypeString,
				Required: true,
			},
			"available": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"workspace_sid": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
		},
	}
}

该文件定义了 Terraform 在任务路由器中创建、读取、更新和销毁活动所需的操作。 这些操作中的每一个都由同一文件中的一个函数定义。

该文件还定义了一个 Importer 函数,这是一种允许 Terraform 导入现有基础设施的特殊类型的函数。 如果您已经运行了基础架构并想开始使用 Terraform 来管理它,这将非常方便。

最后,该函数定义了一个模式——这些是 API 提供的用于执行 CRUD 操作的参数。 对于任务路由器活动,参数是friendly_name、available 和workspace_sid。

为了完善这个例子,让我们看看我们编写的 create 函数:

func resourceActivityCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	var diags diag.Diagnostics

	twilioClient := m.(*twilio.Client)
	workspace := twilioClient.TaskRouter.Workspace(
		d.Get("workspace_sid").(string),
	)

	params := map[string]string{
		"FriendlyName": d.Get("friendly_name").(string),
		"Available":    d.Get("available").(string),
	}

	result, err := workspace.Activities.Create(context.TODO(), GenerateParams(params))

	if err != nil {
		diags = append(diags, diag.Diagnostic{
			Severity: diag.Error,
			Summary:  "Failed to create activity",
			Detail:   err.Error(),
		})
	}

	d.SetId(result.Sid)

	return diags
}

该函数传递上下文、模式资源和空接口。

我们实例化 Twilio API 客户端并找到我们的工作区(任务路由器活动都存在于单个工作区下)。

然后我们格式化我们的参数(在我们的 Schema 中的 resourceTwilioActivity 函数中定义)并将它们传递给我们的 API 客户端库提供给我们的 create 方法。

因为这个函数创建了一个新资源,所以我们将 id (setID) 设置为 API 调用结果的 sid。 在 Twilio 中,sid 是资源的唯一标识符。 现在,Terraform 知道新创建的资源及其唯一标识符,这意味着它可以对资源进行更改。

使用 Terraform

创建提供程序或管理已具有提供程序的应用程序后,您就可以开始使用Terraform。

Terraform 使用 DSL 来管理资源。 好消息是,这个 DSL 比支持提供者的 Go 代码更简单。

DSL 很简单,通过一些指导,非开发人员应该能够安全地更改您的基础架构——但稍后会详细介绍。

以下是定义新任务路由器活动的代码:

resource "twilio_activity" "offline" {
  friendly_name = "Offline"
  available     = false
  workspace_sid = twilio_workspace.beta.id

  depends_on = [twilio_workspace.beta]
}

我们创建一个块来声明资源类型以及我们想要调用它的内容。 在该块中,我们传递在 resourceTwilioActivity 的 Schema 块中定义的变量,以及它所依赖的任何资源。 在这种情况下,活动需要存在于工作空间中。 所以我们在depends_on数组中传入工作空间资源。 在尝试创建活动之前,Terraform 知道它需要此资源存在或创建它。

现在您已经定义了资源,您就可以开始看到 Terraform 的好处了。

Terraform 有一些命令,但 plan 和 apply 是最常见的。

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:

  # twilio_activity.offline_test will be created

+ resource "twilio_activity" "offline" {
      + available     = "false"
      + friendly_name = "Offline"
      + id            = (known after apply)
      + workspace_sid = "WSXXXXXXX"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Terraform 使可视化对基础架构的更改变得非常容易。在此计划步骤中,您可能会发现意外更改 - 如果已经有离线活动,计划步骤将向您显示更新而不是创建。在这一步,您需要做的就是更改资源块的名称,然后再次运行 terraform plan。

当您对更改感到满意时,运行 terraform apply 以对您的基础架构进行更改。现在,Terraform 将了解新创建的资源及其生成的 id,从而允许您通过 Terraform 继续对其进行专门管理。

为了充分利用 Terraform,我们使用了一个名为 Atlantis 的附加工具来管理我们的 GitHub 集成。

这允许人们通过更改资源文件来提出拉取请求,并让 Atlantis 使用 terraform plan 的输出向 PR 添加评论。审核过程完成后,我们会评论 atlantis apply -p terraform 以进行更改。然后合并 PR。

从使用 Web 应用程序中的 GUI 管理我们的基础设施,我们已经走了很长一段路!我们有一个 Terraform 提供者通过 Go API 客户端进行通信,以将我们的基础设施作为代码进行管理。将 Atlantis 插入我们团队的 GitHub 后,我们现在拥有了许多我们在编写软件时所依赖的最佳实践——可审查的 PR,这些 PR 易于理解并在必要时回滚,具有清晰的历史记录,可以使用 git blame 进行扫描。

Terraform 是如何被其他团队接收的?

这个项目最有价值的部分是它是如何被其他团队接受的。无需业务和支持团队提出请求并等待开发人员更改 Twilio 工作流程,Terraform 授权他们自己做。事实上,有些人的第一个 PR 是对我们 Terraform 基础设施的更改!

除了释放开发人员的时间并使业务团队更加独立外,Terraform 还提供了对基础设施随时间变化的可见性。 Terraform 显示了更改的影响,并且可以轻松地在 GitHub 上搜索以前的更改,这使我们可以轻松了解我们团队所做更改的历史。

构建出色的工具通常需要维护第三方基础架构。就我的团队而言,这意味着管理 Twilio TaskRouter 以正确路由任务以支持代理。

随着团队需求的增长,您配置基础架构的方式也可能会发生变化。跟踪这些变化并有信心做出这些变化非常重要,但也很困难。

Terraform 使这些更改更具可预测性,并使开发人员和非开发人员在进行这些更改时都能够使用软件工程最佳实践。