我正在将我们的基础设施换成 terraform。实际管理 terraform 文件和状态的最佳实践是什么?我意识到它是作为代码的基础设施,我会将我的 .tf 文件提交到 git 中,但我是否也提交 tfstate ?它应该位于 S3 之类的地方吗?我最终希望 CI 能够管理所有这些,但这太过分了,需要我找出文件的移动部分。
我真的只是想看看人们如何在生产中实际使用这种类型的东西
我正在将我们的基础设施换成 terraform。实际管理 terraform 文件和状态的最佳实践是什么?我意识到它是作为代码的基础设施,我会将我的 .tf 文件提交到 git 中,但我是否也提交 tfstate ?它应该位于 S3 之类的地方吗?我最终希望 CI 能够管理所有这些,但这太过分了,需要我找出文件的移动部分。
我真的只是想看看人们如何在生产中实际使用这种类型的东西
我还处于将现有 AWS 基础设施迁移到 Terraform 的状态,因此我的目标是在开发过程中更新答案。
我一直在严重依赖官方的 Terraform示例和多次反复试验来充实我不确定的领域。
.tfstate
文件
Terraform 配置可用于在不同的基础设施上配置许多盒子,每个盒子都可以有不同的状态。因为它也可以由多人运行,所以这个状态应该在一个集中的位置(如 S3),而不是git。
这可以通过查看 Terraform 来确认.gitignore
。
开发者控制
我们的目标是为开发人员提供对基础设施的更多控制,同时保持完整的审计(git log)和健全性检查更改的能力(拉取请求)。考虑到这一点,我的目标是新的基础设施工作流程:
编辑 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
,stg
等prod
)中存储了我们的 VPC 设置、实例创建和对等连接等。
旁注:如您所见,我反对我自己的建议,而不是保留terraform.tfstate
git。在我迁移到 S3 之前,这是一项临时措施,但适合我,因为我目前是唯一的开发人员。
下一步
这仍然是一个手动过程,还没有在 Jenkins 中,但我们正在移植一个相当大、复杂的基础设施,到目前为止一切都很好。就像我说的,很少有错误,但进展顺利!
编辑 2 - 更改
自从我写下这个最初的答案已经快一年了,Terraform 和我自己的状态都发生了显着变化。我现在处于使用 Terraform 管理 Azure 集群的新位置,而 Terraform 现在是v0.10.7
.
状态
人们一再告诉我 state 不应该进入Git - 他们是正确的。我们将此作为依赖开发人员沟通和纪律的两人团队的临时措施。通过一个更大的分布式团队,我们现在可以充分利用 S3 中的远程状态以及DynamoDB 提供的锁定。理想情况下,这将被迁移到 consul,现在它是 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
命令的输出。我们可以检查并知道将运行什么而不会有在阶段plan
和apply
阶段之间发生一些变化的风险(尽管锁定对此有帮助)。请记住删除此计划文件,因为它可能包含纯文本“秘密”变量。
总的来说,我们对 Terraform 非常满意,并会继续学习和改进添加的新功能。
我们大量使用 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
: 实际资源。通常,我们在两个文件夹中定义我们的基础设施:
infrastructure-modules
:此文件夹包含小型、可重复使用的版本控制模块。将每个模块视为如何创建单个基础架构(例如 VPC 或数据库)的蓝图。infrastructure-live
:此文件夹包含实际实时运行的基础架构,它通过组合infrastructure-modules
. 将此文件夹中的代码视为您根据蓝图建造的实际房屋。Terraform 模块就是文件夹中的任意一组 Terraform 模板。例如,我们可能有一个名为vpc
in的文件夹,infrastructure-modules
其中定义了单个 VPC 的所有路由表、子网、网关、ACL 等:
infrastructure-modules
└ vpc
└ main.tf
└ vars.tf
└ outputs.tf
然后,我们可以在其中使用该模块infrastructure-live/stage
并infrastructure-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。因此,如果两个团队成员同时在同一个文件上运行,他们最终可能会覆盖彼此的更改。.tfstate
terraform apply
.tfstate
2020 年编辑:Terraform 现在支持锁定:https ://www.terraform.io/docs/state/locking.html
为了解决这个问题,我们创建了一个名为Terragrunt的开源工具,它是 Terraform 的一个瘦包装器,它使用 Amazon DynamoDB 提供锁定(对于大多数团队来说应该是完全免费的)。查看使用 Terragrunt 将自动远程状态锁定和配置添加到 Terraform 以获取更多信息。
我们刚刚开始了一系列名为“Terraform 综合指南”的博客文章,其中详细描述了我们在现实世界中使用 Terraform 所学到的所有最佳实践。
更新:Terraform 博客文章系列综合指南非常受欢迎,我们将其扩展为一本书,名为Terraform:Up & Running!
@Yevgeny Brikman 进行了更深入的介绍,但专门回答了 OP 的问题:
实际管理 terraform 文件和状态的最佳实践是什么?
将 git 用于 TF 文件。但不要检查状态文件(即 tfstate)。而是Terragrunt
用于将状态文件同步/锁定到 S3。
但我也提交 tfstate 吗?
不。
它应该位于 S3 之类的地方吗?
是的
我知道这里有很多答案,但我的方法完全不同。
⁃ Modules
⁃ Environment management
⁃ Separation of duties
模块
环境管理
IaC 已使 SDLC 流程与基础架构管理相关,期望拥有开发基础架构和开发应用程序环境是不正常的。
职责分离
如果您在一个小型组织或运行个人基础设施,这并不适用,但它会帮助您管理您的操作。
这也有助于解决发布问题,因为您会发现一些资源很少更改,而另一些则一直在更改。分离消除了风险和复杂性。
该策略与 AWS 的多账户策略相似。阅读更多信息。
CI/CD
这是一个独立的主题,但 Terraform 在良好的管道中运行良好。这里最常见的错误是将 CI 视为灵丹妙药。从技术上讲,Terraform 应该只在装配管道的阶段提供基础设施。这与通常验证和测试模板的 CI 阶段发生的事情是分开的。
NB写在手机上,如有错误请见谅。
在答案非常可靠和信息丰富之前,我将尝试在此处添加我的 2 美分
使用较少数量的资源更容易、更快捷:
terraform plan
和terraform
apply 都调用云 API 来验证资源的状态。爆炸半径更小,资源更少:
使用远程状态启动您的项目:
tfstate
文件是一场噩梦。尝试实践一致的结构和命名约定:
使资源模块尽可能简单。
不要对可以作为变量传递或使用数据源发现的值进行硬编码。
使用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 编排基础架构时几乎不需要遵循最佳实践
- 不要再次编写相同的代码(可重用性)
- 将环境配置分开以轻松维护。
- 使用远程后端 s3(加密)和 dynamo DB 来处理并发锁定
- 创建一个模块并在主基础设施中多次使用该模块,它就像一个可重用的函数,可以通过传递不同的参数多次调用。
处理多种环境
大多数时候推荐的方法是使用 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
我不喜欢子文件夹的想法,因为这会导致每个环境产生不同的来源,而且这往往会漂移。
更好的方法是为所有环境(比如说 dev、preprod 和 prod)设置一个堆栈。要在单一环境中工作,请使用terraform workspace
.
terraform workspace new dev
这将创建一个新的工作区。这包括一个专用的状态文件和terraform.workspace
您可以在代码中使用的变量。
resource "aws_s3_bucket" "bucket" {
bucket = "my-tf-test-bucket-${terraform.workspace}"
}
通过这种方式,您将获得调用的存储桶
应用到上面的工作区之后(用于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 地区)
要遵循的一些 Terraform 最佳实践:
避免硬编码:有时开发人员直接手动创建资源。您需要标记这些资源并使用 terraform import 将它们包含在代码中。一个样品:
account_number="123456789012" account_alias="mycompany"
从 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
我想为这个线程做出贡献。
在某些特殊情况下,将需要手动访问 Terraform 状态文件。诸如重构、中断更改或修复缺陷之类的事情将需要运维人员运行 Terraform 状态操作。在这种情况下,使用堡垒主机、VPN 等计划对 Terraform 状态的特殊控制访问。
查看更长的最佳实践博客,其中详细介绍了这一点,包括 CI/CD 管道指南。
如果您仍在寻找更好的解决方案,请查看可以替换维护不同环境文件夹结构的工作区,该文件夹结构可以具有工作区特定变量。
正如Yevgeniy Brikman 所说,最好有一个模块结构。
使用 terraform cloud 管理和保存状态,以及上面的建议。