Answers:
我也处于将现有的AWS基础架构迁移到Terraform的状态,因此将在我开发时致力于更新答案。
我一直严重依赖于官方的Terraform 示例以及多次尝试和错误来充实我不确定的区域。
.tfstate
档案
Terraform配置可用于在不同的基础架构上置备许多盒子,每个盒子可能具有不同的状态。由于它也可以由多个人运行,因此该状态应位于集中位置(例如S3),而不是 git。
查看Terraform可以确认这一点.gitignore
。
开发者控制
我们的目标是向开发人员提供对基础架构的更多控制,同时保持完整的审核(git日志)和健全检查更改(拉动请求)的能力。考虑到这一点,我旨在实现的新基础架构工作流程是:
编辑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
。
州
人们一再告诉我的状态应该不会在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
命令的输出。我们可以检查并知道将运行什么,而不会在plan
和apply
阶段之间进行任何更改(尽管锁定对此有所帮助)。切记删除此计划文件,因为它可能包含纯文本“秘密”变量。
总体而言,我们对Terraform感到非常满意,并继续通过添加的新功能来学习和改进。
我们大量使用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
:实际资源。通常,我们在两个文件夹中定义基础架构:
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
创建舞台和产品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:启动与运行》的书!
remote config
如果您刚刚签出Terraform配置或想要更改以前的远程配置,则需要运行。Terraform 0.9将引入的概念backends
,这将简化很多工作。有关更多详细信息,请参见此PR。
remote config
命令以指向生产状态。假设每个环境的状态不同。那正确吗?我期待v0.9。
.tf
文件集部署到两个不同的环境中,可以,remote config
每次切换时都需要运行。显然,这很容易出错,因此我不建议您实际使用此技术。相反,请在此博客文章中查看推荐的Terraform文件布局,以及如何在此博客文章中使用Terraform模块。
以前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
有关详细信息,请参阅文档。
我知道这里有很多答案,但是我的方法却大不相同。
⁃ Modules
⁃ Environment management
⁃ Separation of duties
模组
环境管理
IaC已使SDLC流程与基础架构管理相关,并且期望拥有开发基础架构以及开发应用程序环境是不正常的。
职责分离
如果您在小型组织中或正在运行个人基础结构,那么这实际上并不适用,但是它将帮助您管理操作。
这也有助于解决发行方面的问题,因为您会发现一些资源很少更改,而其他资源则始终更改。分离消除了风险和复杂性。
该策略与AWS的多账户策略具有相似之处。阅读更多信息。
CI / CD
这是一个主题,但是Terraform在良好的管道中效果很好。这里最常见的错误是将CI视为灵丹妙药。从技术上讲,Terraform仅应在组装管道的阶段配置基础结构。这将与CI阶段(通常验证和测试模板)所发生的情况不同。
NB写在手机上,所以请原谅任何错误。
在答案非常扎实和有用之前,我将尝试在此处添加2美分
使用更少的资源可以更轻松,更快捷地进行工作:
terraform plan
和Capps terraform
都进行云API调用以验证资源状态。爆炸半径较小,资源较少:
使用远程状态启动项目:
tfstate
在git中管理文件是一场噩梦。尝试练习一致的结构和命名约定:
使资源模块尽可能简单。
不要硬编码可以作为变量传递或使用数据源发现的值。
使用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“工作区”来处理多个环境,但是我认为工作区的使用可能会根据组织中的工作方式而有所不同。其他方法是为每个环境(例如,阶段,产品,质量检查)存储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
我不喜欢子文件夹的想法,因为这将导致每个环境的源不同,并且这往往会漂移。
更好的方法是针对所有环境使用单个堆栈(让我们说一下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导入将它们包括在代码中。一个样品:
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状态的非凡控制访问。
如果您仍在寻找更好的解决方案,请查看可以代替维护不同环境文件夹结构的工作区,这些文件夹可以具有工作区特定的变量。
正如Yevgeniy Brikman所 提到的,最好具有模块结构。