在我们开始导入资源之前,我需要吐槽一下当前工具的状况。 如果你不关心这个,可以点击这里直接跳到教程部分。
多年来,我一直从项目一开始就编写基础设施即代码(IaC)。
当我加入一个通过"点击操作"管理基础设施的团队时,我通常会使用 terraform import
来导入我遇到的云资源及其相关基础设施。这很繁琐,很烦人,但是有效。
我很兴奋能深入研究这个主题。我以前从未使用过从现有基础设施生成基础设施即代码的"反向 Terraform"工具,而这是我们许多客户的常见要求。
但是,我有一个坏消息要告诉你:
如果你认为你找到了处理"点击操作"债务的灵丹妙药,那么"反向 Terraform"工具和流程简直糟糕透顶。
如果你不熟悉 Terraform 和云™️,这很难做到 - 如果你发现自己有一堆点击操作并正在阅读这篇文章,你可能就不熟悉。
如果你有云™️和 Terraform 的经验,我发现从头开始编写 Terraform 并使用 terraform import
导入比使用任何"反向 Terraform"工具都更容易。
我建议你穿越时空,从一开始就使用 IaC,但如果你不能穿越时空,我提前道歉:欢迎来到地狱。
反向 Terraform 工具的旋风之旅
啊,"反向 Terraform",解决基础设施债务的灵丹妙药。
我觉得我们称之为"反向 Terraform"很有趣。我知道这是因为命名很难,但如果你考虑代码之外的反向地球改造会是什么样子,那将是破坏地球的宜居性。
纯粹的末日。抱歉,需要先定个基调。
许多工具声称可以完成这项工作,而且开发人员就是开发人员,我假设在我写完这篇文章的时候会有 6 个更半成品的想法。
一些比较知名的:
terraforming[1]
最原始的工具,当然已经不再维护并归档了。
README 建议使用两个已经死亡的工具:terraformer 和 terracognita。下面会讨论这两个。
terracognita[2]
这是一个"后起之秀"。它已经开发了几年,但不知何故感觉像是上周刚 git init
的。
它支持所有云的最小资源集。
对于 AWS,它支持 127 种资源。AWS 有超过 200 种服务[3] ,每种服务都有_许多资源_。从整体来看,Terracognita 提供的覆盖范围非常有限。
对于任何复杂的基础设施,如果你使用 terracognita,你还需要使用 terraform import
。
我试图导入一个相当基本的模块(我们的 AWS VPC 模块[4] ),网络级别不支持的资源数量令人惊讶。
terracognita aws \ -i aws_iam_role \ -i aws_iam_role_policy_attachment -i aws_internet_gateway -i aws_eip -i aws_nat_gateway \ -i aws_route_table -i aws_subnet -i aws_vpc \ --hcl=terracognita-output \ --aws-default-region=us-west-2 \ --tags=md-package:tea-staging-network-jn09
- 没有流日志
- 没有 KMS(完全没有!?!?)
- 没有日志组
它支持路由表!!11! 但不支持默认表、路由或路由表关联。
我还尝试导入 AWS Aurora PostgreSQL 数据库,但它认为这是 AWS Neptune 集群。
resource "aws_neptune_cluster" "tea_staging_database_z0x2" { tags = { managed-by = "massdriver" md-manifest = "database" md-package = "tea-staging-database-z0x2" md-project = "tea" md-target = "staging" } tags_all = { managed-by = "massdriver" md-manifest = "database" md-package = "tea-staging-database-z0x2" md-project = "tea" md-target = "staging" } availability_zones = ["us-west-2c", "us-west-2b", "us-west-2a"] backup_retention_period = 1 cluster_identifier = "tea-staging-database-z0x2" copy_tags_to_snapshot = true engine = "aurora-postgresql" engine_version = "14.6" kms_key_arn = "arn:aws:kms:us-west-2:YEAH_RIGHT_DAWG:key/33b8e85-b47b-4a2d-a188-db22320eba16" neptune_cluster_parameter_group_name = aws_db_parameter_group.default_aurora_postgresql14.id neptune_subnet_group_name = "tea-staging-database-z0x2" port = 5432 preferred_backup_window = "12:05-12:35" preferred_maintenance_window = "sun:06:56-sun:07:26" storage_encrypted = true vpc_security_group_ids = [aws_security_group.sg_0845903aac706f466.id] }
这不是一个严肃的工具。aws2tf[5] , aztfy / aztfexport[6] 等有很多_特定云_的工具可以将特定云的资源转换为 Terraform。它们存在与下面 terraformer
相同的问题,但还有一个额外的弱点,就是只支持一种云。哎。
terraformer[7]
Terraformer 是本教程将重点介绍的工具。它是"最好的"。
由 Waze 团队构建,但就像在高峰时段发现自己在一个随机的社区里试图穿过四车道而没有红绿灯或停车标志一样,Terraformer 让你很容易被撞到。
从现有云资源生成 Terraform / OpenTofu
注意:本教程中的所有内容都适用于 Terraform 和 OpenTofu。你可以随意使用你选择的工具。你需要将看到的任何 terraform
替换为 tofu
。
Terraformer 还_不错_。
它创建 terraform 代码和状态文件,这是至关重要的。它还支持 AWS、GCP、Azure 以及许多其他 云[8] 。它还支持大量 AWS 资源[9] 。
这就是它的优点。称 terraformer 还不错有点像在 Yelp 上给一家餐厅打 5 星,因为他们把食物放在盘子里端上来。这是一个很低的标准。
入门
从 源代码[10] 安装或直接安装软件包:
brew install terraformer sudo port install terraformer choco install terraformer
我将使用 AWS 资源作为示例( 支持的资源在这里[8] ),但这个演练也适用于 Azure 或 GCP。
生成 Terraform
导入所有 Terraform 资源
从所有现有资源生成 IaC 是用户希望这些工具提供的体验,一个命令,魔法。这确实可以做到,但它_不会_为你的成功做好准备。
导入所有资源也是一个非常缓慢的过程,因为 Terraform 将调用每个云服务的列表/获取 API。
一个重要的注意事项:在所有 *
上运行这个命令会为_每个_云服务创建一个 terraform 目录,而不仅仅是你使用的那些。该目录将有两个无关紧要的文件:providers.tf
和 terraform.tfstate
。我建议删除只有这两个文件的任何目录,以防止你浪费(更多)时间。
terraformer import aws --resources=* --regions=us-west-2
按服务分组的整个区域的 Terraform 资源不是组织代码的好方法,并且使连接 terraform 模块和强制执行奇偶校验变得极其困难。下面,你会看到我的子网在与 VPC 不同的模块中。我建议合并这两个状态文件和模块,并对代码进行大量重构,以使其准备好用于生产 CI/CD 管道。
即使我试了也想不出更糟糕的组织策略。
如果你的云账户中资源很少,导入所有资源可能是一个好的开始;对于了解所有云资产来说,这是一种_不错_的方法,但我会考虑将代码丢弃(很多代码都会_很糟糕_,但我们稍后会讨论这一点)。
按资源类型导入 Terraform 资源
另一个选择是按资源类型生成 IaC。如果你有任何云服务的多个实例,这种方法不是一个很好的选择。
例如,假设你有两个 postgres 数据库:staging 和 prod。结果将是每个资源的一个 terraform 模块。为了在环境之间强制执行奇偶校验,我们希望有一个带有 staging
和 production
工作区/变量的单一模块。
terraformer import aws --resources=vpc,subnet --regions=us-west-2 --path-pattern {output}
在这个例子中,我添加了 --path-pattern {output}
,这将把所有生成的代码放入一个单一模块中。
在反向 Terraforming 或一般编写 IaC 时,你应该考虑单一模块用例。省略路径模式会导致每种资源类型一个目录,如下所示:
嗯
如果你在云中的资源_非常_少,按资源类型导入是一个好方法。当你只有一个给定服务的实例时,这是最佳选择,例如,一个网络,一个 aurora 数据库,一个 ETL 管道等。在运行这个例子时,我在同一个模块中有 5 个 VPC。这生成的代码并不理想,因为它将所有 VPC 的生命周期绑定在我的 CI/CD 管道中。要向一个 VPC 添加子网,我最终必须读取所有 VPC。这是一个很大的影响范围。
按资源 ID 导入 Terraform 资源
按资源 ID 生成 IaC 将产生_目前为止_最好的模块结构。但是,你仍然需要回溯你的模块以支持任何额外的服务实例(如上面的 staging/prod 示例)。
当你_多次_使用一个服务用于不同的用例时,这种方法非常好。
例如,为长期存储配置的 redis 集群与用于页面缓存的 redis 集群。这是两个非常不同的用例,需要两个不同的 terraform 模块,向你的团队公开非常不同的变量接口。
注意: 常见的是看到围绕没有用例设计的贫血 terraform 模块,而只是在一些 Terraform 资源之上创建另一个抽象/接口。一个明显的指标是一个变量文件有 10 个甚至 100 多个设置。在 Massdriver,IaC 模块应该设计为特定用例,并将操作专业知识_内置_到模块中。我们认为团队不应该有一个"s3 存储桶"模块,而应该有一个"日志存储桶模块"、"cdn 存储桶模块"和"数据湖存储桶模块",每个模块都有精心策划的变量接口,这样工程师可以快速配置,而不会陷入弄清楚数十到数百个字段的困境。
terraformer import aws --resources=vpc,subnet --filter=vpc=myvpcid --regions=us-west-2 --path-pattern {output}
如果你有很多云资源,这种方法非常耗时。你必须进入 AWS 控制台,找到所有 ID,弄清楚它们是如何组合在一起的,然后运行导入。
有趣的事实 在我们继续之前,要说明"导入所有"命令非常慢。我已经写到这里了,它还在运行。耶。
按标签导入 Terraform 资源
这是导入资源并获得良好的基线 terraform 模块集以开始你的 IaC 之旅的最有效方法。我想明确一点,运行这个命令_让你开始_,你仍然有很多工作要做。
这种方法的一个警告是,你必须有一个在云中标记资源的良好流程。如果你没有一直按环境、目的、项目等一致地标记你的资源,那么这种方法对你不起作用。
在这个例子中,我正在导入我们的 AWS VPC 模块[11] 的资源。
当我们设计我们的基础设施时,我们非常注重标记,我们认为像 cloudwatch 警报和通知通道这样的东西是我们部署的基础设施(例如 vpc)的一部分。
这确实很好地创建了一个基线 terraform 模块,包含我们期望的所有组件。再次强调,这对我们有效是因为我们在标记约定上保持一致。下面的 md-package
是我们分配给所有属于单个"用例"的云资源的唯一标识符。
terraformer import aws \ --resources=vpc,subnet,igw,eip,route_table,sns,nat,cloudwatch \ --filter="Name=tags.md-package;Value=tea-staging-network-jn09" \ --regions=us-west-2 \ --path-output=generated \ --path-pattern="{output}"
你仍然需要做的工作
无论你采取什么方法从现有云资源生成 terraform,前面都会有不少坎坷。
tag
, tag_all
& default_tags
如果你熟悉 AWS Terraform 提供程序,它支持所有资源的"default_tags"概念。使用这个工具时不会使用这种方法,而是在每个资源下重复两次标签,分别在 tag
和 tag_all
下。你可能想要去掉至少其中一个和/或移动到 default_tags
以使同一模块中的所有资源标记变得更加简洁。
变量
这是困难的部分,定义你的模块的接口。这些工具都不会做魔法,官方注册表上的许多模块只是将 80% 的资源字段投影到变量中。Terraform 中良好的变量设计会导致更容易管理的模块,更少的破坏性变更,并给你机会将操作专业知识编码到你的模块中,而不是暴露其所有内部结构。
我的观点是,这是 IaC 最困难的部分,良好的接口设计。祝你好运!
没有 for_each
对于每个有多个实例的资源,都会向 Terraform 代码添加一个新的 resource
块 - 带有一个绝对残酷的资源标识符。你可能最终会重构这个,但也要确保重构那个状态文件!祝你好运!
resource "aws_subnet" "tfer--subnet-0cbe7d08a2a32f7d0" { vpc_id = "vpc-41bc19c86f3cd8be8" cidr_block = "10.0.1.0/24" availability_zone = "us-west-2a" } resource "aws_subnet" "tfer--subnet-0dbe8308a2a32f7d0" { vpc_id = "vpc-41bc19c86f3cd8be8" cidr_block = "10.0.2.0/24" availability_zone = "us-west-2b" } resource "aws_subnet" "tfer--subnet-1cbe7d08a2a32f7d0" { vpc_id = "vpc-41bc19c86f3cd8be8" cidr_block = "10.0.3.0/24" availability_zone = "us-west-2c" }
没有资源之间的引用或互联
这些工具都不会对你的资源如何连接在一起进行任何推断。它们不使用引用资源的能力 aws_ec2_vpc.main.arn
,而是简单地将字符串 ARN 放入字段中。你需要交叉引用状态文件和生成的代码来弄清楚。祝你好运!
这是上一节的同一个例子,注意没有对另一个资源的引用,而是裸露的 VPC ID 字符串。
resource "aws_subnet" "tfer--subnet-0cbe7d08a2a32f7d0" { vpc_id = "vpc-41bc19c86f3cd8be8" cidr_block = "10.0.1.0/24" availability_zone = "us-west-2a" } resource "aws_subnet" "tfer--subnet-0dbe8308a2a32f7d0" { vpc_id = "vpc-41bc19c86f3cd8be8" cidr_block = "10.0.2.0/24" availability_zone = "us-west-2b" } resource "aws_subnet" "tfer--subnet-1cbe7d08a2a32f7d0" { vpc_id = "vpc-41bc19c86f3cd8be8" cidr_block = "10.0.3.0/24" availability_zone = "us-west-2c" }
命名,最难按的按钮
生成的资源名称绝对是垃圾。你会想要更新这些以使其有意义,但你还需要同步这些更改到状态文件,这样 terraform 就不会认为你想删除并重新创建资源(那会很糟糕)。祝你好运!
output "aws_vpc_tfer--vpc-0004a66b0b36ecaf9_id" { value = "${aws_vpc.tfer--vpc-0004a66b0b36ecaf9.id}" } output "aws_vpc_tfer--vpc-03ff27926df168eea_id" { value = "${aws_vpc.tfer--vpc-03ff27926df168eea.id}" } output "aws_vpc_tfer--vpc-08dee04735e8785d8_id" { value = "${aws_vpc.tfer--vpc-08dee04735e8785d8.id}" } output "aws_vpc_tfer--vpc-0bbe7d08a2a32f7d0_id" { value = "${aws_vpc.tfer--vpc-0bbe7d08a2a32f7d0.id}" } output "aws_vpc_tfer--vpc-b8c9c5c0_id" { value = "${aws_vpc.tfer--vpc-b8c9c5c0.id}" }
这些工具可以改进的地方
所有的基础设施即代码(IaC)导入工具都是原型工具,它们不是万能的,也不能给你生产就绪的代码。
我个人认为,这些工具应该停止优化"导入所有内容"。这永远不会奏效,而且这是一种糟糕的IaC布局方式。这些工具不应该鼓励不良实践。我见过很多团队陷入这个陷阱,要摆脱它非常耗时。
在处理一组资源时(无论是按类型、ID还是标签),它们应该找到相同类型的资源并在for each中将它们分组。在Terraform中为我的每个子网添加一个新的resource
块并不能让我在网络之间灵活设计子网。
命名有点糟糕,如果只有一个资源,就叫它main
或this
。
如果有多个资源,提示用户输入资源名称。在main.tf
和状态文件之间来回切换以弄清楚资源是什么,这很糟糕。在生成过程中,你有参数和ID,问我想叫它什么。
用资源引用连接我的资源。如果一个字段看起来像ARN或Azure订阅ID,在你生成的Terraform中找到那个资源,为了上帝的sake使用bucket = aws_s3_bucket.main.arn
而不是只放入ARN的字符串。
结论
作为一个认真编写基础设施代码的人,我对生成IaC的现状印象不佳。你最终会做大量的重构,在我看来,从一个干净的状态(双关语)开始手动编写IaC,然后将资源导入其中会更容易。
我还准备了一个免费的网络研讨会,讨论2024年4月17日生成IaC的策略。我还将分享一个围绕Terraformer和一些LLM工具的开源包装器,它解决了上述一些问题。在下面注册,它是免费的!undefined
参考链接
- terraforming: https://github.com/dtan4/terraforming
- terracognita: https://github.com/cycloidio/terracognita
- 200 种服务: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
- AWS VPC 模块: https://www.massdriver.cloud/marketplace/aws-vpc
- aws2tf: https://github.com/aws-samples/aws2tf
- aztfy / aztfexport: https://github.com/Azure/aztfexport
- terraformer: https://github.com/GoogleCloudPlatform/terraformer
- 云: https://github.com/GoogleCloudPlatform/terraformer/tree/master/docs
- AWS 资源: https://github.com/GoogleCloudPlatform/terraformer/blob/master/docs/aws.md
- 源代码: https://github.com/GoogleCloudPlatform/terraformer#installation
- AWS VPC 模块: https://github.com/massdriver-cloud/aws-vpc