【问题标题】:How to organize terraform modules for multiple environments?如何为多种环境组织 terraform 模块?
【发布时间】:2021-05-07 12:47:33
【问题描述】:

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

  1. 多个环境(开发、阶段)
  2. 远程后端 (s3)
  3. 一些基本资源(存储桶或 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 解释。谢谢!

【问题讨论】:

  • 你确实想要不同的 modules.tf 用于不同的阶段,这完全不是你的基础设施应该看起来的样子。因为现在你的 stage 不再与你的 dev 相关,你在 dev 上开发的东西不会是在 stage 上测试的东西,也不会是在 prod 上发布的东西。您只有一个目录,您可以在其中放置所有基础设施、所有模块、所有逻辑。这由一些变量参数化,例如ec2 实例大小或日志保留时间等。这些变量将根据 env 具有不同的值,这是唯一改变的东西。
  • 然后你需要一个包装器,要么是 terragrunt,要么是一些自定义的基本 shell 脚本。从技术上讲,您不需要有包装器,但另一种方法是必须记住 terraform 命令的许多参数。
  • 您的发现似乎与我的建议完全相反。 Terraform 工作空间的存在是为了解决您要解决的确切问题。为什么你认为工作空间不适合你?
  • 工作空间如何“反文档化”?我认为您应该真正尝试一下这些功能,以了解它们的工作原理。工作区和模块是两个完全不同的东西,它们可以完美地协同工作。模块应该用于封装一组资源,例如 VPC 模块、EC2 模块等。我强烈建议浏览(甚至使用)Terraform 注册表中的一些开源模块,看看它们是如何工作的registry.terraform.io/browse/modules
  • @MarkB 我很容易理解模块的工作原理。哎呀,我什至在单独的 repo 上创建了一个 s3 模块,我知道如何调用它。我的大部分问题都来自于将东西分成子目录,然后不知道从那时起如何管理状态。只是我很难理解我应该如何以这种子目录的方式配置和管理状态。网络上的很多例子都在谈论你“不应该把所有东西都放在一个地方”,但很少有人谈论你的 terraform 命令应该如何适应它。

标签: amazon-web-services terraform terraform-provider-aws


【解决方案1】:

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

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

模块用途和大小

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

示例模块

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

应用模块

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

假设您有一个应用程序,它需要LambdaECS serviceCloudWatch alarmsRDSEBS 等。所有这些元素都紧密相连。

你有两个选择:

  • 为上述每个项目创建单独的模块 - 但是您的应用程序使用 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
  • 复杂的目录结构,特别是对于团队中的新人,但这是逻辑
  • 可能存在很多依赖关系,但从一开始就考虑到这不是问题。
  • 您需要小心,以保持完全相同的环境。
  • 需要进行大量初始化,并且很难进行重构。

优点:

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

单体基础架构

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

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

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

例如,您想对 prod 应用更改:

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

应用暂存:

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

缺点:

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

优点:

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

总结

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

我仍在尝试找出最优化的方法,我可能会尝试第一种方法。

不要把我的优势当作肯定的事。这篇文章只是我的经验,也许不是最好的。

参考文献

我会发布一些对我有很大帮助的参考资料:

【讨论】:

  • 你在这里比较苹果和橘子。唯一的区别实际上是您是否有不同阶段的 something.tf 文件,或者您有不同的阶段变量文件。单体应用的缺点基本上都是不正确的,你根本不应该为整个公司部署一个部署,但没有什么能阻止你为一个团队的一个可部署工件拥有一个存储库。您应该像在“微模块”方法中那样拆分不同的可部署项目,但每个环境的不同模块应该是相同的。
  • 是的,我对所有环境都有相同的模块。我不同意你的论文。对于单体应用来说,这就是我的工作方式,而且我知道很少有公司以这种方式组织代码,这对我不利。但很难说:you simply should not have one deployment for your entire company,,因为即使它有很多缺点,它在某些情况下也很有用。悬停是的,您可以将多个部署放在一个存储库中,但想法是将所有资源组放在单个部署中而不是多个小型部署中。
【解决方案2】:

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

所以我的项目结构看起来像这样:

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

ma​​in.tf 引用模块,provider.tf 设置提供程序,backend.tf 将设置远程后端(尚未添加)等。

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

【讨论】:

    【解决方案3】:

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

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

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

    这是 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-25
      • 1970-01-01
      • 2019-05-20
      • 1970-01-01
      • 2020-07-19
      • 2021-07-03
      • 1970-01-01
      • 2020-06-06
      相关资源
      最近更新 更多