Terraform (Hashicorp) 的不同环境
Posted
技术标签:
【中文标题】Terraform (Hashicorp) 的不同环境【英文标题】:Different environments for Terraform (Hashicorp) 【发布时间】:2016-08-28 13:37:33 【问题描述】:我一直在使用 Terraform 来构建我的 AWS 堆栈并且一直很喜欢它。如果要在商业环境中使用,则需要将配置重新用于不同的环境(例如 QA、STAGING、PROD)。
我怎样才能做到这一点?我是否需要创建一个包装脚本来调用 terraform 的 cli,同时在每个环境中传递不同的状态文件,如下所示?我想知道 Terraform 是否提供了更原生的解决方案。
terraform apply -state=qa.tfstate
【问题讨论】:
github.com/unfor19/terraform-multienv - 因为已经有一个公认的答案,而且这个问题已经很老了,所以我在这里添加它作为评论。我创建了一个模板,用于使用 Terraform 维护多环境基础架构。此模板包含一个 CI/CD 流程,该流程在 AWS 账户中应用基础设施。 github.com/unfor19/terraform-multienv 现在应该是答案了! 【参考方案1】:无需制作包装脚本。我们所做的是将我们的 env 拆分为一个模块,然后有一个*** terraform 文件,我们只需为每个环境导入该模块。只要您的模块设置有足够的变量,通常是 env_name 和其他一些变量,就可以了。举个例子
# project/main.tf
module "dev"
source "./env"
env = "dev"
aws_ssh_keyname = "dev_ssh"
module "stage"
source "./env"
env = "stage"
aws_ssh_keyname = "stage_ssh"
# Then in project/env/main.tf
# All the resources would be defined in here
# along with variables for env and aws_ssh_keyname, etc.
编辑 2020/03/01
这个答案在这一点上已经很老了,但值得更新。对 dev 和 stage 共享同一状态文件的批评是错误的,这是一个观点问题。对于上面提供的确切代码,它完全有效,因为 dev 和 stage 也共享相同的代码。因此,“破坏开发会破坏你的舞台”是正确的。写这个答案时我没有注意到的关键是source "./env"
也可以写成source "git::https://example.com/network.git//modules/vpc?ref=v1.2.0"
这样做会使您的整个 repo 成为 TF 脚本的子模块,允许您将一个分支拆分为您的 QA 分支,然后将引用标记为您的生产环境。这避免了通过更改开发来破坏您的登台环境的问题。
下一个状态文件共享。我说这是一个视角问题,因为只需一次运行就可以更新所有环境。在一家小公司中,在促进更改时节省时间可能很有用,如果你小心的话,--target
的一些技巧通常足以加快进程,如果这甚至真的需要的话。我们发现从一个地方和一次 terraform 运行管理所有内容更不容易出错,而不是在不同环境中应用可能略有不同的多种不同配置。将它们全部放在一个状态文件中迫使我们对真正需要成为变量的东西更加自律。就我们的目的而言,这只是矫枉过正。它还非常强烈地阻止了我们让我们的环境彼此相距太远。当您 terraform plan
输出显示 2k 行时,差异主要是因为 dev 和 stage 看起来不像 prod,仅是挫折因素就鼓励我们的团队恢复理智。
一个非常有力的反驳论据是,如果您在一家大型公司,各种合规规则阻止您同时接触开发/阶段/产品。在这种情况下,最好拆分您的状态文件,只需确保 如何 您正在运行 terraform apply
是脚本化的。否则,当有人说“哦,我只需要 --target
就在暂存中的这一件事。我们将在下一个 sprint 中修复它时,你将面临非常现实的风险。承诺。”现在我已经多次看到这种螺旋式上升,这使得环境之间的任何类型的比较充其量都是值得怀疑的。
【讨论】:
有趣。你如何保持 .tfstate 文件分开?您是否将环境特定模块保存在单独的文件夹中,并将关联的 .tfstate 文件保存在其中? 不需要,整个 aws 帐户的一个 tfstate 文件 :) 如果您将模块签入 git,那么您可以为 env 设置版本和分支。只需确保在每次部署到非开发环境之前进行 terraform 获取。 你的意思是terraform refresh
?
如果您的source
不是文件路径,则必须使用terraform get
来更新这些模块。在第一个计划中,如果您还没有这样做,它甚至会抱怨基于文件路径的模块。
因此,此解决方案旨在为您尝试控制的每个 AWS 账户提供一个 tfstate 文件。您从*** main.tf
文件调用的每个 module
都定义了它自己的 VPC 以及位于该 VPC 中的全套机器。 tfstate
文件不关心其中有多少对象,每个 VPC 仍然与其他每个 VPC 分开。 terraform apply
因此将同时将更新应用到所有环境(因为它们在同一个 AWS 账户中),这使得整体管理更加容易。【参考方案2】:
随着您扩大 terraform 使用量,您将需要共享状态(在开发人员、构建流程和不同项目之间),支持多个环境和区域。 为此,您需要使用远程状态。 在执行 terraform 之前,您需要设置您的状态。 (我正在使用 powershell)
$environment="devtestexample"
$region ="eu-west-1"
$remote_state_bucket = "$environment-terraform-state"
$bucket_key = "yoursharedobject.$region.tfstate"
aws s3 ls "s3://$remote_state_bucket"|out-null
if ($lastexitcode)
aws s3 mb "s3://$remote_state_bucket"
terraform remote config -backend S3 -backend-config="bucket=$remote_state_bucket" -backend-config="key=$bucket_key" -backend-config="region=$region"
#(see here: https://www.terraform.io/docs/commands/remote-config.html)
terraform apply -var='environment=$environment' -var='region=$region'
您的状态现在按区域、按环境存储在 S3 中,然后您可以在其他 tf 项目中访问此状态。
【讨论】:
现在有更好的方法可以做到这一点,作为第一步,现在首先创建一个没有远程状态的存储桶,然后添加远程,这样您至少可以管理存储桶使用您的 TF 流程,并且不会弄乱 cli。【参考方案3】:我建议你看看hashicorp best-practices repo,它有一个很好的设置来处理不同的环境(类似于 James Woolfenden 的建议)。
我们使用了类似的设置,而且效果很好。但是,这个最佳实践回购假设您使用的是 Atlas,而我们不是。我们创建了一个相当精细的Rakefile
,它基本上(再次通过最佳实践回购)获取/terraform/providers/aws
的所有子文件夹,并使用命名空间将它们公开为不同的构建。所以我们的rake -T
输出会列出以下任务:
us_east_1_prod:init
us_east_1_prod:plan
us_east_1_prod:apply
us_east_1_staging:init
us_east_1_staging:plan
us_east_1_staging:apply
这种分离可以防止可能专属于 dev 的更改意外影响(或更糟的是,破坏)prod 中的某些内容,因为它是不同的状态文件。它还允许在实际应用到产品之前测试开发/暂存中的更改。
另外,我最近偶然发现了这篇小文章,它基本上展示了如果你把所有东西放在一起会发生什么: https://charity.wtf/2016/03/30/terraform-vpc-and-why-you-want-a-tfstate-file-per-env/
【讨论】:
我觉得这对很多人来说可能是一个更好的答案。 为每个环境保留一个单独的 tfstate 绝对是一个最佳实践 - 从 Terraform 0.10 开始,Workspaces 功能在每个“工作空间”(即环境)中都可以做到这一点 工作区在某些情况下不是合适的隔离机制。请查看:“何时使用多个工作区”terraform.io/docs/state/workspaces.html 看起来最佳实践回购现在已弃用 一些团队也对 terragrunt 工具感到满意,该工具对 terraform 文件(而不是 Rake)有一些“魔力”。【参考方案4】:Paul 的模块解决方案是正确的想法。但是,我强烈建议反对在同一个 Terraform 文件中定义所有环境(例如 QA、登台、生产)。如果你这样做了,那么每当你对 staging 进行更改时,你也会冒着意外破坏生产的风险,这在一定程度上违背了最初保持这些环境隔离的意义!请参阅 Terraform, VPC, and why you want a tfstate file per env,了解可能出现问题的丰富多彩的讨论。
我始终建议将每个环境的 Terraform 代码存储在单独的文件夹中。事实上,您甚至可能希望将每个“组件”(例如数据库、VPC、单个应用程序)的 Terraform 代码存储在单独的文件夹中。同样,原因是隔离:当对单个应用程序进行更改时(您可能每天执行 10 次),您不希望将整个 VPC 置于风险之中(您可能永远不会更改)。
因此,我的典型文件布局如下所示:
stage
└ vpc
└ main.tf
└ vars.tf
└ outputs.tf
└ app
└ db
prod
└ vpc
└ app
└ db
global
└ s3
└ iam
staging 环境的所有 Terraform 代码都进入 stage
文件夹,prod 环境的所有代码都进入 prod
文件夹,以及位于环境之外的所有代码(例如 IAM 用户、S3 buckets) 进入global
文件夹。
欲了解更多信息,请查看How to manage Terraform state。如需深入了解 Terraform 最佳实践,请查看 Terraform: Up & Running 一书。
【讨论】:
您是否尝试通过使用模块来增强此解决方案以摆脱单独的文件夹 - 单独的状态问题? @aholbreich 是的,为了避免在每个环境之间复制/粘贴相同的代码,我们使用此处描述的模块:How to create reusable infrastructure with Terraform modules。 @YevgeniyBrikman 对于从每个环境中删除 90% 的代码重复有什么建议吗?我的每个人都非常相似,并且更改所有内容都容易出错。提前致谢。 @MikeD,我想说,你必须尽可能地模块化你的代码以减少此类错误【参考方案5】:请注意,从 0.10.0 版本开始,Terraform 现在支持工作区的概念(0.9.x 中的环境)。
工作空间是 Terraform 状态的命名容器。对于多个工作区,一个 Terraform 配置目录可用于管理多个不同的基础设施资源集。
在此处查看更多信息:https://www.terraform.io/docs/state/workspaces.html
【讨论】:
明确指出 Workspaces 不是解决这个问题的好方法 - terraform.io/docs/state/…【参考方案6】:这个帖子里有很多很好的答案。让我也贡献一个对我和其他一些团队有用的想法。
我们的想法是创建一个包含整个基础架构代码的“伞形”项目。
每个环境的 terraform 文件仅包含一个模块 - “主”。
那么“main”将包括资源和其他模块
- terraform_project
- env
- dev01 <-- Terraform home, run from here
- .terraform <-- git ignored of course
- dev01.tf <-- backend, env config, includes always _only_ the main module
- dev02
- .terraform
- dev02.tf
- stg01
- .terraform
- stg01.tf
- prd01
- .terraform
- prd01.tf
- main <-- main umbrella module
- main.tf
- variables.tf
- modules <-- submodules of the main module
- module_a
- module_b
- module_c
一个示例环境主文件(例如 dev01.tf)将如下所示
provider "azurerm"
version = "~>1.42.0"
terraform
backend "azurerm"
resource_group_name = "tradelens-host-rg"
storage_account_name = "stterraformstate001"
container_name = "terraformstate"
key = "dev.terraform.terraformstate"
module "main"
source = "../../main"
subscription_id = "000000000-0000-0000-0000-00000000000"
project_name = "tlens"
environment_name = "dev"
resource_group_name = "tradelens-main-dev"
tenant_id = "790fd69f-41a3-4b51-8a42-685767c7d8zz"
location = "West Europe"
developers_object_id = "58968a05-dc52-4b69-a7df-ff99f01e12zz"
terraform_sp_app_id = "8afb2166-9168-4919-ba27-6f3f9dfad3ff"
kubernetes_version = "1.14.8"
kuberenetes_vm_size = "Standard_B2ms"
kuberenetes_nodes_count = 4
enable_ddos_protection = false
enable_waf = false
谢谢你:
可以为每个环境的 Terraform 远程状态文件提供单独的后端 能够为不同的环境使用不同的系统帐户 能够在每个环境中使用不同版本的提供程序和 Terraform 本身(并逐个升级) 确保为每个环境提供所有必需的属性(如果缺少环境属性,Terraform 验证将不会通过) 确保始终将所有资源/模块添加到所有环境中。因为只有一个模块,所以不可能“忘记”整个模块。Check a source blog post
【讨论】:
【参考方案7】:Form terraform version 0.10+ 有一种方法可以使用 Workspace 命令来维护状态文件
$ terraform workspace list // The command will list all existing workspaces
$ terraform workspace new <workspace_name> // The command will create a workspace
$ terraform workspace select <workspace_name> // The command select a workspace
$ terraform workspace delete <workspace_name> // The command delete a workspace
您需要做的第一件事是为您的环境创建每个工作区
$ terraform workspace new dev
创建并切换到工作区“dev”!
您现在位于一个新的空工作区。工作区隔离它们的状态, 因此,如果您运行“terraform plan”,Terraform 将看不到任何现有状态 对于这个配置。
$terraform workspace new test
创建并切换到工作区“测试”!
您现在位于一个新的空工作区。工作区隔离它们的状态, 因此,如果您运行“terraform plan”,Terraform 将看不到任何现有状态 对于这个配置。
$terraform workspace new stage
创建并切换到工作区“舞台”!
您现在位于一个新的空工作区。工作区隔离它们的状态, 因此,如果您运行“terraform plan”,Terraform 将看不到任何现有状态 对于这个配置。
将创建后端 terraform.tfstate.d 目录
在它们下面你可以看到 3 个目录 - dev、test、stage,每个目录都会在其工作空间下维护其状态文件。
现在您需要做的就是将环境变量文件移动到另一个文件夹中
每次执行 terraform plan , terraform apply 只保留一个变量文件
main.tf
dev_variable.tfvar
output.tf
记得切换到正确的工作区使用正确的环境状态文件
$terraform workspace select test
main.tf
test_variable.tfvar
output.tf
参考:https://dzone.com/articles/manage-multiple-environments-with-terraform-worksp
【讨论】:
【参考方案8】:绝对没有必要为开发环境和生产环境提供单独的代码库。最佳实践规定(我的意思是 DRY)实际上您最好拥有一个代码库并简单地对其进行参数化,就像您在开发软件时通常所做的那样 - 您没有单独的文件夹用于开发版本应用程序和应用程序的生产版本。您只需要确保正确的部署方案。 terraform 也是如此。考虑一下这个“Hello world”的想法:
terraform-project
├── etc
│ ├── backend
│ │ ├── dev.conf
│ │ └── prod.conf
│ └── tfvars
│ ├── dev.tfvars
│ └── prod.tfvars
└── src
└── main.tf
etc/backend/dev.conf 的内容
storage_account_name = "tfremotestates"
container_name = "tf-state.dev"
key = "terraform.tfstate"
access_key = "****"
etc/backend/prod.conf 的内容
storage_account_name = "tfremotestates"
container_name = "tf-state.prod"
key = "terraform.tfstate"
access_key = "****"
etc/tfvars/dev.tfvars 的内容
environment = "dev"
etc/tfvars/prod.tfvars 的内容
environment = "prod"
src/main.tf 的内容
terraform
backend "azurerm"
provider "azurerm"
version = "~> 2.56.0"
features
resource "azurerm_resource_group" "rg"
name = "rg-$var.environment"
location = "us-east"
现在您只需将适当的值传递给 cli 调用,例如:
export ENVIRONMENT=dev
terraform init -backend-config=etc/backends/$ENVIRONMENT.conf
terraform apply -vars-file=etc/tfvars/$ENVIRONMENT.tfvars
这边:
我们为每个环境都有单独的状态文件(因此它们甚至可以部署在不同的订阅/帐户中) 我们拥有相同的代码库,因此我们确信 dev 和 prod 之间的差异很小,我们可以在上线之前依赖 dev 进行测试 我们遵循 DRY 指令 我们遵循 KISS 指令 无需使用晦涩的“工作区”界面!当然,为了完全安全,您应该结合某种 git 流程和代码审查,也许是一些静态或集成测试、自动部署过程等。但我认为这个解决方案是最好的方法在不重复代码的情况下拥有多个 terraform 环境,并且它对我们来说非常好用了几年。
【讨论】:
【参考方案9】:Hashicorp 推荐不同的状态文件和文件夹,如下所示:
├── assets
│ ├── index.html
├── prod
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfstate
│ └── terraform.tfvars
└── dev
├── main.tf
├── variables.tf
├── terraform.tfstate
└── terraform.tfvars
甚至还有关于如何根据最佳实践重构单体配置以支持多种环境的文档。在这里查看: https://learn.hashicorp.com/tutorials/terraform/organize-configuration#variables-tf
【讨论】:
以上是关于Terraform (Hashicorp) 的不同环境的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Hashicorp Terraform 中配置环境变量
京东云携手HashiCorp,宣布推出Terraform Provider
json HashiCorp Terraform AWS和DOCKER正在运行
微软出品HashiCorp Terraform 和 Vault 系列视频