我一直在使用 Terraform 来构建我的 AWS 堆栈并且一直很喜欢它。如果要在商业环境中使用,则需要将配置重新用于不同的环境(例如 QA、STAGING、PROD)。
我怎么能做到这一点?我是否需要创建一个包装脚本来调用 terraform 的 cli,同时在每个环境中传递不同的状态文件,如下所示?我想知道 Terraform 是否提供了更原生的解决方案。
terraform apply -state=qa.tfstate
我一直在使用 Terraform 来构建我的 AWS 堆栈并且一直很喜欢它。如果要在商业环境中使用,则需要将配置重新用于不同的环境(例如 QA、STAGING、PROD)。
我怎么能做到这一点?我是否需要创建一个包装脚本来调用 terraform 的 cli,同时在每个环境中传递不同的状态文件,如下所示?我想知道 Terraform 是否提供了更原生的解决方案。
terraform apply -state=qa.tfstate
我建议你看看hashcorp 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
这种分离可以防止可能专属于开发的更改意外影响(或更糟的是,破坏)产品中的某些内容,因为它是不同的状态文件。它还允许在实际将其应用于产品之前测试开发/登台的更改。
此外,我最近偶然发现了这篇小文章,它基本上显示了如果你把所有东西放在一起会发生什么: https ://charity.wtf/2016/03/30/terraform-vpc-and-why-you-want-a -tfstate-file-per-env/
Paul 的模块解决方案是正确的想法。但是,我强烈建议不要在同一个 Terraform 文件中定义所有环境(例如 QA、登台、生产)。如果你这样做了,那么每当你对 staging 进行更改时,你也会冒着意外破坏生产的风险,这在一定程度上违背了最初保持这些环境隔离的意义!请参阅Terraform、VPC 以及为什么要为每个环境创建一个 tfstate 文件,以对可能出现的问题进行丰富多彩的讨论。
我始终建议将每个环境的 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 存储桶)都进入该global
文件夹。
有关更多信息,请查看如何管理 Terraform 状态。要更深入地了解 Terraform 最佳实践,请查看Terraform: Up & Running一书。
请注意,从 0.10.0 版本开始,现在 Terraform 支持工作区的概念(0.9.x 中的环境)。
工作空间是 Terraform 状态的命名容器。对于多个工作区,一个 Terraform 配置目录可用于管理多个不同的基础设施资源集。
在此处查看更多信息:https ://www.terraform.io/docs/state/workspaces.html
随着您扩大 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 项目中访问此状态。
无需制作包装脚本。我们所做的是将我们的 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 分支,然后将引用标记为您的生产环境。这避免了通过更改 dev 来破坏您的登台环境的问题。
下一个状态文件共享。我说这是一个视角问题,因为只需一次运行就可以更新所有环境。在一家小公司中,在促进变更时节省时间可能是有用的,--target
如果你小心的话,一些技巧通常足以加快进程,如果那是真的需要的话。我们发现从一个地方和一次 terraform 运行管理所有内容更不容易出错,而不是在不同环境中应用可能略有不同的多种不同配置。将它们全部放在一个状态文件中迫使我们更加自律地了解真正需要成为变量的内容,而不是出于我们的目的而过度使用的内容。它还非常强烈地阻止了我们让我们的环境彼此相距太远。当你terraform plan
输出显示 2k 行,差异主要是因为 dev 和 stage 看起来一点也不像 prod,仅凭挫折因素就鼓励我们的团队恢复理智。
一个非常有力的反驳论据是,如果你在一家大公司,各种合规规则阻止你同时接触开发/阶段/产品。在这种情况下,最好拆分您的状态文件,只需确保您的运行terraform apply
方式是脚本化的。--target
否则,当有人说“哦,我只需要在暂存中处理这件事。我们将在下一个 sprint 中修复它,保证”时,您将面临这些状态文件分开的真正风险。我现在已经多次看到这种螺旋式上升,这使得环境之间的任何比较充其量都是值得怀疑的。
绝对不需要为 dev 和 prod 环境提供单独的代码库。最佳实践规定(我的意思是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
这边走:
当然,为了完全安全,您应该结合某种 git 流程和代码审查,也许是一些静态或集成测试、自动部署过程等。但我认为这个解决方案是拥有多个无需重复代码的 terraform 环境,它已经为我们很好地工作了几年。
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
在这个线程中有很多很好的答案。让我也贡献一个对我和其他一些团队有用的想法。
这个想法是拥有一个包含整个基础架构代码的单一“伞形”项目。
每个环境的 terraform 文件只包含一个模块 - “main”。
然后“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
}
多亏了你:
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