使用 Terraform 的最佳实践

我正在把我们的基础设施改造成地球环境。 实际管理地形文件和状态的最佳实践是什么? 我意识到这是基础设施代码,我会提交我的。Tf 文件转换成 git,但是我是否也提交 tfstate?它应该存在于 S3之类的地方吗?我希望最终由 CI 来管理这一切,但是这太牵强了,需要我找出文件的移动部分。

我真的只是想看看人们是如何在生产中利用这些东西的

42931 次浏览

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

我一直严重依赖于官方的地形 例子和多重试错,以充实的领域,我一直不确定。

.tfstate文件

Terraform 配置可以用来在不同的基础设施上提供许多机顶盒,每个机顶盒可以有不同的状态。由于它也可以由多个人运行,这种状态应该在一个集中的位置(如 S3) ,但 没有 git。

这可以通过观察地形 .gitignore得到证实。

开发者控制

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

  1. 常用 AMI 的基础,包括可重用的模块,例如木偶。
  2. DevOps 使用 Terraform 提供的核心基础设施。
  3. 开发人员根据需要改变 Git 中的 Terraform 配置(数量、新的 VPC、添加区域/可用性区域等)。
  4. 推送 Git 配置并提交一个 pull 请求,由 DevOps 小组的一名成员进行健全性检查。
  5. 如果获得批准,调用 webhook 到 CI 来构建和部署(不确定此时如何划分多个环境)

编辑1-更新当前状态

自从开始回答这个问题以来,我已经写了很多 TF 代码,并且在我们的事务状态中感觉更舒服。我们一路上遇到了一些 bug 和限制,但我承认这是使用新的、快速变化的软件的一个特点。

布局

我们有一个复杂的 AWS 基础设施与多个 VPC 的每个与多个子网。轻松管理它的关键是定义一个灵活的分类法,包括区域、环境、服务和所有者,我们可以用它来组织我们的基础设施代码(包括地形和木偶)。

模组

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

下一步

这仍然是一个手工过程,而不是在 Jenkins 中,但是我们正在移植一个相当大的、复杂的基础设施,到目前为止还不错。就像我说的,没什么问题,但是很顺利!

编辑2-更改

从我写出这个初始答案到现在已经差不多一年了,Terraform 和我自己的状态都发生了显著的变化。我现在在一个新的位置使用 Terraform 来管理 Azure 集群,Terraform 现在是 v0.10.7

国务院

人们反复告诉我应该在 Git 中使用 没有——他们是正确的。我们将此作为一个临时措施,由两个人组成的团队依赖于开发人员的沟通和纪律。通过一个更大的分布式团队,我们现在可以充分利用 S3中的远程状态和 DynamoDB 提供的 锁定。理想情况下,这将被迁移到领事现在是1.0版本,以削减跨云供应商。

模组

以前我们创建并使用内部模块。这仍然是情况,但随着 地形注册表的出现和增长,我们试图使用这些至少作为一个基础。

文件结构

新的位置有一个简单得多的分类法,只有两个 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阶段之间没有发生一些变化的风险的情况下将运行什么(尽管锁定对此有所帮助)。请记住删除该计划文件,因为它可能包含纯文本“ secret”变量。

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

以前的 remote config允许这样做,但现在已经被“ 后端”取代,所以地形遥控器不再可用。

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,我们的推荐设置如下:

文件布局

我们强烈建议将每个环境的 Terraform 代码(例如 stage、 prod、 qa)存储在单独的模板集中(因此,单独的 .tfstate文件)。这一点很重要,这样在进行更改时,各个独立的环境实际上是相互隔离的。否则,在准备阶段搞乱一些代码的同时,也很容易在电击中引爆某些东西。有关原因的多彩讨论,请参见 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个文件:

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

模组

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

  1. infrastructure-modules: 此文件夹包含小型的、可重用的、版本化的模块。可以将每个模块视为如何创建单个基础设施(如 VPC 或数据库)的蓝图。
  2. infrastructure-live: 该文件夹包含实际运行的基础设施,它通过组合 infrastructure-modules中的模块创建这些基础设施。将这个文件夹中的代码想象成您根据蓝图建造的实际房屋。

地形模块只是文件夹中的一组 Terraform 模板。例如,我们可能在 infrastructure-modules中有一个名为 vpc的文件夹,它定义了一个 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 (参见 模块源)。GitURL 的优点是我们可以指定特定的 gitsha1或标记(ref=v0.0.4)。现在,我们不仅将基础设施定义为一堆小模块,而且还可以对这些模块进行版本控制,并根据需要进行仔细的更新或回滚。

我们已经为创建 VPC、 Docker 集群、数据库等创建了大量可重用、测试和文档化的 基础设施一揽子计划,其中大多数只是版本化的 Terraform 模块。

国务院

当您使用 Terraform 创建资源(例如 EC2实例、数据库、 VPC)时,它会在 .tfstate文件中记录所创建内容的信息。要对这些资源进行更改,团队中的每个人都需要访问这个相同的 .tfstate文件,但是您不应该将其签入 Git (参见 来解释一下为什么)。

相反,我们建议通过启用 地球形态遥远的国家在 S3中存储 .tfstate文件,这将在每次运行 Terraform 时自动推送/拉取最新文件。请确保在您的 S3桶中使用 启用版本控制,以便您可以回滚到旧的 .tfstate文件,以防您以某种方式损坏了最新版本。然而,一个重要的注意事项是: Terraform 不提供锁定。因此,如果两个团队成员在同一个 ABC0文件上同时运行 terraform apply,他们最终可能会覆盖对方的更改。

编辑2020 : Terraform 现在支持锁定: < a href = “ https://www.Terraform.io/docs/state/locking.html”rel = “ noReferrer”> https://www.Terraform.io/docs/state/locking.html

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

进一步阅读

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

更新: Terraform 博客系列的综合指南变得如此流行,以至于我们把它扩展成了一本名为 Terraform: Up & Running 地形: 向上运行 的书!

作者@Yevgeny Brikman 对此进行了更深入的报道,但特别回答了观察员的问题:

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

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

但是我是不是也犯了罪?

没有。

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

是的

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

⁃   Modules
⁃   Environment management
⁃   Separation of duties

模组

  1. 为资源的逻辑集合创建模块。 示例: 如果您的目标是部署一个 API,它需要 DB、 HA VM、自动伸缩、 DNS、 PubSub 和对象存储,那么所有这些资源都应该在一个模块中进行模板化。
  2. 避免创建使用单一资源的模块。注册中心的许多模块都可以这样做,而且已经这样做了,但这种做法有助于资源的可访问性,而不是基础设施编排。 示例: AWS EC2的模块通过使复杂的配置更容易调用,但是使用类似于1中示例的模块来帮助用户访问 EC2。协助用户编排应用程序、组件或服务驱动的基础设施。
    1. 避免在工作区中进行资源声明。这更多的是关于保持代码的整洁和组织。由于模块很容易进行版本控制,因此您可以对发布版本进行更多的控制。

环境管理

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

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

职责分离

如果你在一个小型组织或运行个人基础设施,这并不真正适用,但它将帮助你管理你的业务。

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

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

该策略与 AWS 的多帐户策略相似。

CI/CD

这是一个自己的主题,但 Terraform 在一个良好的管道中工作得非常好。这里最常见的错误是将 CI 视为一种灵丹妙药。从技术上讲,Terraform 应该只在装配流水线的各个阶段提供基础设施。这与 CI 阶段的情况是分开的,在 CI 阶段,人们通常会验证和测试模板。

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

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

作为 叶夫根尼 · 布里克曼 提到,最好有一个模块结构。

在答案变得非常扎实和信息丰富之前,我将试着补充一点 这是我的两分钱

构造代码的常见建议

  1. 使用数量较少的资源更容易、更快地开展工作:

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

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

  4. 试着练习一个一致的结构和变数命名原则:

    • 像过程代码一样,地形改造代码应该先写给人们阅读,一致性将有助于六个月后发生变化。
    • 地形状态文件中移动资源是可能的,但是如果您的结构和命名不一致,那么移动资源可能会更困难。
  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

我相信,在使用 terraform 协调基础设施时,几乎没有什么最佳实践需要遵循

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

处理多个环境

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

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

我遵循了一些不同的方法来处理和避免重复相同的地形代码保持在每个环境文件夹,因为我相信大多数时间所有的环境将是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"
    

Conditional skipping of infrastructure part

Create a configuration in env specific variable file and based on that variable decide to create or skipping that part. In this way based on need the specific part of the infrastructure can be skipped.

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

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

更好的方法是为所有环境(比如 dev、 preprod 和 prod)建立一个单独的堆栈。使用 terraform workspace在单一环境中工作。

terraform workspace new dev

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

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

通过这种方式,您将被调用 bucket

  • My-tf-test-bucket-dev
  • 我的,我的,我的,我的,我的,我的,我的,我的,我的,我的,我的,我的
  • 我的天啊

在应用到上面的工作区之后(使用 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
  • 我的 TFTTBuckusEast-1-Preprod
  • 我的天啊

一些 Terraform 的最佳实践:

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

    Account _ number = “123456789012” Account _ alias = “ mycompany”

  2. 从一个码头集装箱运行 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

我想为这个帖子做点贡献。

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

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

检查一个 更长的最佳实践博客,详细介绍这一点,包括 CI/CD 管道的指导方针。

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