【问题标题】:Terraform environments - how to get it DRYTerraform 环境 - 如何使其干燥
【发布时间】:2018-08-29 13:05:56
【问题描述】:

我们大量使用 Terraform 进行 AWS 云配置。我们的基本地形结构如下所示:

├─ modules
├── x
├── y
├─ environments
├── dev
│   ├── main.tf
│   ├── output.tf
│   └── variables.tf
└── uat
│   ├── main.tf
│   ├── output.tf
│   └── variables.tf
└── prod
    ├── main.tf
    ├── output.tf
    └── variables.tf

随着我们达到拥有许多模块和许多环境的地步,代码重复现在变得更加令人头疼,我们希望尽可能多地摆脱它。

我们目前主要关注的是output.tf 文件——每次我们扩展现有模块或添加新模块时,我们都需要为其设置环境特定配置(这是预期的),但我们仍然必须将所需部分复制/粘贴到 output.tf 以输出配置结果(如 IP 地址、AWS ARN 等)。

有没有办法摆脱重复的output.tf 文件?我们是否可以在模块本身中定义所需的输出,并在为特定环境运行 terraform 时查看所有定义的输出?

【问题讨论】:

    标签: amazon-web-services devops terraform


    【解决方案1】:

    我们构建并开源了Terragrunt 来解决这个问题。 Terragrunt 的功能之一是能够下载远程 Terraform 配置。这个想法是,您只需在一个存储库中为您的基础架构定义一次 Terraform 代码,例如,称为modules

    └── modules
     ├── app
     │   └── main.tf
     ├── mysql
     │   └── main.tf
     └── vpc
         └── main.tf
    

    这个 repo 包含典型的 Terraform 代码,有一个区别:代码中任何在环境之间应该不同的东西都应该作为输入变量公开。例如,app 模块可能会暴露以下变量:

    variable "instance_count" {
      description = "How many servers to run"
    }
    
    variable "instance_type" {
      description = "What kind of servers to run (e.g. t2.large)"
    }
    

    在一个单独的 repo 中,例如,live,您为所有环境定义代码,现在每个组件只包含一个 .tfvars 文件(例如 app/terraform.tfvarsmysql/terraform.tfvars 等)。这将为您提供以下文件布局:

    └── live
        ├── prod
        │   ├── app
        │   │   └── terraform.tfvars
        │   ├── mysql
        │   │   └── terraform.tfvars
        │   └── vpc
        │       └── terraform.tfvars
        ├── qa
        │   ├── app
        │   │   └── terraform.tfvars
        │   ├── mysql
        │   │   └── terraform.tfvars
        │   └── vpc
        │       └── terraform.tfvars
        └── stage
            ├── app
            │   └── terraform.tfvars
            ├── mysql
            │   └── terraform.tfvars
            └── vpc
                └── terraform.tfvars
    

    请注意,任何文件夹中都没有 Terraform 配置(.tf 文件)。相反,每个.tfvars 文件都指定一个terraform { ... } 块,该块指定从何处下载 Terraform 代码,以及该 Terraform 代码中输入变量的环境特定值。例如,stage/app/terraform.tfvars 可能如下所示:

    terragrunt = {
      terraform {
        source = "git::git@github.com:foo/modules.git//app?ref=v0.0.3"
      }
    }
    
    instance_count = 3
    instance_type = "t2.micro"
    

    prod/app/terraform.tfvars 可能看起来像这样:

    terragrunt = {
      terraform {
        source = "git::git@github.com:foo/modules.git//app?ref=v0.0.1"
      }
    }
    
    instance_count = 10
    instance_type = "m2.large"
    

    请参阅Terragrunt documentation 了解更多信息。

    【讨论】:

      【解决方案2】:

      解决此问题的一种方法是创建一个base 环境,然后对公共元素进行符号链接,例如:

      ├─ modules
      ├── x
      ├── y
      ├─ environments
      ├── base
      │   ├── output.tf
      │   └── variables.tf
      ├── dev
      │   ├── main.tf
      │   ├── output.tf -> ../base/output.tf
      │   └── variables.tf -> ../base/variables.tf
      ├── uat
      │   ├── main.tf
      │   ├── output.tf -> ../base/output.tf
      │   └── variables.tf -> ../base/variables.tf
      ├── super_custom
      │   ├── main.tf
      │   ├── output.tf # not symlinked
      │   └── variables.tf # not symlinked
      └── prod
          ├── main.tf
          ├── output.tf -> ../base/output.tf
          └── variables.tf -> ../base/variables.tf
      

      这种方法只有在你的output.tfvariables.tf 文件对于每个环境都相同的情况下才真正有效,尽管你可以有非符号链接的变体(例如上面的super_custom),但这可能会变得令人困惑,因为它不会立即生效很明显哪些环境是自定义的,哪些不是。 YMMV。我尝试将环境之间的更改限制为每个环境一个 .tfvars 文件。

      值得一读Charity Major's excellent post on tfstate files,它让我走上了这条道路。

      【讨论】:

      • 感谢您的回答。我想到了符号链接,但不喜欢这种方法。如果这是唯一的方法,我会接受它。感谢您链接这篇文章,它真的很有见地,非常棒!
      • 我发布了另一个答案,通过完全删除每个环境的模块来解决这个问题。听起来您的环境非常相似,这可能是一种更清洁的方法。如果没有,您能否详细说明您在环境之间存在的各种差异的信息来扩展您的问题?
      【解决方案3】:

      如果您的devuatprod 环境具有相同的形状,但不同的属性,您可以利用workspaces 来分隔您的环境状态,以及单独的*.tfvars 文件来指定不同的配置。

      这可能看起来像:

      ├─ modules
      │   ├── x
      │   └── y
      ├── dev.tfvars
      ├── prod.tfvars
      ├── uat.tfvars
      ├── main.tf
      ├── outputs.tf
      └── variables.tf
      

      您可以使用以下方法创建新工作区:

      terraform workspace new uat
      

      那么部署变更就变成了:

      terraform workspace select uat
      terraform apply --var-file=uat.tfvars
      

      工作区功能可确保单独管理不同的环境状态,这是一个好处。

      这种方法仅适用于环境之间的差异足够小以至于可以将逻辑封装在各个模块中的情况(例如,有一个 high_availability 标志,它为 uat 添加了一些额外的冗余基础架构和prod)。

      【讨论】:

      • 我们也考虑过这种方法,但我个人觉得它太危险了。就在有人忘记在调用terraform 时设置正确的状态文件时,我们已经遇到了很大的麻烦。此外,我们正在为我们的脚本使用共享的 S3 状态,我不确定它是否适用于这种方法。
      • 另外,我相信 Terraform 自己的主页将其列为最佳实践部分中的反模式,因此根据他们的说法,这可能也不是最好的方法:(
      • 你有这方面的参考吗?工作区被设计为每个环境使用一个 (terraform.io/docs/enterprise/guides/recommended-practices/…)。我同意如果你手动输入命令听起来很冒险,但我们通过将这些命令包装到一个脚本中来解决这个问题,所以你总是一致的。
      • 是的,请参阅terraform.io/docs/state/workspaces.html,“最佳实践”段落。我还要强调这部分:“单独的工作区对于隔离一组资源以在开发过程中测试更改很有用。例如,通常将 VCS 中的分支与临时工作区相关联,以便可以开发新功能而不影响默认工作区。”。基于此,我不认为工作空间最适合解决这个问题。
      • 嗨@KristofJozsa,我认为该部分是说工作区应该用于此类问题,但是如果您有复杂的配置差异,您应该也 使用terraform_remote_state 将配置封装到每个模块中。当他们说“单独的工作区”时,他们的意思是“工作区可用作开发人员的唯一隔离机制”,而不是“工作区仅用于隔离开发人员”
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-04
      相关资源
      最近更新 更多