我正在将现有的AWS基础架构迁移到Terraform上,因此在开发过程中会更新答案。
我一直在依赖官方的Terraform 示例和多次尝试来解决我不确定的领域。
.tfstate
文件
Terraform配置可以用于在不同的基础架构上提供许多盒子,每个盒子都可能有不同的状态。由于它也可以由多个人运行,因此这个状态应该位于集中位置(如S3),但是不要放在git中。
这可以通过查看Terraform .gitignore
来确认。
开发者控制
我们的目标是为开发人员提供更多的基础架构控制权,同时保持完整的审计(git log)和检查更改的能力(pull requests)。考虑到这一点,我正在努力实现新的基础架构工作流程:
编辑1-当前状态更新
自开始回答以来,我已经编写了大量TF代码,并对我们的情况感到更加舒适。我们一路上遇到了错误和限制,但我接受这是使用新的、快速变化的软件的特点。
布局
我们有一个复杂的AWS基础架构,每个VPC都有多个子网。方便管理的关键是定义一个灵活的分类法,包括区域、环境、服务和所有者,我们可以用它来组织我们的基础设施代码(包括terraform和puppet)。
模块
下一步是创建一个单独的git存储库来存储我们的terraform模块。我们的模块顶层目录结构如下:
tree -L 1 .
结果:
├── README.md
├── aws-asg
├── aws-ec2
├── aws-elb
├── aws-rds
├── aws-sg
├── aws-vpc
└── templates
.
├── 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
.tf
文件来提供全局资源(如IAM角色);接下来是区域级别,包括EC2 SSH公钥;最后,在我们的环境中(例如dev
,stg
,prod
等),存储了我们的VPC设置、实例创建和对等连接等内容。
附注:正如您所看到的,我违背了自己的建议,将terraform.tfstate
保存在git中。这只是一个临时措施,直到我转移到S3,但它适合我,因为我目前是唯一的开发人员。
下一步
这仍然是一个手动过程,尚未在Jenkins中实现,但我们正在移植一个相当大而复杂的基础架构,目前还不错。像我说的那样,有一些错误,但进展顺利!
编辑2-更改
距离我写下这个初始答案已经快一年了,Terraform和我自己的状况都发生了很大的变化。我现在在一个新职位上使用Terraform来管理Azure集群,而Terraform现在是v0.10.7
。
状态
人们一再告诉我,状态不应该进入Git - 他们是正确的。我们将其用作两个人团队的临时措施,这个团队依赖于开发者的沟通和纪律性。现在,我们正在使用由DynamoDB提供的锁定完全利用S3中的远程状态来管理一个更大的分布式团队。理想情况下,现在它已经是v1.0,我们将把它迁移到consul以削减跨云提供商的成本。 模块 以前,我们创建并使用内部模块。虽然仍然如此,但随着Terraform注册表的出现和增长,我们尝试使用它们至少作为基础。 文件结构 新的职位有一个更简单的分类法,只有两个infrastructure环境-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非常满意,并继续学习和改进新功能。
stage
└ main.tf
└ vars.tf
└ outputs.tf
prod
└ main.tf
└ vars.tf
└ outputs.tf
global
└ main.tf
└ vars.tf
└ outputs.tf
stage
文件夹中,所有生产VPC的代码都放在prod
文件夹中,而所有不属于VPC的代码(例如IAM用户、SNS主题、S3存储桶)都放在global
文件夹中。vars.tf
:输入变量。outputs.tf
:输出变量。main.tf
:实际资源。infrastructure-modules
:此文件夹包含小型、可重用、有版本控制的模块。将每个模块视为创建单个基础设施部件(如VPC或数据库)的蓝图。infrastructure-live
:此文件夹包含实际正在运行的基础设施,它通过组合infrastructure-modules
中的模块来创建。将此文件夹中的代码视为从蓝图建造的实际房屋。一个Terraform模块就是指包含在文件夹中的一组Terraform模板。例如,我们可能有一个名为vpc
的文件夹在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
)。现在,我们不仅将基础设施定义为一堆小模块,而且可以对这些模块进行版本控制,并根据需要进行仔细的更新或回滚。.tfstate
文件中创建的信息。要对这些资源进行更改,团队中的每个人都需要访问同一个.tfstate
文件,但是你不应该将其提交到Git中(请参见此处的解释)。.tfstate
文件存储在S3中,每次运行Terraform时都会自动推送/拉取最新的文件。确保在S3存储桶中启用版本控制,以便在某种情况下损坏最新版本时可以回滚到旧的.tfstate
文件。 .tfstate
文件上运行terraform apply
,他们可能会覆盖彼此的更改。编辑2020年:Terraform现在支持锁定功能:https://www.terraform.io/docs/state/locking.html
为了解决这个问题,我们创建了一个名为Terragrunt的开源工具,它是Terraform的轻量级封装,使用Amazon DynamoDB提供锁定功能(对于大多数团队来说应该完全免费)。查看Add Automatic Remote State Locking and Configuration to Terraform with Terragrunt以获取更多信息。remote config
. Terraform 0.9将会引入“后端”这一概念,这将大大简化操作。有关更多详细信息,请参见此PR. - Yevgeniy Brikmaninputs.tf
来处理输入变量。但是,在一个项目中保持一致性是很好的,这样当你在模块之间跳转时,你就知道它们得到了什么,并且暴露了什么。 - Aldo 'xoen' Giambelluca在@ Yevgeny Brikman的更深入介绍中已经涉及,但是特别回答OP的问题:
管理terraform文件和状态的最佳实践是什么?
使用git来处理TF文件。但不要检查State文件(即tfstate)。而是使用Terragrunt
将状态文件同步/锁定到S3。
我需要提交tfstate吗?
不需要。
它应该保存在类似于S3的地方吗?
是的
⁃ Modules
⁃ Environment management
⁃ Separation of duties
模块
环境管理
IaC 已经使得 SDLC 过程与基础设施管理相关联,因此我们希望拥有开发基础设施和开发应用程序环境已经变得正常。
职责分离
如果您在一个小组织或运行个人基础设施,则不太适用,但它将有助于您管理操作。
这也有助于释放关注点,因为您会发现一些资源很少更改,而其他资源则经常更改。职责分离可消除风险和复杂性。
这种策略与 AWS 的多帐号策略相似。阅读更多信息。
这是一个独立的主题,但Terraform在良好的流水线内运作得非常好。最常见的错误是将CI视为一种万能解决方案。从技术上讲,Terraform只应在组装流水线的阶段中提供基础架构。这将与CI阶段中发生的内容分开,通常在这些阶段中验证和测试模板。
N.B. 在手机上编写,请原谅任何错误。
我不喜欢子文件夹的想法,因为这会导致每个环境有不同的源代码,很容易出现偏差。
更好的方法是为所有环境(比如开发、预生产和生产)使用单一堆栈。要在单个环境中工作,请使用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 状态操作来运行。对于这种情况,计划使用堡垒机、VPN 等进行特别控制的 Terraform 状态访问。
查看更长的最佳实践博客,其中详细介绍了 CI/CD 流水线的指导方针。
使用更少的资源可以更轻松、更快速地工作:
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 的最佳实践:
避免硬编码: 有时开发人员会直接手动创建资源。您需要标记这些资源并使用 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编排基础架构时需要遵循一些最佳实践:
- 不要重复编写相同的代码(可重用性)
- 将环境配置保持分离以便于维护。
- 使用远程后端s3(加密)和dynamo DB来处理并发锁定
- 创建一个模块并在主基础架构中多次使用该模块,就像可重用函数一样,可以通过传递不同的参数多次调用它。
处理多个环境
大多数情况下,推荐的方法是使用terraform的“工作区”来处理多个环境,但我认为工作区的使用可能会因组织的工作方式而异。 另一种方法是将每个环境(例如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
与环境相关的配置
将与环境相关的配置和参数分别保存在一个变量文件中,并将该值传递以配置基础设施。例如:
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"
基础设施部分的条件跳过
在特定环境变量文件中创建配置,并根据该变量决定是否创建或跳过该部分。通过这种方式,可以根据需要跳过基础设施的特定部分。
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"
}
以下命令用于初始化和执行每个环境的基础设施更改,请切换到所需环境文件夹。
terraform init -var-file=dev.variables.tfvar -backend-config=dev.backend.tfvar ../../deployment/
terraform apply -var-file=dev.variables.tfvar ../../deployment
.tfstate
文件是一个非常糟糕的想法,应该避免。如果部署并忘记检查您的.tfstate
文件,可能会破坏整个基础架构。您的状态文件应存储在S3或官方文档中描述的其他后端中。 - jottr