如何为多个环境组织 Terraform 模块?

How to organize terraform modules for multiple environments?

网络上的每个 Terraform 指南都提供了部分解决方案,几乎总是不是真实的图片。
我明白了,并不是每个人都有相同的基础设施需求,但让我担心的是常见的情况:

  1. 多个环境(开发、阶段)
  2. 远程后端 (s3)
  3. 一些基本资源(bucket 或 ec2 实例)

未在真实示例项目的任何地方展示。
我正在寻找那个,与此同时,我已经研究并得出结论,除了这些需求我还想要:

  1. 利用模块
  2. 不使用工作区,而是使用不同的目录-每个环境方法
  3. 不要使用 terragrunt 包装器

我当前的结构,不使用模块——只有根模块:

infra/ ------------------------------ 'terraform init', 'terraform apply' inside here*  
     main.tf ------------------------ Sets up aws provider, backend, backend bucket, dynamodb table   
     terraform.tfvars   
     variables.tf ----------------- Holds few variables such as aws_region, project_name...

我想要的结构文件夹树(用于单个存储桶资源的简单开发和暂存模拟)是这样的:

infra/  
     dev/  
        s3/  
            modules.tf ------ References s3 module from local/remote folder with dev inputs   
     stage/  
        s3/  
            modules.tf ------ References s3 module from local/remote folder with stage inputs   

但是我以前的根模块中的文件呢?我还是想有一个remote backend 和以前一样,只是现在我想有两个状态文件(dev.tfstatestage.tfstate) 在同一个后端桶中? backend.tf 文件在每个子目录中的外观如何,它们在哪里?在 s3/ 文件夹或 dev/ 文件夹中?

这有点令人困惑,因为我正在从 根模块 'terraform init' 方法过渡到 特定子目录 'terraform init',我不清楚我是否还应该有一个根模块或另一个文件夹,例如 global/ 我应该考虑我应该在开始时初始化的先决条件该项目从那时起基本上就不再管了,因为它创建了 dev/staging/ 可以引用的存储桶?

还有一个问题是:如果我在每个环境中有s3/ec​​2/ecr/子目录,我在哪里执行'terraform plan'命令?是否遍历所有子目录?

当我得到上面的答案和清晰的图片时,通过 DRY 来改进它会很棒,但现在,我更看重通过示例而不是理论上的 DRY 解释的更实用的解决方案。谢谢!

我工作 terraform 5 年。我在模块和环境方面的职业生涯中犯了很多错误。 下面的文字只是分享我的知识和经验。他们可能不好。

真正的示例项目可能很难找到,因为 terraform 不用于创建开源项目。共享 terraform 文件通常是不安全的,因为您显示了内部结构

中的所有 漏洞

模块用途和大小

您应该创建具有单一用途的模块,但您的模块应该是通用的。

示例模块

您可以创建 bastion host module,但更好的办法是创建 module for generic server。此模块可能有一些专门针对您的业务问题的逻辑,例如 CW Log group、一些通用 security group rules

应用模块

有时值得创建更具体的模块。

假设您有应用程序,需要 LambdaECS serviceCloudWatch alarmsRDSEBS 等。所有这些元素都非常强大已连接。

您有 2 个选择:

  • 为以上各项创建单独的模块 - 但是您的应用程序使用 5 个模块。
  • 创建一个大模块,然后您可以使用单个模块部署您的应用程序
  • 混合以上解决方案 - 我更喜欢

一切都取决于细节和一些情况。

但我将向您展示我如何在不同公司的作品中使用 Terraform。

分离资源的分离定义

这是项目,您将环境作为目录。对于每个应用程序、网络、数据资源,您都有独立的状态。我将可变数据保存在单独的目录中(如 RDS、EBS、EFS、S3 等),因此所有应用程序、网络等都可以销毁和重新创建,因为它们是无状态的。没有人可以销毁有状态的项目,因为数据可能会丢失。这就是我过去几年一直在做的事情。

project/
├─ packer/
├─ ansible/
├─ terraform/
│  ├─ environments/
│  │  ├─ production/
│  │  │  ├─ apps/
│  │  │  │  ├─ blog/
│  │  │  │  ├─ ecommerce/
│  │  │  ├─ data/
│  │  │  │  ├─ efs-ecommerce/
│  │  │  │  ├─ rds-ecommerce/
│  │  │  │  ├─ s3-blog/
│  │  │  ├─ general/
│  │  │  │  ├─ main.tf
│  │  │  ├─ network/
│  │  │  │  ├─ main.tf
│  │  │  │  ├─ terraform.tfvars
│  │  │  │  ├─ variables.tf
│  │  ├─ staging/
│  │  │  ├─ apps/
│  │  │  │  ├─ ecommerce/
│  │  │  │  ├─ blog/
│  │  │  ├─ data/
│  │  │  │  ├─ efs-ecommerce/
│  │  │  │  ├─ rds-ecommerce/
│  │  │  │  ├─ s3-blog/
│  │  │  ├─ network/
│  │  ├─ test/
│  │  │  ├─ apps/
│  │  │  │  ├─ blog/
│  │  │  ├─ data/
│  │  │  │  ├─ s3-blog/
│  │  │  ├─ network/
│  ├─ modules/
│  │  ├─ apps/
│  │  │  ├─ blog/
│  │  │  ├─ ecommerce/
│  │  ├─ common/
│  │  │  ├─ acm/
│  │  │  ├─ user/
│  │  ├─ computing/
│  │  │  ├─ server/
│  │  ├─ data/
│  │  │  ├─ efs/
│  │  │  ├─ rds/
│  │  │  ├─ s3/
│  │  ├─ networking/
│  │  │  ├─ alb/
│  │  │  ├─ front-proxy/
│  │  │  ├─ vpc/
│  │  │  ├─ vpc-pairing/
├─ tools/

申请单一申请,您需要做的是:

cd ./project/terraform/environments/<ENVIRONMENT>/apps/blog;

terraform apply;

您可以看到所有环境中都有很多目录。正如我所见,这些工具各有利弊。

缺点:

  • 很难检查所有模块是否同步
  • 复杂CI
  • 复杂的目录结构,特别是对于团队中的新人,但很合逻辑
  • 可能会有很多依赖关系,但是从一开始就想这不是问题。
  • 您需要小心,以保持完全相同的环境。
  • 需要大量的初始化工作,很难进行重构。

优点:

  • 小改动后快速应用
  • 分离的应用程序和资源。在不了解整个系统的情况下很容易修改小模块或小部署
  • 当你删除一些东西时更容易清理
  • 很容易判断哪个模块需要修复。我使用我编写的一些工具来分析基础设施特定部分的状态,并且我可以向特定开发人员发送电子邮件,说明他的基础设施由于某些原因需要重新同步。
  • 与单体应用相比,您可以更轻松地拥有不同的环境。如果你不需要它,你可以销毁单个应用程序 environemnt

单体基础设施

上次我开始在新公司工作。他们将基础架构定义保存在几个巨大的存储库(或文件夹)中,当您执行 terraform apply 时,您会同时创建所有应用程序。

project/
├─ modules/
│  ├─ acm/
│  ├─ app-blog/
│  ├─ app-ecommerce/
│  ├─ server/
│  ├─ vpc/
├─ vars/
│  ├─ user/
│  ├─ prod.tfvars
│  ├─ staging.tfvars
│  ├─ test.tfvars
├─ applications.tf
├─ providers.tf
├─ proxy.tf
├─ s3.tf
├─ users.tf
├─ variables.tf
├─ vpc.tf

这里为每个环境准备不同的输入值。

例如,您想要将更改应用于产品:

terraform apply -var-file=vars/prod.tfvars -lock-timeout=300s

应用分期:

terraform apply -var-file=vars/staging.tfvars -lock-timeout=300s

缺点:

  • 您没有依赖关系,但有时您需要手动准备一些环境元素,如域、弹性 IP 等,或者您需要在 terraform plan/apply 之前创建它们。那你有问题
  • 很难清理,因为您同时拥有数百个资源和模块
  • 极长的地形执行。这里 plan/apply 单一环境
  • 大约需要 45 分钟
  • 很难理解整个环境。
  • 通常你需要有 2/3 的存储库,如果你保持这种结构来分离网络、应用程序、dns 等...
  • 你需要做更多的工作来应对不同的环境。您需要使用计数等...

优点:

  • 检查您的基础设施是否是最新的很容易
  • 没有复杂的目录结构...
  • 你所有的环境都完全一样。
  • 重构可能更容易,因为您在很少的地方拥有所有资源。
  • 需要少量初始化。

总结

如您所见,这是更多的架构问题,学习它的唯一方法是获得更多经验或阅读其他人的一些 posts...

我仍在尝试找出最佳方式,我可能会尝试第一种方式。

不要把我的优势当成肯定的事情。这post只是我的经验,也许不是最好的。

参考资料

我会post一些对我帮助很大的参考资料:

我可以分享我们最终为 Indeni Cloudrail 服务所做的工作。希望对你有帮助。

我们创建了一个包含所有模块的文件夹。然后,有一个名为“all”的模块,它基本上使用正确的参数调用其他模块(s3、acm 等)。 “所有”模块都有变量。

然后是环境。他们每个人都用这些变量的特定值调用“所有”模块。

这是 Terraform 代码根目录上的“查找”命令的输出(抱歉,它不够漂亮)。我删除了很多文件,因为不需要它们来表达观点:

./common.tfvars
./terragrunt.hcl
./environments
./environments/prod
./environments/prod/main.tf
./environments/prod/terragrunt.hcl
./environments/prod/lambda.layer.zip
./environments/prod/terraform.tfvars
./environments/prod/lambda.zip
./environments/prod/common.tf
./environments/dev-john
./environments/dev-john/main.tf
./environments/dev-john/terragrunt.hcl
./environments/dev-john/terraform.tfvars
./environments/dev-john/common.tf
./environments/mgmt-dr
./environments/mgmt-dr/data.tf
./environments/mgmt-dr/main.tf
./environments/mgmt-dr/terragrunt.hcl
./environments/mgmt-dr/network.tf
./environments/mgmt-dr/terraform.tfvars
./environments/mgmt-dr/jenkins.tf
./environments/mgmt-dr/keypair.tf
./environments/mgmt-dr/common.tf
./environments/mgmt-dr/openvpn-as.tf
./environments/mgmt-dr/tgw.tf
./environments/mgmt-dr/vars.tf
./environments/staging
./environments/staging/main.tf
./environments/staging/terragrunt.hcl
./environments/staging/terraform.tfvars
./environments/staging/common.tf
./environments/mgmt
./environments/mgmt/data.tf
./environments/mgmt/main.tf
./environments/mgmt/terragrunt.hcl
./environments/mgmt/network.tf
./environments/mgmt/terraform.tfvars
./environments/mgmt/route53.tf
./environments/mgmt/acm.tf
./environments/mgmt/jenkins.tf
./environments/mgmt/keypair.tf
./environments/mgmt/common.tf
./environments/mgmt/openvpn-as.tf
./environments/mgmt/tgw.tf
./environments/mgmt/alb.tf
./environments/mgmt/vars.tf
./environments/develop
./environments/develop/main.tf
./environments/develop/terragrunt.hcl
./environments/develop/terraform.tfvars
./environments/develop/common.tf
./environments/preproduction
./environments/preproduction/main.tf
./environments/preproduction/terragrunt.hcl
./environments/preproduction/terraform.tfvars
./environments/preproduction/common.tf
./environments/prod-dr
./environments/prod-dr/main.tf
./environments/prod-dr/terragrunt.hcl
./environments/prod-dr/terraform.tfvars
./environments/prod-dr/common.tf
./environments/preproduction-dr
./environments/preproduction-dr/main.tf
./environments/preproduction-dr/terragrunt.hcl
./environments/preproduction-dr/terraform.tfvars
./environments/preproduction-dr/common.tf
./README.rst
./modules
./modules/secrets-manager
./modules/secrets-manager/main.tf
./modules/s3
./modules/s3/main.tf
./modules/cognito
./modules/cognito/main.tf
./modules/cloudfront
./modules/cloudfront/main.tf
./modules/cloudfront/files
./modules/cloudfront/files/lambda.zip
./modules/cloudfront/main.py
./modules/all
./modules/all/ecs.tf
./modules/all/data.tf
./modules/all/db-migration.tf
./modules/all/s3.tf
./modules/all/kms.tf
./modules/all/rds-iam-auth.tf
./modules/all/network.tf
./modules/all/acm.tf
./modules/all/cloudfront.tf
./modules/all/templates
./modules/all/lambda.tf
./modules/all/tgw.tf
./modules/all/guardduty.tf
./modules/all/cognito.tf
./modules/all/step-functions.tf
./modules/all/secrets-manager.tf
./modules/all/api-gateway.tf
./modules/all/rds.tf
./modules/all/cloudtrail.tf
./modules/all/vars.tf
./modules/ecs
./modules/ecs/cluster
./modules/ecs/cluster/main.tf
./modules/ecs/task
./modules/ecs/task/main.tf
./modules/step-functions
./modules/step-functions/main.tf
./modules/api-gw
./modules/api-gw/resource
./modules/api-gw/resource/main.tf
./modules/api-gw/method
./modules/api-gw/method/main.tf
./modules/api-gw/rest-api
./modules/api-gw/rest-api/main.tf
./modules/cloudtrail
./modules/cloudtrail/main.tf
./modules/cloudtrail/README.rst
./modules/transit-gateway
./modules/transit-gateway/attachment
./modules/transit-gateway/attachment/main.tf
./modules/transit-gateway/README.rst
./modules/transit-gateway/gateway
./modules/transit-gateway/gateway/main.tf
./modules/openvpn-as
./modules/openvpn-as/main.tf
./modules/load-balancer
./modules/load-balancer/outputs.tf
./modules/load-balancer/main.tf
./modules/load-balancer/vars.tf
./modules/lambda
./modules/lambda/main.tf
./modules/vpc
./modules/vpc/3tier
./modules/vpc/3tier/main.tf
./modules/vpc/3tier/README.rst
./modules/vpc/peering
./modules/vpc/peering/main.tf
./modules/vpc/peering/README.rst
./modules/vpc/public
./modules/vpc/public/main.tf
./modules/vpc/public/README.rst
./modules/vpc/endpoint
./modules/vpc/endpoint/main.tf
./modules/vpc/README.rst
./modules/vpc/isolated
./modules/vpc/isolated/main.tf
./modules/vpc/isolated/README.rst
./modules/vpc/subnets
./modules/vpc/subnets/main.tf
./modules/vpc/subnets/README.rst
./modules/guardduty
./modules/guardduty/README.md
./modules/guardduty/region
./modules/guardduty/region/main.tf
./modules/guardduty/region/guardduty.tf
./modules/guardduty/region/sns-topic.tf
./modules/guardduty/region/vars.tf
./modules/guardduty/.gitignore
./modules/guardduty/base
./modules/guardduty/base/data.tf
./modules/guardduty/base/guardduty-sqs.tf
./modules/guardduty/base/guardduty-lambda.tf
./modules/guardduty/base/variables.tf
./modules/guardduty/base/guardduty-kms.tf
./modules/guardduty/base/bucket.tf
./modules/guardduty/base/guardduty-sns.tf
./modules/guardduty/base/src
./modules/guardduty/base/src/guardduty_findings_relay.py
./modules/guardduty/base/src/guardduty_findings_relay.zip
./modules/jenkins
./modules/jenkins/main.tf
./modules/rds
./modules/rds/main.tf
./modules/acm
./modules/acm/main.tf

我意识到正如@MarkB 所建议的那样,terraform 工作空间实际上是多环境项目的解决方案。

所以我的项目结构是这样的:

infra/
  dev/
    dev.tfvars
  stage/
    stage.tfvars 
  provider.tf
  main.tf
  variables.tf

main.tf 引用模块,provider.tf 设置提供商,backend.tf 将设置远程后端(尚未添加)等

此配置中的“terraform plan” 变为 'terraform plan -var-file dev/dev.tfvars',我在其中指定了具有特定配置的文件环境。