Terraform(Hashicorp)的不同环境

时间:2016-05-03 13:18:11

标签: terraform

我一直在使用Terraform来构建我的AWS栈,并且一直很喜欢它。如果要在商业环境中使用,则需要针对不同环境(例如QA,STAGING,PROD)重用配置。

我怎样才能做到这一点?我是否需要创建一个包装器脚本来调用terraform的cli,同时按照下面的环境传入不同的状态文件?我想知道Terraform是否提供了更原生的解决方案。

terraform apply -state=qa.tfstate

8 个答案:

答案 0 :(得分:17)

我建议你看看hashicorp best-practices repo,它有一个非常好的设置来处理不同的环境(类似于James Woolfenden所建议的)。

我们正在使用类似的设置,它运行得非常好。但是,这个最佳实践回购假设你使用的是Atlas,我们不是。我们创建了一个非常精细的Rakefile,它基本上(再次通过最佳实践的repo)获取/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中的某些东西,因为它是一个不同的状态文件。它还允许在实际应用于prod之前测试dev / staging的变化。

另外,我最近偶然发现了这个小小的写作,它基本上显示了如果你把所有东西放在一起会发生什么: https://charity.wtf/2016/03/30/terraform-vpc-and-why-you-want-a-tfstate-file-per-env/

答案 1 :(得分:13)

Paul的模块解决方案是正确的想法。但是,我强烈建议反对在同一个Terraform文件中定义所有环境(例如QA,登台,制作)。如果你这样做,那么每当你改变分期时,你都会冒着意外破坏生产的风险,这部分地阻碍了首先将这些环境隔离开来!有关可能出现问题的详细讨论,请参阅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

登台环境的所有Terraform代码都进入stage文件夹,prod环境的所有代码都进入prod文件夹,以及生活在环境之外的所有代码(例如IAM用户,S3存储桶)进入global文件夹。

有关详细信息,请查看How to manage Terraform state。要深入了解Terraform最佳做法,请查看书籍 Terraform: Up & Running

答案 2 :(得分:13)

请注意,从版本0.10.0开始,Terraform支持Workspaces的概念(0.9.x中的环境)。

工作空间是Terraform状态的命名容器。通过多个工作区,Terraform配置的单个目录可用于管理多个不同的基础架构资源集。

在此处查看更多信息:https://www.terraform.io/docs/state/workspaces.html

答案 3 :(得分:6)

在扩展您的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项目中访问此状态。

答案 4 :(得分:2)

无需制作包装脚本。我们所做的是将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.

答案 5 :(得分:1)

Form Terraform版本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计划时仅保留一个变量文件,适用terraform

   main.tf
   dev_variable.tfvar
   output.tf

记住要切换到正确的工作空间以使用正确的环境状态文件

   $terraform workspace select test
   main.tf
   test_variable.tfvar
   output.tf

Ref:https://dzone.com/articles/manage-multiple-environments-with-terraform-worksp

答案 6 :(得分:1)

绝对不需要为开发和生产环境设置单独的代码库。最佳实践规定(我的意思是 DRY)实际上您最好拥有一个代码库并简单地对其进行参数化,就像您在开发软件时通常所做的那样 - 您没有单独的开发文件夹应用程序的版本和应用程序的生产版本。您只需要确保正确的部署方案。地形也是如此。考虑这个“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 环境而无需重复代码,几年来它对我们来说非常有效。

答案 7 :(得分:0)

此主题中有很多不错的答案。让我也为对我和其他团队有用的想法做出贡献。

这个想法是有一个包含整个基础结构代码的“ umbrella”项目。

每个环境的terraform文件仅包含一个模块-“主”模块。

然后“主要”将包括资源和其他模块

- 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