使用Terraform的最佳做法


111

我正在将基础架构转换为terraform。实际管理terraform文件和状态的最佳实践是什么?我意识到它是作为代码的基础结构,我将.tf文件提交到git中,但是我也提交tfstate吗?那应该在S3之类的地方吗?我希望最终由CI来管理所有这些,但是这牵扯很大,需要我弄清楚文件的移动部分。

我真的只是想看看外面的人如何在生产中实际利用这种东西

Answers:


85

我也处于将现有的AWS基础架构迁移到Terraform的状态,因此将在我开发时致力于更新答案。

我一直严重依赖于官方的Terraform 示例以及多次尝试和错误来充实我不确定的区域。

.tfstate 档案

Terraform配置可用于在不同的基础架构上置备许多盒子,每个盒子可能具有不同的状态。由于它也可以由多个人运行,因此该状态应位于集中位置(例如S3),而不是 git。

查看Terraform可以确认这一点.gitignore

开发者控制

我们的目标是向开发人员提供对基础架构的更多控制,同时保持完整的审核(git日志)和健全检查更改(拉动请求)的能力。考虑到这一点,我旨在实现的新基础架构工作流程是:

  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公钥的区域级别;最后,在我们的环境中(devstgprod等)是我们的VPC设置,例如创建和对等连接等存储。

旁注:如您所见,在保留terraform.tfstategit的基础上,我违背了自己的建议。这是临时措施,直到我移至S3为止,但由于我是目前唯一的开发人员,因此适合我。

下一步

这仍然是一个手动过程,尚未在Jenkins中进行,但是我们正在移植一个相当庞大,复杂的基础架构,到目前为止还算不错。就像我说的,很少有错误,但是进展顺利!

编辑2-变更

自从我写了这个初步答案以来已经过去了一年,Terraform和我自己的状态都发生了很大变化。我现在处于使用Terraform来管理Azure群集的新位置,而Terraform现在已开始v0.10.7

人们一再告诉我的状态应该不会在Git中去-他们是正确的。我们将此作为与两个人的团队的临时措施,该团队依靠开发人员的沟通和纪律。通过一个更大的分布式团队,我们现在可以利用DynamoDB提供的锁定来充分利用S3中的远程状态。理想情况下,此工具将迁移到领事,现在是削减跨云提供商的v1.0。

模组

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

档案结构

新职位只有两个infx环境- dev和,分类简单得多prod。每个模块都有自己的变量和输出,可重复使用上面创建的模块。该remote_state供应商还有助于在环境之间共享创建的资源的输出。我们的方案是将不同Azure资源组中的子域连接到全局管理的TLD。

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

规划

再次面临分布式团队的额外挑战,我们现在始终保存terraform plan命令的输出。我们可以检查并知道将运行什么,而不会在planapply阶段之间进行任何更改(尽管锁定对此有所帮助)。切记删除此计划文件,因为它可能包含纯文本“秘密”变量。

总体而言,我们对Terraform感到非常满意,并继续通过添加的新功能来学习和改进。


自从这个答案以来,您有运气吗?您的看起来很像我要去做,但您可能会比我走得更远。
Marc Young

3
我很好奇为什么您认为tfstate文件不应该存储在git中?仅仅是因为旧状态不值得保存,还是还有其他问题?
agbodike

3
@agbodike-当以单个开发人员或非常小的团队的一部分工作时,只要定期提交并推动tfstate以避免冲突,就可以将其保留在git中。我的下一步是根据S3中的远程状态文档进行设置(该操作还说:“由于它是合并冲突的常见来源,因此使团队中的Terraform的工作变得很复杂。远程状态有助于缓解这些问题。”) 。与大多数事情一样,良好的团队沟通可以缓解大多数/所有问题,无论采取何种行动保持状态:-)
Ewan

1
@ the0ther-恐怕我的主存储库是专有的,但是我目前正在开发一个个人存储库,我将在不久的将来公开使用该存储库。
伊万

2
Git仓库@Ewan有运气吗?我很想看看你在做什么。
大卫,

85

我们大量使用Terraform,建议的设置如下:

文件布局

我们强烈建议将您的每个环境(例如,阶段,产品,质量保证)的Terraform代码存储在单独的模板集中(因此,在单独的.tfstate文件中)。这一点很重要,因此在进行更改时,您的单独环境实际上彼此隔离。否则,在分阶段处理一些代码时,也很容易在生产中炸毁某些东西。请参阅Terraform,VPC,以及为什么每个环境都需要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模板。例如,我们可能有一个名为的文件夹vpcinfrastructure-modules该文件夹定义单个VPC的所有路由表,子网,网关,ACL等:

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

然后,我们可以在其中使用该模块infrastructure-live/stageinfrastructure-live/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(原因请参见此处)。

相反,我们建议.tfstate通过启用Terraform Remote State将文件存储在S3中,这将在每次运行Terraform时自动推送/拉动最新文件。确保在S3存储桶中启用版本控制,以便您可以回滚到旧.tfstate文件,以防万一您破坏了最新版本。但是,请注意:Terraform不提供lock。因此,如果两个团队成员terraform apply同时在同一.tfstate文件上运行,则他们最终可能会覆盖彼此的更改。

为了解决此问题,我们创建了一个名为Terragrunt的开源工具,该工具是Terraform的瘦包装,它使用Amazon DynamoDB提供锁定(大多数团队应该完全免费)。退房添加远程自动状态下进行锁定和配置对terraform与Terragrunt获取更多信息。

进一步阅读

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

更新:《 Terraform综合指南》博客文章系列如此受欢迎,以至于将其扩展为一本名为《Terraform:启动与运行》的书


我认为这是正确的答案。使用模块,对其进行版本控制,并使环境分离。
wrangler

每次您要在不同的Terraform组件/环境/模块/无论是否使用terragrunt或其他一些包装器时,是否都需要重新运行远程配置步骤?
jmreicha

@jmreicha:remote config如果您刚刚签出Terraform配置或想要更改以前的远程配置,则需要运行。Terraform 0.9将引入的概念backends,这将简化很多工作。有关更多详细信息,请参见此PR
Yevgeniy Brikman

以便我了解-我正在环境“阶段”上工作,但随后开始在“产品”上工作。我将需要重新运行remote config命令以指向生产状态。假设每个环境的状态不同。那正确吗?我期待v0.9。
jmreicha

如果要将完全相同的.tf文件集部署到两个不同的环境中,可以,remote config每次切换时都需要运行。显然,这很容易出错,因此我不建议您实际使用此技术。相反,请在此博客文章中查看推荐的Terraform文件布局,以及如何在此博客文章中使用Terraform模块
Yevgeniy Brikman'2

9

以前remote config允许这样做,但现在已被“ 后端 ” 代替,因此不再提供terraform远程。

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

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


每次要在不同的Terraform组件/环境/模块/任何地方上工作时,是否都需要重新配置远程源?
jmreicha

6

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

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

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

但是我也要提交tfstate吗?

没有。

应该在S3之类的地方吗?


2

我知道这里有很多答案,但是我的方法却大不相同。

   Modules
   Environment management 
   Separation of duties

模组

  1. 创建用于逻辑收集资源的模块。示例:如果您的目标是部署需要DB,HA VM,自动缩放,DNS,PubSub和对象存储的API,则所有这些资源都应在单个模块中进行模板化。
  2. 避免创建使用单一资源的模块。这可以并且已经完成,注册表中的许多模块都可以做到这一点,但这是一种有助于资源可访问性而不是架构流程的实践。示例:AWS EC2的模块通过使复杂的配置更易于调用来帮助用户访问EC2,但是类似于1中的示例的模块在编排应用程序,组件或服务驱动的基础架构时可以帮助用户。
    1. 避免在工作空间中声明资源。这更多的是保持代码整洁有序。由于模块易于版本控制,因此您可以更好地控制发行版。

环境管理

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

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

职责分离

如果您在小型组织中或正在运行个人基础结构,那么这实际上并不适用,但是它将帮助您管理操作。

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

这也有助于解决发行方面的问题,因为您会发现一些资源很少更改,而其他资源则始终更改。分离消除了风险和复杂性。

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

CI / CD

这是一个主题,但是Terraform在良好的管道中效果很好。这里最常见的错误是将CI视为灵丹妙药。从技术上讲,Terraform仅应在组装管道的阶段配置基础结构。这将与CI阶段(通常验证和测试模板)所发生的情况不同。

NB写在手机上,所以请原谅任何错误。


0

在答案非常扎实和有用之前,我将尝试在此处添加2美分

构造代码的常见建议

  1. 使用更少的资源可以更轻松,更快捷地进行工作:

    • Cmd terraform plan和Capps terraform都进行云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

0

我相信在使用terraform编排基础结构时几乎没有最佳实践可以遵循

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

处理多种环境

大多数时候,推荐的方法是使用terraform“工作区”来处理多个环境,但是我认为工作区的使用可能会根据组织中的工作方式而有所不同。其他方法是为每个环境(例如,阶段,产品,质量检查)存储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

与环境相关的配置

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

  • dev.backend.tfvar

      region = "ap-southeast-2"
      bucket = "dev-samplebackendterraform"
      key = "dev/state.tfstate"
      dynamo_db_lock = "dev-terraform-state-lock"
  • dev.variable.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


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测试桶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
  • my-tf-test-bucket-us-east-1-preprod
  • 我的tf测试桶我们东部1产品

0

遵循的一些Terraform最佳做法:

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

    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


0

我想为此线程做贡献。

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

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

请访问一个较长的最佳实践博客,该博客详细介绍了此内容,包括CI / CD管道指南。


-1

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

正如Yevgeniy Brikman所 提到的,最好具有模块结构。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.