128

我正在将我们的基础设施换成 terraform。实际管理 terraform 文件和状态的最佳实践是什么?我意识到它是作为代码的基础设施,我会将我的 .tf 文件提交到 git 中,但我是否也提交 tfstate ?它应该位于 S3 之类的地方吗?我最终希望 CI 能够管理所有这些,但这太过分了,需要我找出文件的移动部分。

我真的只是想看看人们如何在生产中实际使用这种类型的东西

4

12 回答 12

104

我还处于将现有 AWS 基础设施迁移到 Terraform 的状态,因此我的目标是在开发过程中更新答案。

我一直在严重依赖官方的 Terraform示例和多次反复试验来充实我不确定的领域。

.tfstate文件

Terraform 配置可用于在不同的基础设施上配置许多盒子,每个盒子都可以有不同的状态。因为它也可以由多人运行,所以这个状态应该在一个集中的位置(如 S3),而不是git。

这可以通过查看 Terraform 来确认.gitignore

开发者控制

我们的目标是为开发人员提供对基础设施的更多控制,同时保持完整的审计(git log)和健全性检查更改的能力(拉取请求)。考虑到这一点,我的目标是新的基础设施工作流程:

  1. 常见 AMI 的基础,包括可重复使用的模块,例如 puppet。
  2. DevOps 使用 Terraform 提供的核心基础设施。
  3. 开发人员根据需要更改 Git 中的 Terraform 配置(实例数量;新 VPC;添加区域/可用区等)。
  4. 推送 Git 配置并提交拉取请求以供 DevOps 小队成员进行完整性检查。
  5. 如果获得批准,调用 webhook 到 CI 进行构建和部署(目前不确定如何划分多个环境)

编辑 1 - 更新当前状态

自从开始这个答案以来,我已经编写了很多 TF 代码,并且对我们的情况感到更加自在。我们在此过程中遇到了错误和限制,但我接受这是使用新的、快速变化的软件的一个特点。

布局

我们有一个复杂的 AWS 基础设施,其中有多个 VPC,每个 VPC 都有多个子网。轻松管理这一点的关键是定义一个灵活的分类法,该分类法包含区域、环境、服务和所有者,我们可以使用它来组织我们的基础设施代码(包括 terraform 和 puppet)。

模块

下一步是创建一个单一的 git 存储库来存储我们的 terraform 模块。我们的模块的顶级目录结构如下所示:

tree -L 1 .

结果:

├── README.md
├── aws-asg
├── aws-ec2
├── aws-elb
├── aws-rds
├── aws-sg
├── aws-vpc
└── templates

每一个都设置了一些合理的默认值,但将它们公开为可以被我们的“胶水”覆盖的变量。

胶水

我们有一个glue使用上述模块的第二个存储库。它的布局符合我们的分类文件:

.
├── README.md
├── clientA
│   ├── eu-west-1
│   │   └── dev
│   └── us-east-1
│       └── dev
├── clientB
│   ├── eu-west-1
│   │   ├── dev
│   │   ├── ec2-keys.tf
│   │   ├── prod
│   │   └── terraform.tfstate
│   ├── iam.tf
│   ├── terraform.tfstate
│   └── terraform.tfstate.backup
└── clientC
    ├── eu-west-1
    │   ├── aws.tf
    │   ├── dev
    │   ├── iam-roles.tf
    │   ├── ec2-keys.tf
    │   ├── prod
    │   ├── stg
    │   └── terraform.tfstate
    └── iam.tf

在客户端级别内,我们有 AWS 账户特定.tf文件,用于预置全局资源(如 IAM 角色);接下来是具有 EC2 SSH 公钥的区域级别;最后在我们的环境(dev,stgprod)中存储了我们的 VPC 设置、实例创建和对等连接等。

旁注:如您所见,我反对我自己的建议,而不是保留terraform.tfstategit。在我迁移到 S3 之前,这是一项临时措施,但适合我,因为我目前是唯一的开发人员。

下一步

这仍然是一个手动过程,还没有在 Jenkins 中,但我们正在移植一个相当大、复杂的基础设施,到目前为止一切都很好。就像我说的,很少有错误,但进展顺利!

编辑 2 - 更改

自从我写下这个最初的答案已经快一年了,Terraform 和我自己的状态都发生了显着变化。我现在处于使用 Terraform 管理 Azure 集群的新位置,而 Terraform 现在是v0.10.7.

状态

人们一再告诉我 state 不应该进入Git - 他们是正确的。我们将此作为依赖开发人员沟通和纪律的两人团队的临时措施。通过一个更大的分布式团队,我们现在可以充分利用 S3 中的远程状态以及DynamoDB 提供的锁定。理想情况下,这将被迁移到 consul,现在它是 v1.0 以减少跨云提供商。

模块

之前我们创建并使用了内部模块。情况仍然如此,但随着Terraform 注册表的出现和发展,我们至少尝试使用这些作为基础。

文件结构

新职位的分类要简单得多,只有两个 infx 环境 -devprod. 每个都有自己的变量和输出,重用我们上面创建的模块。提供remote_state者还帮助在环境之间共享创建资源的输出。我们的方案是将不同 Azure 资源组中的子域指向全局托管的 TLD。

├── main.tf
├── dev
│   ├── main.tf
│   ├── output.tf
│   └── variables.tf
└── prod
    ├── main.tf
    ├── output.tf
    └── variables.tf

规划

再次面对分布式团队的额外挑战,我们现在总是保存terraform plan命令的输出。我们可以检查并知道将运行什么而不会有在阶段planapply阶段之间发生一些变化的风险(尽管锁定对此有帮助)。请记住删除此计划文件,因为它可能包含纯文本“秘密”变量。

总的来说,我们对 Terraform 非常满意,并会继续学习和改进添加的新功能。

于 2015-11-10T11:22:29.450 回答
89

我们大量使用 Terraform,我们推荐的设置如下:

文件布局

我们强烈建议将您的每个环境(例如 stage、prod、qa)的 Terraform 代码存储在单独的模板集(因此,单独的.tfstate文件)中。这很重要,这样您的独立环境在进行更改时实际上是相互隔离的。否则,当在 staging 中弄乱一些代码时,也很容易在 prod 中炸毁某些东西。请参阅Terraform、VPC 以及为什么要为每个 env提供一个 tfstate 文件,以进行丰富多彩的讨论。

因此,我们典型的文件布局如下所示:

stage
  └ main.tf
  └ vars.tf
  └ outputs.tf
prod
  └ main.tf
  └ vars.tf
  └ outputs.tf
global
  └ main.tf
  └ vars.tf
  └ outputs.tf

阶段 VPC 的所有 Terraform 代码都进入stage文件夹,生产 VPC 的所有代码进入prod文件夹,VPC 之外的所有代码(例如 IAM 用户、SNS 主题、S3 存储桶)进入global文件夹.

请注意,按照惯例,我们通常将 Terraform 代码分解为 3 个文件:

  • vars.tf:输入变量。
  • outputs.tf:输出变量。
  • main.tf: 实际资源。

模块

通常,我们在两个文件夹中定义我们的基础设施:

  1. infrastructure-modules:此文件夹包含小型、可重复使用的版本控制模块。将每个模块视为如何创建单个基础架构(例如 VPC 或数据库)的蓝图。
  2. infrastructure-live:此文件夹包含实际实时运行的基础架构,它通过组合infrastructure-modules. 将此文件夹中的代码视为您根据蓝图建造的实际房屋。

Terraform 模块就是文件夹中的任意一组 Terraform 模板。例如,我们可能有一个名为vpcin的文件夹,infrastructure-modules其中定义了单个 VPC 的所有路由表、子网、网关、ACL 等:

infrastructure-modules
  └ vpc
    └ main.tf
    └ vars.tf
    └ outputs.tf

然后,我们可以在其中使用该模块infrastructure-live/stageinfrastructure-live/prod创建 stage 和 prod VPC。例如,这infrastructure-live/stage/main.tf可能看起来像:

module "stage_vpc" {
  source = "git::git@github.com:gruntwork-io/module-vpc.git//modules/vpc-app?ref=v0.0.4"

  vpc_name         = "stage"
  aws_region       = "us-east-1"
  num_nat_gateways = 3
  cidr_block       = "10.2.0.0/18"
}

要使用模块,您可以使用module资源并将其source字段指向硬盘驱动器上的本地路径(例如source = "../infrastructure-modules/vpc"),或者如上例所示,指向 Git URL(请参阅模块源代码)。Git URL 的优点是我们可以指定特定的 git sha1 或标签 ( ref=v0.0.4)。现在,我们不仅将我们的基础设施定义为一堆小模块,而且我们可以对这些模块进行版本控制,并根据需要仔细更新或回滚。

我们创建了许多可重用、经过测试和记录的基础设施包,用于创建 VPC、Docker 集群、数据库等,并且在后台,它们中的大多数只是版本化的 Terraform 模块。

状态

当您使用 Terraform 创建资源(例如 EC2 实例、数据库、VPC)时,它会在文件中记录有关其创建内容的信息.tfstate。要更改这些资源,您团队中的每个人都需要访问同一个.tfstate文件,但您不应将其签入 Git(请参阅此处了解原因)。

相反,我们建议通过启用Terraform Remote State.tfstate将文件存储在 S3 中,这将在您每次运行 Terraform 时自动推送/拉取最新文件。确保在您的 S3 存储桶中启用版本控制,以便您可以回滚到旧文件,以防您以某种方式损坏了最新版本。然而,一个重要的提示:Terraform 不提供 locking因此,如果两个团队成员同时在同一个文件上运行,他们最终可能会覆盖彼此的更改。.tfstateterraform apply.tfstate

2020 年编辑:Terraform 现在支持锁定:https ://www.terraform.io/docs/state/locking.html

为了解决这个问题,我们创建了一个名为Terragrunt的开源工具,它是 Terraform 的一个瘦包装器,它使用 Amazon DynamoDB 提供锁定(对于大多数团队来说应该是完全免费的)。查看使用 Terragrunt 将自动远程状态锁定和配置添加到 Terraform 以获取更多信息。

进一步阅读

我们刚刚开始了一系列名为“Terraform 综合指南”的博客文章,其中详细描述了我们在现实世界中使用 Terraform 所学到的所有最佳实践。

更新:Terraform 博客文章系列综合指南非常受欢迎,我们将其扩展为一本书,名为Terraform:Up & Running

于 2016-08-03T16:52:56.250 回答
10

以前remote config允许这样做,但现在已被“后端”取代,因此 terraform remote 不再可用。

terraform remote config -backend-config="bucket=<s3_bucket_to_store_tfstate>" -backend-config="key=terraform.tfstate" -backend=s3
terraform remote pull
terraform apply
terraform remote push

有关详细信息,请参阅文档

于 2016-07-09T18:25:30.853 回答
6

@Yevgeny Brikman 进行了更深入的介绍,但专门回答了 OP 的问题:

实际管理 terraform 文件和状态的最佳实践是什么?

将 git 用于 TF 文件。但不要检查状态文件(即 tfstate)。而是Terragrunt用于将状态文件同步/锁定到 S3。

但我也提交 tfstate 吗?

不。

它应该位于 S3 之类的地方吗?

是的

于 2017-06-19T10:47:06.207 回答
3

我知道这里有很多答案,但我的方法完全不同。

⁃   Modules
⁃   Environment management 
⁃   Separation of duties

模块

  1. 为资源的逻辑集合创建模块。示例:如果您的目标是部署需要数据库、HA 虚拟机、自动缩放、DNS、PubSub 和对象存储的 API,那么所有这些资源都应该在单个模块中进行模板化。
  2. 避免创建使用单一资源的模块。这可以并且已经完成,并且注册表中的许多模块都这样做,但这是一种有助于资源可访问性而不是基础架构编排的实践。示例:AWS EC2 的一个模块通过使复杂的配置更易于调用来帮助用户访问 EC2,但是像 1. 中的示例这样的模块在编排应用程序、组件或服务驱动的基础设施时帮助用户。
    1. 避免在工作区中声明资源。这更多是为了让你的代码保持整洁和有条理。由于模块易于版本控制,您可以更好地控制您的版本。

环境管理

IaC 已使 SDLC 流程与基础架构管理相关,期望拥有开发基础架构和开发应用程序环境是不正常的。

  1. 不要使用文件夹来管理您的 IaC 环境。这会导致漂移,因为您的基础架构没有通用模板。
  2. 请使用单个工作区和变量来控制环境规范。示例:编写您的模块,以便当您更改环境变量(var.stage 很流行)时,计划会根据您的要求进行更改。通常情况下,环境应尽可能少地变化,数量、暴露和容量通常是可变配置。开发人员可能会在私有拓扑中部署 1 个具有 1 个内核和 1GB RAM 的 VM,但生产可能是具有 2 个内核和 4GB RAM 的 3 个 VM,具有额外的公共拓扑。你当然可以有更多的变化:开发可能在与应用程序相同的服务器上运行数据库进程以节省成本,但生产可能有一个专用的数据库实例。所有这些都可以通过更改单个变量、三元语句和插值来管理。

职责分离

如果您在一个小型组织或运行个人基础设施,这并不适用,但它会帮助您管理您的操作。

  1. 按职责、职责或团队分解您的基础架构。示例:中央 IT 控制底层共享服务(虚拟网络、子网、公共 IP 地址、日志组、治理资源、多租户数据库、共享密钥等),而 API 团队仅控制其服务所需的资源(VM、LB 、PubSub 等)并通过数据源和远程状态查找使用中央 IT 服务。
    1. 管理团队访问。示例:中央 IT 可能具有管理员权限,但 API 团队只能访问一组受限的公共云 API。

这也有助于解决发布问题,因为您会发现一些资源很少更改,而另一些则一直在更改。分离消除了风险和复杂性。

该策略与 AWS 的多账户策略相似。阅读更多信息。

CI/CD

这是一个独立的主题,但 Terraform 在良好的管道中运行良好。这里最常见的错误是将 CI 视为灵丹妙药。从技术上讲,Terraform 应该只在装配管道的阶段提供基础设施。这与通常验证和测试模板的 CI 阶段发生的事情是分开的。

NB写在手机上,如有错误请见谅。

于 2018-09-22T15:45:52.877 回答
0

在答案非常可靠和信息丰富之前,我将尝试在此处添加我的 2 美分

结构化代码的常见建议

  1. 使用较少数量的资源更容易、更快捷:

    • Cmdsterraform planterraformapply 都调用云 API 来验证资源的状态。
    • 如果您将整个基础架构放在一个组合中,这可能需要几分钟(即使您在同一个文件夹中有多个文件)。
  2. 爆炸半径更小,资源更少:

    • 通过将不相关的资源放置在单独的组合(文件夹)中来将它们相互隔离,从而降低出现问题时的风险。
  3. 使用远程状态启动您的项目:

  4. 尝试实践一致的结构和命名约定:

    • 与程序代码一样,Terraform代码应该首先编写供人们阅读,当六个月后发生更改时,一致性将有所帮助。
    • 可以在Terraform 状态文件中移动资源,但如果结构和命名不一致,则可能会更难。
  5. 使资源模块尽可能简单。

  6. 不要对可以作为变量传递或使用数据源发现的值进行硬编码。

  7. 使用data资源,terraform_remote_state特别是作为组合内基础设施模块之间的粘合剂。

参考文章: https ://www.terraform-b​​est-practices.com/code-structure )


例子:

使用较少数量的资源更容易和更快,因此下面我们提供推荐的代码布局。

注意:作为参考,不要严格遵循,因为每个项目都有自己的特定特征

.
├── 1_tf-backend #remote AWS S3 + Dynamo Lock tfstate 
│   ├── main.tf
│   ├── ...
├── 2_secrets
│   ├── main.tf
│   ├── ...
├── 3_identities
│   ├── account.tf
│   ├── roles.tf
│   ├── group.tf
│   ├── users.tf
│   ├── ...
├── 4_security
│   ├── awscloudtrail.tf
│   ├── awsconfig.tf
│   ├── awsinspector.tf
│   ├── awsguarduty.tf
│   ├── awswaf.tf
│   └── ...
├── 5_network
│   ├── account.tf
│   ├── dns_remote_zone_auth.tf
│   ├── dns.tf
│   ├── network.tf
│   ├── network_vpc_peering_dev.tf
│   ├── ...
├── 6_notifications
│   ├── ...
├── 7_containers
│   ├── account.tf
│   ├── container_registry.tf
│   ├── ...
├── config
│   ├── backend.config
│   └── main.config
└── readme.md
于 2019-06-29T18:57:59.900 回答
0

我相信在使用 terraform 编排基础架构时几乎不需要遵循最佳实践

  1. 不要再次编写相同的代码(可重用性)
  2. 将环境配置分开以轻松维护。
  3. 使用远程后端 s3(加密)和 dynamo DB 来处理并发锁定
  4. 创建一个模块并在主基础设施中多次使用该模块,它就像一个可重用的函数,可以通过传递不同的参数多次调用。

处理多种环境

大多数时候推荐的方法是使用 terraform 'workspace' 来处理多个环境,但我相信工作空间的使用可能会因组织中的工作方式而异。其他是为每个环境(例如 stage、prod、QA)存储 Terraform 代码以分离环境状态。但是,在这种情况下,我们只是在许多地方复制相同的代码。

├── main.tf
├── dev
│   ├── main.tf
│   ├── output.tf
│   └── variables.tf
└── prod
├── main.tf
├── output.tf
└── variables.tf

我采用了一些不同的方法来处理和避免重复相同的 terraform 代码,方法是保存在每个环境文件夹中,因为我相信大多数情况下所有环境都会 90% 相同。

├── deployment
│ ├── 01-network.tf
│ ├── 02-ecs_cluster.tf
│ ├── 03-ecs_service.tf
│ ├── 04-eks_infra.tf
│ ├── 05-db_infra.tf
│ ├── 06-codebuild-k8s.tf
│ ├── 07-aws-secret.tf
│ ├── backend.tf
│ ├── provider.tf
│ └── variables.tf
├── env
│ ├── dev
│ │ ├── dev.backend.tfvar
│ │ └── dev.variables.tfvar
│ └── prod
│ ├── prod.backend.tfvar
│ └── prod.variables.tfvar
├── modules
│ └── aws
│ ├── compute
│ │ ├── alb_loadbalancer
│ │ ├── alb_target_grp
│ │ ├── ecs_cluster
│ │ ├── ecs_service
│ │ └── launch_configuration
│ ├── database
│ │ ├── db_main
│ │ ├── db_option_group
│ │ ├── db_parameter_group
│ │ └── db_subnet_group
│ ├── developertools
│ ├── network
│ │ ├── internet_gateway
│ │ ├── nat_gateway
│ │ ├── route_table
│ │ ├── security_group
│ │ ├── subnet
│ │ ├── vpc
│ └── security
│ ├── iam_role
│ └── secret-manager
└── templates

与环境相关的配置

将与环境相关的配置和参数分开保存在一个变量文件中,并传递该值来配置基础架构。例如如下

  • 开发后端.tfvar

      region = "ap-southeast-2"
      bucket = "dev-samplebackendterraform"
      key = "dev/state.tfstate"
      dynamo_db_lock = "dev-terraform-state-lock"
    
  • 开发变量.tfvar

    environment                     =   "dev"
    vpc_name                        =   "demo"
    vpc_cidr_block                  =   "10.20.0.0/19"
    private_subnet_1a_cidr_block    =   "10.20.0.0/21"
    private_subnet_1b_cidr_block    =   "10.20.8.0/21"
    public_subnet_1a_cidr_block     =   "10.20.16.0/21"
    public_subnet_1b_cidr_block     =   "10.20.24.0/21"
    

有条件地跳过基础设施部分

在 env 特定变量文件中创建配置,并根据该变量决定创建或跳过该部分。通过这种方式,可以根据需要跳过基础架构的特定部分。

variable vpc_create {
   default = "true"
}

module "vpc" {
  source = "../modules/aws/network/vpc"
  enable = "${var.vpc_create}"
  vpc_cidr_block = "${var.vpc_cidr_block}"
  name = "${var.vpc_name}"
 }

 resource "aws_vpc" "vpc" {
    count                = "${var.enable == "true" ? 1 : 0}"
    cidr_block           = "${var.vpc_cidr_block}"
    enable_dns_support   = "true"
   enable_dns_hostnames = "true"
}

需要以下命令来初始化和执行每个环境的基础设施更改,cd 到所需的环境文件夹。

  terraform init -var-file=dev.variables.tfvar -backend-config=dev.backend.tfvar ../../deployment/

  terraform apply -var-file=dev.variables.tfvar ../../deployment

供参考:https ://github.com/mattyait/devops_terraform

于 2019-09-11T09:58:23.697 回答
0

我不喜欢子文件夹的想法,因为这会导致每个环境产生不同的来源,而且这往往会漂移。

更好的方法是为所有环境(比如说 dev、preprod 和 prod)设置一个堆栈。要在单一环境中工作,请使用terraform workspace.

terraform workspace new dev

这将创建一个新的工作区。这包括一个专用的状态文件和terraform.workspace您可以在代码中使用的变量。

resource "aws_s3_bucket" "bucket" {
  bucket = "my-tf-test-bucket-${terraform.workspace}"
}

通过这种方式,您将获得调用的存储桶

  • 我的 tf 测试桶开发
  • 我的 tf-test-bucket-preprod
  • 我的 tf 测试桶产品

应用到上面的工作区之后(用于terraform workspace select <WORKSPACE>改变环境)。为了使代码甚至是多区域证明,请这样做:

data "aws_region" "current" {}

resource "aws_s3_bucket" "bucket" {
  bucket = "my-tf-test-bucket-${data.aws_region.current.name}-${terraform.workspace}"
}

获取(对于 us-east-1 地区)

  • my-tf-test-bucket-us-east-1-dev
  • 我的 tf-test-bucket-us-east-1-preprod
  • 我的 tf-test-bucket-us-east-1-prod
于 2019-11-22T11:48:28.507 回答
0

要遵循的一些 Terraform 最佳实践:

  1. 避免硬编码:有时开发人员直接手动创建资源。您需要标记这些资源并使用 terraform import 将它们包含在代码中。一个样品:

    account_number="123456789012" account_alias="mycompany"

  2. 从 docker 容器运行 Terraform:Terraform 发布了一个官方 Docker 容器,允许您轻松控制可以运行的版本。

建议在 CI/CD 管道中设置构建作业时运行 Terraform Docker 容器。

TERRAFORM_IMAGE=hashicorp/terraform:0.11.7
TERRAFORM_CMD="docker run -ti --rm -w /app -v ${HOME}/.aws:/root/.aws -v ${HOME}/.ssh:/root/.ssh -v `pwd`:/app $TERRAFORM_IMAGE"

更多内容请参考我的博客:https ://medium.com/tech-darwinbox/how-darwinbox-manages-infrastructure-at-scale-with-terraform-371e2c5f04d3

于 2020-01-14T12:45:01.807 回答
0

我想为这个线程做出贡献。

  • 除非您使用的是 Terraform Cloud,否则这很可能是 AWS S3+DynamoDB。
  • 生产和非生产后端的独立基础架构(网络 + RBAC)。
  • 计划禁止从指定网络(例如部署代理池)外部访问状态文件(网络访问和 RBAC)。
  • 不要将 Terraform 后端基础架构与运行时环境保持一致。使用单独的帐户。
  • 在 Terraform 后端启用对象版本控制以避免丢失更改和状态文件,并维护 Terraform 状态历史。

在某些特殊情况下,将需要手动访问 Terraform 状态文件。诸如重构、中断更改或修复缺陷之类的事情将需要运维人员运行 Terraform 状态操作。在这种情况下,使用堡垒主机、VPN 等计划对 Terraform 状态的特殊控制访问。

查看更长的最佳实践博客,其中详细介绍了这一点,包括 CI/CD 管道指南。

于 2020-03-04T12:06:27.237 回答
-1

如果您仍在寻找更好的解决方案,请查看可以替换维护不同环境文件夹结构的工作区,该文件夹结构可以具有工作区特定变量。

正如Yevgeniy Brikman 所说,最好有一个模块结构。

于 2018-10-22T22:03:52.860 回答
-1

使用 terraform cloud 管理和保存状态,以及上面的建议。

于 2020-05-28T19:07:53.757 回答