1.1.1. 前言
自从开始动手编写 Terraform 入门教程一晃已经四年多过去了,这四年多里发生了很多事,HashiCorp 和 Terraform 也经历了许多的变化。笔者在这段时间里主要从事 Terraform Module 生态的工作,愈发感到 Module 是 Terraform 生态中最重要的部分。思来想去,决定将这些年在这方面累积的一些经验教训编纂成书,帮助后来的开发者,尤其是中国开发者们更好地使用 Terraform。
1.1.1.1. 什么是 Terraform Module
Terraform 模块是独立的基础设施即代码片段,抽象了基础设施部署的底层复杂性。Terraform 用户通过使用预置的配置代码加速采用 IaC,并降低了使用门槛。所以,模块的作者应尽量遵循诸如清晰的代码结构以及 DRY("Don't Repeat Yourself")原则的代码最佳实践。
本质上 Terraform Module 就是一个包含多个 Terraform 配置文件的目录,通常用于封装一组相关的资源和配置,以便于重用和共享。Module 可以帮助用户更好地组织和管理 Terraform 代码,提高代码的可读性和可维护性。
重要提示:本书会假设读者已经熟悉了基本的 Terraform 使用技能,对这方面的知识不再赘述。
1.1.1.2. 什么是 Azure Verified Module
Azure Verified Modules(AVM)是微软推出的官方开源项目,旨在为基础设施即代码(Infrastructure as Code,IaC)模块建立统一标准,提升 Azure 资源部署的一致性、可靠性和可维护性。
AVM 提供了由微软开发和支持的预定义、可重用的 IaC 模块,适用于 Bicep 和 Terraform。这些模块遵循微软的最佳实践和架构规范(如 Well-Architected Framework,WAF),帮助用户快速、安全地部署 Azure 资源和架构模式。
它的核心优势有:
- 标准化与一致性:所有模块遵循统一的结构和编码规范,确保部署的一致性。
- 微软官方支持:由微软官方开发和维护,提供长期支持和定期更新。
- 跨语言支持:目前支持 Bicep 和 Terraform,未来可能扩展到更多 IaC 工具。
- 合规与最佳实践:模块对齐 WAF 和安全基准,确保部署的安全性和合规性。
- 自动化文档与测试:内置自动生成文档和全面的测试,提升模块的可用性和可靠性。
笔者是 AVM 核心开发人员,从 AVM 项目启动时就参与开发与治理工作至今,本书总结的一些经验、教训与规范来自于 AVM。
重要提示:本书中介绍的所有规范、经验,均为本人主观意见,并不代表 AVM 团队或微软的官方意见,倘若读者发现某些内容与 AVM 规范不同,或是不同意其中部分观点,请勿将之视为是 AVM 或是微软的观点。本书是一部作者个人风格相当强烈的作品,所有观点仅代表笔者个人意见。
重要提示:本书会介绍一些我们曾经使用过的,或是正在使用的工具,由于笔者能力所限,以及基础设施即代码社区的快速迭代演进,如果读者发现工具篇的某些内容与官方文档描述不一致,或是使用时出现某种错误,请不要怀疑,这大概率是笔者犯的错误,烦请届时提交相关 Issue 通知笔者进行更改,先行谢过。
1.1.1.3. 从大泥球(Big Mud Ball)开始
一般 Terraform 新手看到的教程中,往往会把所有演示代码写在一个main.tf
文件中,或者略微正式一些的,会呈现这样一个结构:
.
├── main.tf
├── outputs.tf
└── variables.tf
这种将整个基础设施写在一个 main.tf
文件中的做法对于演示功能或者新手教学来说没有问题,但是在生产环境中这样做就会导致该文件过长,并且很难进行代码审查。所以略微复杂一些的模块结构可以是像这样的:
.
├── Makefile
├── README.md
├── chefignore
├── db.tf
├── gateway.tf
├── images
│ └── diagram.png
├── kitchen.tf
├── main.tf
├── securitygroup.tf
├── subnet.tf
├── terraform.tfvars
├── variables.tf
└── vpc.tf
将资源按照分类以独立文件的形式存放,这样单个代码文件不至于过长,代码文件的内聚性提升了。
但即使这样,仍然存在某些问题:
- 栈过大,管理的资源过多,每次执行
plan
、apply
时间很长 - 爆炸半径过大,一旦犯错,例如错误地执行了
destroy
,会将整个基础设施全部抹除
1.1.1.4. 多层单体 Terraform
我们假设一个简单的云端应用,由 Vpc、MySQL 数据库和无状态 App 三层构成,我们把项目结构设计成这样:
.
├── app
│ ├── main.tf
│ ├── outputs.tf
│ ├── terraform.tfstate
│ └── variables.tf
├── mysql
│ ├── main.tf
│ ├── outputs.tf
│ ├── terraform.tfstate
│ └── variables.tf
└── vpc
├── main.tf
├── outputs.tf
├── terraform.tfstate
└── variables.tf
与一开始的大泥球不同的是,这次我们将基础设施拆分成了 3 个文件夹分别管理,资源被写入三个独立的状态文件。这样每次我们进行变更,只会与这一层的资源打交道,这就可以降低 plan
、apply
操作的执行时间,也可以降低我们犯错的爆炸半径。
但我们仍然把所有的数据库资源放在了一个状态文件当中管理,假如出现操作失误,我们很可能会把所有的数据库资源都删除掉,这就会导致数据丢失。
1.1.1.5. 进一步的模块化拆分
└── live
├── prod
│ ├── app
│ │ └── main.tf
│ ├── mysql
│ │ └── main.tf
│ └── vpc
│ └── main.tf
├── qa
│ ├── app
│ │ └── main.tf
│ ├── mysql
│ │ └── main.tf
│ └── vpc
│ └── main.tf
└── stage
│ ├── app
│ │ └── main.tf
│ ├── mysql
│ │ └── main.tf
│ └── vpc
│ └── main.tf
└── modules
├── app
│ └── main.tf
├── mysql
│ └── main.tf
└── vpc
└── main.tf
我们进一步针对部署环境维度进行了拆分,划分为 prod
、qa
、stage
三个环境。每个环境下又包含了 app
、mysql
、vpc
三个模块。每个环境下的子模块都引用了 modules
目录下的相同模块,这样我们就可以在不同的环境中部署相似的基础设施,它们的部署代码完全相同,参数可能不同,避免了代码的重复,同时我们可以先把变更应用到 qa
环境,经过验证后再逐层推广到 prod
环境,实现了基础设施的灰度发布。
在实际生产环境中,以上结构可以进一步按照区域划分,我们可以根据部署的区域划分出 us-east-1
、us-west-2
等区域,或者根据不同的云服务商划分出 aws
、azure
、gcp
等区域,将爆炸半径进一步缩小。
这里我们描述的所有基础设施部署代码,都是针对某个特定在线服务的,比如某个电商平台的在线支付服务。我们可以将这些代码放在一个 Git 仓库中,命名为 payment-service
;企业可能会有数十、数百甚至上千个这样的仓库,对应了内部的各种服务的基础设施,基于这样的大规模 Terraform Module 结构,实现 GitOps,也就是对基础设施的所有变更,都是通过 Pull(Merge) Request 完成的;仓库的管理员也就是服务的所有者,通过审查代码变更和 CI 流水线生成的 Terraform Plan 对变更进行审查,如果同意变更,则合并该变更请求,然后由流水线自动执行经过审计的变更。
这样的管理看起来会有些复杂,但真正实现了无关大小,所有对基础设施的变更都是经过审查,并有版本控制记录,如果发生问题,Terraform 的能力确保了我们可以回滚到最近已知正确的状态(当然,为了防止数据丢失,我们在设计 Module 时需要额外做很多的设计工作)。
1.1.1.6. 如何得到这些模块
我们在上面的例子里以一种极其简化的过程描述了两种不同的 Terraform 项目结构风格,从混乱的大泥球,到大量层次清晰的模块化矩阵,那么如何能够得到组成该矩阵的这些模块呢?这就是本书的目的:从如何构建一个模块,如何维护一个模块,到如何规模化地构建和维护多个模块,最后到如何在企业中推广模块化的 Terraform 代码。Terraform 可复用模块与许多常见的开源项目有些不同,会需要一些特殊的治理方法。
1.1.1.7. 本书的目标读者
已经对 Terraform 有一定了解,可以熟练使用 Terraform 创建并管理基础设施的读者,如果你们想要进一步了解如何使用 Terraform 模块优化你的基础设施管理框架,或是想要了解如何规模化地维护大量自有的 Terraform 模块,那么本书就是为你们准备的。
1.1.1.8. 结语
这么说吧,使用 Terraform 管理基础设施的唯一正确方法就是将庞大的基础设施拆分成这样的模块化结构。我们可以将这些模块化的代码放在 Git 仓库中,使用 GitOps 的方式进行管理。这样我们就可以在不同的环境中部署相似的基础设施,避免了代码的重复,同时也实现了基础设施的灰度发布。我曾经见过不少团队都动过这样的念头,即将 Terraform 作为一个执行工具,由其他工具,例如一个 GUI 图形化工具生成 Terraform 和 HCL 或者是 JSON 代码,最后由 Terraform 执行。这样的做法是错误的,因为 Terraform Module 无论是使用、维护还是设计上,都有很多需要注意的地方,这也是本书希望讨论的内容。
1.1.1.9. 马驰排除条款
本电子书使用 CC-BY-SA-4.0 license 授权发布,读者可以在该协议的许可范围内自由阅读、引用或是使用本电子书的内容,但以下情况除外:
- 禁止名为“马驰”的特定个人实体阅读、引用、复制本电子书的内容
- 禁止在名为“马驰”的特定个人实体所拥有的任何设备上打开、保存本书的内容或是离线副本、拷贝
- 禁止名为“马驰”的特定个人实体打印、抄写本书内容,或是保有本书内容的非数字化副本(包含并不限于书籍、手抄本、照片等)
- 禁止在与名为“马驰”的特定个人实体有劳动关系、股权关系,或是与其直系亲属有关联的企业、团体所拥有的任何电子设备上打开、保存本电子书的内容或是离线副本、拷贝
以上情况均会被视为侵权行为。若读者名为“马驰”,但不知道自己是否是该条款所禁止的特定“马驰”个人实体,可以在本书 GitHub 仓库 中提交 issue 与作者确认。
对本电子书的复刻(Fork)以及再创作遵循 CC-BY-SA-4.0 License 相关规定,但不允许去除本条款内容。