如何使用 GitOps 自动化 Terraform

尝试使用 Flux 和 GitOps 这种替代方法来自动化 Terraform,而不是使用 CI/CD 管道或 Terraform Cloud。
3 位读者喜欢此文。
Tips and gears turning

opensource.com

GitOps 作为一种工作流程,非常适合应用程序交付,主要用于 Kubernetes 环境,但也可以用于基础设施。在一个典型的 GitOps 场景中,您可能希望考虑像 Crossplane 这样 Kubernetes 原生的替代方案,而大多数传统基础设施仍然使用 CI/CD 管道。使用 Kubernetes 作为基础创建部署平台有几个好处,但也意味着更多的人需要具备特定的技能。像 Terraform 这样的基础设施即代码工具的优势之一是它易于学习,并且不需要太多的专业知识。

当我的团队构建我们的平台服务时,我们希望每个人都能做出贡献。我们的大部分工程师(如果不是全部的话)每天都在使用 Terraform。他们知道如何创建可以在多种场景和为多个客户使用的 Terraform 模块。虽然有很多自动化 Terraform 的方法,但我们希望尽可能地利用合适的 GitOps 工作流程。

Terraform 控制器如何工作

在寻找使用 Kubernetes 运行 Terraform 的替代方案时,我发现了几种控制器和运算符,但没有一种让我觉得像 Weaveworks 的 tf-controller 那样具有潜力。我们已经在使用 Flux 作为我们的 GitOps 工具。tf-controller 通过利用 Flux 的一些核心功能工作,并且具有 Terraform 部署的自定义资源。源控制器负责获取我们的模块,Kustomize 控制器应用 Terraform 资源,然后控制器启动静态 Pod(称为 runner)来运行您的 Terraform 命令。

Terraform 资源看起来像这样

apiVersion: infra.contrib.fluxcd.io/v1alpha1
kind: Terraform
metadata:
  name: helloworld
  namespace: flux-system
spec:
  interval: 1m
  approvePlan: auto
  path: ./terraform/module
  sourceRef:
    kind: GitRepository
    name: helloworld
    namespace: flux-system

这里有一些关于规范需要注意的地方。spec 中的 interval 控制控制器启动 runner pod 的频率。然后,它对您的根模块执行 terraform plan,该根模块由 path 参数定义。

这个特定的资源被设置为自动批准计划。这意味着如果计划与目标系统的当前状态之间存在差异,则会运行一个新的 runner 来自动应用更改。这使得流程尽可能地“GitOps”,但是您可以禁用此功能。如果禁用它,您必须手动批准计划。您可以使用 Terraform Controller CLI 或通过使用对应该应用的提交的引用更新您的清单来做到这一点。有关更多详细信息,请参阅有关手动批准的文档

tf-controller 利用 Flux 的源控制器。sourceRef 属性用于定义您想要使用的源资源,就像 Flux Kustomization 资源一样。

高级部署

虽然上面的示例有效,但它不是我的团队通常会做的部署类型。当不定义后端存储时,状态将存储在集群中,这对于测试和开发来说是可以的。但是对于生产,我更喜欢将状态文件存储在集群外部的某个地方。我不希望直接在根模块中定义这一点,因为我想在多个部署中重用我们的根模块。这意味着我必须在我们的 Terraform 资源中定义我们的后端。

这是一个我如何设置自定义后端配置的例子。您可以在 Terraform 文档中找到所有可用的后端

apiVersion: infra.contrib.fluxcd.io/v1alpha1
kind: Terraform
metadata:
  name: helloworld
  namespace: flux-system
spec:
  backendConfig:
	customConfiguration: |
		backend "azurerm" {
		  resource_group_name  = "rg-terraform-mgmt"
		  storage_account_name = "stgextfstate"
		  container_name       = "tfstate"
		  key                  = "helloworld.tfstate"
		}
  ...

将状态文件存储在集群之外意味着我可以重新部署我们的集群。但是这样就没有存储依赖性。不需要备份或状态迁移。一旦新的集群启动,它就会针对相同的状态运行命令,并且我恢复了正常运行。

另一个高级举措是模块之间的依赖关系。有时我们将部署设计为像一个两级火箭,一个部署设置某些资源,下一个部署使用这些资源。在这些场景中,我们需要确保我们的 Terraform 以这样一种方式编写,以便我们输出作为第二个模块输入所需的任何数据,并确保第一个模块首先成功运行。

这两个例子来自演示依赖关系时使用的代码,所有的代码都可以在我的 GitHub 上找到。为了简洁起见,省略了一些代码

apiVersion: infra.contrib.fluxcd.io/v1alpha1
kind: Terraform
metadata:
  name: shared-resources
  namespace: flux-system
spec:
  ...
  writeOutputsToSecret:
    name: shared-resources-output
  ...
apiVersion: infra.contrib.fluxcd.io/v1alpha1
kind: Terraform
metadata:
  name: workload01
  namespace: flux-system
spec:
  ...
  dependsOn:
    - name: shared-resources
	...
  varsFrom:
    - kind: Secret
      name: shared-resources-output
  ...

在我称之为 shared-resources 的部署中,您会看到我定义了一个 secret,用于存储部署的输出。在本例中,输出如下

output "subnet_id" {
  value = azurerm_virtual_network.base.subnet.*.id[0]
}

output "resource_group_name" {
  value = azurerm_resource_group.base.name
}

workload01 部署中,我首先使用 dependsOn 属性定义我们的依赖关系,这确保了 shared-resources 在调度 workload01 之前成功运行。来自 shared-resources 的输出然后被用作 workload01 的输入,这就是我想等待的原因。

为什么不使用管道或 Terraform Cloud

自动化 Terraform 最常见的方法是使用 CI/CD 管道或 Terraform Cloud。使用管道进行 Terraform 工作得很好,但通常最终需要一遍又一遍地复制管道定义。有一些解决方案可以解决这个问题,但是通过使用 tf-controller,您可以采用更具声明性的方法来定义您希望部署看起来是什么样子,而不是以命令式的方式定义步骤。

Terraform Cloud 已经引入了许多与使用 GitOps 工作流程重叠的功能,但是使用 tf-controller 不会阻止您使用 Terraform Cloud。您可以将 Terraform Cloud 用作部署的后端,只通过 tf-controller 自动化运行。

我的团队使用这种方法的原因是,我们已经使用 GitOps 部署应用程序,并且我们可以更灵活地提供这些功能作为服务。我们可以通过 API 控制我们的实现,使我们的运营商和最终用户更容易访问自助服务。关于我们的平台方法的细节是一个很大的话题,我们将在另一篇文章中再讨论这些。


本文最初发表在作者的博客上,并已获得许可转载。

Roberth Strand, standing in front of a window, wearing a Kubernetes hoodie and his company t-shirt
我在 Amesto Fortytwo 构建可扩展的平台,这是您友好的社区平台工程商店。除了工作,我还积极参与 CNCF,更具体地说是在 TAG App Delivery 中。我在那里担任平台工作组的联合主席和 OpenGitOps 项目的维护者。

评论已关闭。

Creative Commons License本作品采用 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.