Terraform:多租户的状态管理
Posted
技术标签:
【中文标题】Terraform:多租户的状态管理【英文标题】:Terraform: state management for multi-tenancy 【发布时间】:2017-08-29 07:34:03 【问题描述】:由于我们正在评估 Terraform 以替代(部分)多租户 SaaS 的 Ansible 配置流程,我们意识到 Terraform 的便利性、性能和可靠性,因为我们可以处理基础架构更改(添加/删除)顺利,跟踪基础设施状态(这很酷)。
我们的应用程序是多租户 SaaS,我们为客户提供单独的实例 - 在 Ansible 中,我们拥有自己的动态清单(与 EC2 动态清单非常相似)。我们浏览了许多 Terraform 书籍/教程和最佳实践,其中许多建议应该在 Terraform 中单独和远程管理多环境状态,但它们都看起来像静态环境(如 Dev/Staging/Prod)。
是否有管理多租户应用的动态状态清单的最佳实践或真实示例?我们希望跟踪每个客户实例集的状态 - 轻松填充对它们的更改。
一种方法可能是我们为每个客户创建一个目录并在其中放置 *.tf 脚本,这将调用托管在全球某处的我们的模块。状态文件可能会被放到 S3 中,这样我们可以在需要时为每个客户填充更改。
【问题讨论】:
【参考方案1】:您建议的方法对我来说听起来不错,但您可能会考虑做更多的事情。
将原始 Terraform 模板(下面树中的_template
)保留为版本化工件(例如 git 存储库),并且只需传递键值属性即可重新创建您的基础架构。这样,您将在目录中放置非常少量的复制粘贴 Terraform 配置代码。
看起来是这样的:
/tf-infra
├── _global
│ └── global
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── terraform.tfvars
│ └── variables.tf
└── staging
└── eu-west-1
├── saas
│ ├── _template
│ │ └── dynamic.tf.tpl
│ ├── customer1
│ │ ├── auto-generated.tf
│ │ └── terraform.tfvars
│ ├── customer2
│ │ ├── auto-generated.tf
│ │ └── terraform.tfvars
...
需要两个帮助脚本:
模板渲染。使用sed
生成module's source attribute或使用更强大的工具(例如在airbnb/streamalert中完成)
包装脚本。运行terraform -var-file=...
通常就足够了。
共享的 terraform 状态文件以及应该是全局的资源(上面的目录 _global
)可以存储在 S3 上,以便其他层可以访问它们。
PS:我非常愿意接受 cmets 提出的解决方案,因为这是一项有趣的工作:)
【讨论】:
【参考方案2】:Terraform 在文件夹级别上工作,拉入所有.tf
文件(默认情况下是terraform.tfvars
文件)。
所以我们做了一些类似于Anton 的answer 的事情,但在使用 sed 进行模板化时消除了一些复杂性。因此,作为一个基本示例,您的结构可能如下所示:
$ tree -a --dirsfirst
.
├── components
│ ├── application.tf
│ ├── common.tf
│ ├── global_component1.tf
│ └── global_component2.tf
├── modules
│ ├── module1
│ ├── module2
│ └── module3
├── production
│ ├── customer1
│ │ ├── application.tf -> ../../components/application.tf
│ │ ├── common.tf -> ../../components/common.tf
│ │ └── terraform.tfvars
│ ├── customer2
│ │ ├── application.tf -> ../../components/application.tf
│ │ ├── common.tf -> ../../components/common.tf
│ │ └── terraform.tfvars
│ └── global
│ ├── common.tf -> ../../components/common.tf
│ ├── global_component1.tf -> ../../components/global_component1.tf
│ ├── global_component2.tf -> ../../components/global_component2.tf
│ └── terraform.tfvars
├── staging
│ ├── customer1
│ │ ├── application.tf -> ../../components/application.tf
│ │ ├── common.tf -> ../../components/common.tf
│ │ └── terraform.tfvars
│ ├── customer2
│ │ ├── application.tf -> ../../components/application.tf
│ │ ├── common.tf -> ../../components/common.tf
│ │ └── terraform.tfvars
│ └── global
│ ├── common.tf -> ../../components/common.tf
│ ├── global_component1.tf -> ../../components/global_component1.tf
│ └── terraform.tfvars
├── apply.sh
├── destroy.sh
├── plan.sh
└── remote.sh
在这里,您从根级别运行您的 plan/apply/destroy,其中包装器 shell 脚本处理诸如 cd'ing 进入目录并运行 terraform get -update=true
但还为文件夹运行 terraform init
等事情,因此您可以获得唯一的状态S3 的文件密钥,允许您独立跟踪每个文件夹的状态。
上述解决方案具有通用模块,这些模块包装资源以提供通用接口(例如,我们的 EC2 实例根据某些输入变量以特定方式标记,并提供私有 Route53 记录),然后是“实现的组件” .
这些组件包含一堆模块/资源,Terraform 将在同一文件夹中应用这些模块/资源。因此,我们可能会在application.tf
下放置一个 ELB、一些应用程序服务器和一个数据库,然后将其符号链接到一个位置,以便我们使用 Terraform 进行控制。如果我们可能在某个位置的资源上有一些差异,那么它们就会被分离出来。在上面的示例中,您可以看到staging/global
有一个global_component2.tf
,它在生产中不存在。这可能只是在非生产环境中应用的东西,例如一些网络控制,以防止互联网访问环境。
真正的好处是,开发人员可以直接在源代码管理中轻松查看所有内容,而不是通过模板步骤生成所需的 Terraform 代码。
它还有助于遵循 DRY,环境之间唯一真正的区别在于位置中的 terraform.tfvars
文件,并且可以更轻松地在将更改生效之前测试更改,因为每个文件夹都与另一个文件夹几乎相同。
【讨论】:
使用这种方法,您将在每个文件夹内或从根目录运行 terraform?我问是因为根据这一点,状态文件可能存储在根路径或每个文件夹中。 您无法从父文件夹运行 Terraform。 Terraform 仅适用于当前目录中的内容。碰巧的是,我们有一些帮助脚本位于 repo 的根目录中,cd
进入我们想要操作的位置,然后从那里运行terraform
CLI 命令。
是的,你可以,我一直这样做...terraform plan path/to/something
但是谢谢。我明白了,使用脚本并在文件夹中执行cd
,我在每个文件夹中得到一个状态文件,这就是我想要的。有一个标志也可以将状态文件放在文件夹中,从根terraform plan path/to/something -state=path/to/something
。
如果我们必须将 Gitlab CI/CD 与 Hashicorp Vault 一起使用,我们将如何实施此解决方案?管道将如何切换上下文?它不会让它变得复杂和一个定时炸弹来搞砸事情吗?此外,如果我们使用 Azure 存储帐户,我们如何确保状态文件不被混淆,并且通过适当的备份/恢复得到适当的保护?以上是关于Terraform:多租户的状态管理的主要内容,如果未能解决你的问题,请参考以下文章
加密存储在远程后端(如 GCS 存储桶)上的 Terraform 状态是不是有用?