使用Terraform时的最佳做法

时间:2015-10-15 20:03:53

标签: devops terraform

我正在将我们的基础设施交换成terraform。 实际管理terraform文件和状态的最佳做法是什么? 我将它的基础设施视为代码,并且我将我的.tf文件提交到git中,但我也提交了tfstate吗?它应该驻留在S3这样的地方吗?我最终希望CI能够管理所有这些,但这种情况已经很紧张,需要我找出文件的移动部分。

我真的只是想看看那里的人们是如何在生产中使用这类东西的

12 个答案:

答案 0 :(得分:71)

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

文件布局

我们强烈建议将每个环境(例如stage,prod,qa)的Terraform代码存储在不同的模板集中(因此,单独的.tfstate个文件)。这很重要,这样您的单独环境实际上会在进行更改时彼此隔离。否则,虽然在分期中搞乱了一些代码,但它也很容易在生产中炸掉一些东西。有关原因的详细讨论,请参阅Terraform, VPC, and why you want a tfstate file per env

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

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

舞台VPC的所有Terraform代码都进入stage文件夹,prod VPC的所有代码都进入prod文件夹,以及生活在VPC之外的所有代码(例如IAM用户,SNS主题,S3存储桶)进入global文件夹。

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

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

模块

通常,我们在两个文件夹中定义基础架构:

  1. infrastructure-modules:此文件夹包含小型,可重复使用的版本化模块。将每个模块视为如何创建单个基础架构(如VPC或数据库)的蓝图。
  2. infrastructure-live:此文件夹包含实际的实时运行基础架构,它是通过组合infrastructure-modules中的模块创建的。将此文件夹中的代码视为您根据蓝图构建的实际房屋。
  3. Terraform module只是文件夹中的任何一组Terraform模板。例如,我们可能在vpc中有一个名为infrastructure-modules的文件夹,它定义了单个VPC的所有路由表,子网,网关,ACL等:

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

    然后我们可以在infrastructure-live/stageinfrastructure-live/prod中使用该模块来创建舞台和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(见module sources)。 Git URL的优点是我们可以指定特定的git sha1或标记(ref=v0.0.4)。现在,我们不仅将基础架构定义为一堆小模块,而且我们可以对这些模块进行版本化,并根据需要仔细更新或回滚。

    我们已经创建了许多可重用,经过测试和记录的Infrastructure Packages来创建VPC,Docker群集,数据库等等,而且大多数都只是版本化的Terraform模块。

    国家

    当您使用Terraform创建资源(例如EC2实例,数据库,VPC)时,它会记录有关在.tfstate文件中创建的内容的信息。要对这些资源进行更改,团队中的每个人都需要访问同一个.tfstate文件,但不应将其检入Git(请参阅here for an explanation why)。

    相反,我们建议您通过启用Terraform Remote State在{3}}中存储.tfstate个文件,这会在每次运行Terraform时自动推送/拉取最新文件。确保在您的S3存储桶中enable versioning,以便您可以回滚到较旧的.tfstate文件,以防您以某种方式损坏最新版本。但是,一个重要的注意事项: Terraform不提供锁定。因此,如果两个团队成员同时在同一个terraform apply文件上运行.tfstate,则他们最终可能会覆盖彼此的更改。

    为了解决这个问题,我们创建了一个名为Terragrunt的开源工具,它是Terraform的一个瘦包装器,它使用Amazon DynamoDB提供锁定(大多数团队应该完全免费)。查看Add Automatic Remote State Locking and Configuration to Terraform with Terragrunt了解详情。

    进一步阅读

    我们刚刚开设了一系列名为A Comprehensive Guide to Terraform的博客文章,详细介绍了我们在现实世界中使用Terraform所学到的所有最佳做法。

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

答案 1 :(得分:68)

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

我一直非常依赖官方的Terraform examples和多次试错,以充实我不确定的领域。

.tfstate个文件

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

可以通过Terraform .gitignore确认这一点。

开发者控制

我们的目标是为开发人员提供更多的基础架构控制,同时保持完整的审计(git日志)和完整性检查更改(拉取请求)的能力。考虑到这一点,我的目标是:

  1. 常见AMI的基础,包括可重复使用的模块,例如:木偶。
  2. DevOps使用Terraform提供的核心基础架构。
  3. 开发人员根据需要在Git中更改Terraform配置(实例数;新VPC;添加区域/可用区等)。
  4. 推送Git配置并提交拉取请求以便由DevOps小组成员检查。
  5. 如果获得批准,请调用webhook到CI进行构建和部署(此时不确定如何对多个环境进行分区)
  6. 编辑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
    

    每个人设置一些理智的默认值,但将它们公开为可以被我们的&#34;胶水&#34;覆盖的变量。

    <强>胶

    我们有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.tfstate保留在git中。这是一个临时措施,直到我转到S3但适合我,因为我目前是唯一的开发人员。

    后续步骤

    这仍然是一个手动过程而不是詹金斯,但我们正在移植一个相当大的,复杂的基础设施,到目前为止一直很好。就像我说的那样,很少有错误,但进展顺利!

    编辑2 - 更改

    我写这个初步答案已经快一年了,Terraform和我的状态都发生了很大的变化。我现在处于一个新的位置,使用Terraform管理Azure群集,Terraform现在是v0.10.7

    <强>国家

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

    <强>模块

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

    文件结构

    新职位的分类更为简单,只有两个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阶段之间发生某些变化(尽管锁定有助于此)。请记住删除此计划文件,因为它可能包含纯文本&#34; secret&#34;变量。

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

答案 2 :(得分:9)

以前remote config允许这样做,但现在已被&#34; backends&#34;取代,因此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

有关详细信息,请参阅docs

答案 3 :(得分:4)

由@Yevgeny Brikman更深入地介绍,但特别回答OP的问题:

  

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

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

  

但我也提交tfstate吗?

没有。

  

它应该存在于S3这样的地方吗?

答案 4 :(得分:1)

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

⁃   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阶段(通常验证和测试模板)所发生的情况不同。

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

答案 5 :(得分:0)

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

Yevgeniy Brikman mentioned一样,最好具有模块结构。

答案 6 :(得分:0)

  

在回答非常扎实和有益的内容之前,我将尝试添加   我的2分钱在这里

关于代码构造的常见建议

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

    • Cmds terraform planterraform都应用make Cloud API调用来验证资源状态。
    • 如果您将整个基础架构包含在一个组合中,则可能要花费几分钟(即使您在同一文件夹中有多个文件)。
  2. 爆炸半径较小,资源较少:

    • 通过将无关的资源放在不同的组合物(文件夹)中来彼此隔离,以减少发生问题时的风险。
  3. 使用远程状态启动项目:

  4. 尝试练习一致的结构和命名约定:

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

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

  7. 使用data来源和terraform_remote_state作为组合中基础结构模块之间的粘合剂。

参考文章: https://www.terraform-best-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

答案 7 :(得分:0)

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

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

处理多个环境

大多数时候,建议的方法是使用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 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

答案 8 :(得分:0)

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

更好的方法是为所有环境使用单个堆栈(让我们说dev,preprod和prod)。要在单个环境上工作,请使用terraform workspace

terraform workspace new dev

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

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

通过这种方式,您将获得名为的存储桶

  • my-tf-test-bucket-dev
  • my-tf-test-bucket-preprod
  • my-tf-test-bucket-prod
应用于上述工作区后,请

(使用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}"
}

获得(针对美国东部1地区)

  • my-tf-test-bucket-us-east-1-dev
  • my-tf-test-bucket-us-east-1-preprod
  • my-tf-test-bucket-us-east-1-prod

答案 9 :(得分: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

答案 10 :(得分:0)

我想为这个话题做贡献。

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

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

检查longer best practices blog,其中涵盖了这一点,包括CI / CD管道指南。

答案 11 :(得分:-1)

使用地形云管理和保存状态,以及上面的建议。