从手动操作到声明式基础设施
很多团队在云上起步时,都是从控制台点击开始的。几台虚拟机、一个负载均衡、一条安全组规则,手工配起来很快。但当环境数量超过三个,或者需要同时维护开发、测试、预发布和生产时,麻烦就来了:配置漂移、操作记录缺失、回滚困难、新成员入职时看着一堆截图无从下手。这些问题并不是因为团队不够细心,而是因为手工操作本身就缺乏可重复性和一致性。

Terraform 的出现,正是为了解决这类问题。它用声明式的配置文件描述基础设施的最终状态,然后由工具本身去计算和执行变更计划。也就是说,你不需要告诉它“先创建网络,再创建子网,然后部署虚拟机”,而是直接定义“我需要一个网络、一个子网、一台虚拟机”,Terraform 会自己决定依赖顺序并执行。这种思路,让基础设施管理开始具备软件工程里常见的版本控制、代码审查和自动化能力。
但这不意味着 Terraform 就是银弹。从入门到真正在企业里稳定运行,中间有大量工程细节需要认真对待。下面我们就从最核心的概念开始,一层层聊到生产环境里真正会踩的坑和可行的解法。
几个容易理解错的关键概念
HCL(HashiCorp Configuration Language)是 Terraform 的配置语言,看起来像 JSON 但更贴近人类阅读习惯。很多新手会把它当成一种“云资源 DSL”,但更深层的理解是:它描述的是期望状态,而不是执行步骤。这一点决定了后面很多行为。
Provider 是连接云平台或服务的插件,比如 AWS Provider、Kubernetes Provider。每当你定义 resource 时,实际上是在让某个 provider 去管理对应的 API 对象。data source 则是只读查询,用来获取已经存在的资源信息但不会去管理它。很多人刚接触时会把 data source 和 resource 混用,导致一些资源被意外创建或依赖错误。
State 文件可能是整个 Terraform 使用中最容易被低估的部分。它记录了最后一次 apply 后真实基础设施与配置之间的映射关系。没有 state,Terraform 就不知道哪些资源已经存在,也无法计算变更。本地 state 文件在单机实验时没问题,但一旦多人协作,就必须使用远程后端(remote backend)并配合状态锁,否则很容易出现状态冲突甚至损坏。
一个常见误区是认为 state 是配置的备份,或者可以随意删除重建。实际上,state 是 Terraform 运行时的核心数据,丢失或损坏后,如果没有妥善的恢复手段,可能意味着需要通过 import 手动把现有资源拉回来,甚至重新构建部分基础设施。
状态管理在生产环境里的真实处境
小团队初期往往把 state 文件放在工程师本地,或者简单地丢进一个 S3 存储桶。这在前几个环境还能用,但当多人同时执行 plan 或 apply 时,问题就暴露了:并行操作会互相覆盖 state,导致资源重复创建或状态文件损坏。即使用了 S3,如果没有开启 DynamoDB 锁或者使用 etcd 等外部锁机制,仍然会面临并发写入的风险。
另外一个容易被忽略的问题是关于敏感信息。state 文件里会包含所有资源的属性,包括数据库密码、访问密钥等。如果你把 state 存在 S3 但没加密,或者加密密钥管理不善,就会造成严重的安全隐患。不少团队在审计时才发现 state 文件里明文躺着生产环境密钥。
企业级方案通常会把 state 放在加密的远程后端(如 S3 + DynamoDB 锁,或 Terraform Cloud 的远程状态管理),并配合工作空间(workspace)隔离不同环境。但 workspace 并不是万能的,它更适合环境差异不大的场景,对于需要完全隔离的账户或网络拓扑,目录分离或 Terragrunt 这类包装工具反而更灵活。下面用一个简化的后端配置示例说明:
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "prod/network/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-lock"
}
}
这段配置把生产环境的网络相关状态放在独立的路径下,开启加密和锁,同时 key 的命名规则本身就体现了目录隔离的思路。这样的设计在后期扩展多个环境时会非常清晰。
模块化:不是所有复用都值得抽象
当基础设施代码量开始膨胀,把所有 resource 堆在一个目录里显然不可维护。模块化是必然的,但很多团队会走向另一个极端:过早抽象、过度参数化。比如为了统一管理,把 VPC 模块设计成几十个输入变量,覆盖所有可能的网络场景,结果任何小改动都要担心影响其他使用者,模块反而成为瓶颈。
合理的模块化策略通常遵循“稳定的基础设施单元”原则。比如一个标准的 VPC 模块,只暴露子网 CIDR、可用区列表、是否开启 NAT 网关等核心参数,内部逻辑处理好路由表、Internet Gateway 和子网关联。模块版本通过 Git 标签或 Terraform Registry 管理,使用者可以锁定版本,避免上游变更引发意外。
下表对比了几种常见的模块化策略和适用场景:
| 策略 | 适用团队规模 | 维护成本 | 灵活性 | 典型场景 |
|---|---|---|---|---|
| 单一模块仓库,多环境调用 | 中小团队 | 中 | 中等 | 所有环境共用同一套模块,通过变量区分 |
| 多模块仓库,按域拆分 | 中大团队 | 较高 | 高 | 网络、计算、安全等独立模块库,独立版本 |
| 按环境分支的模块(不推荐) | — | 高 | 低 | 容易导致版本分裂,正式环境与开发环境模块不一致 |
这个表格并不是要给出一个绝对答案,而是提醒团队:模块化方案必须匹配团队规模和协作方式。一个五人团队用多仓库策略可能反而拖慢速度,因为模块间协调成本过高。
企业级目录结构与自动化流水线
很多人会纠结于“应该用 Terraform Workspace 还是目录分离来管理环境”。实际上这两种方式不是互斥的,而是可以组合。一种常见的企业级目录结构如下:
infrastructure/
├── modules/
│ ├── vpc/
│ ├── eks/
│ └── rds/
├── accounts/
│ ├── dev/
│ │ ├── network/
│ │ ├── compute/
│ │ └── data/
│ ├── staging/
│ └── prod/
└── global/
├── iam/
└── dns/
每个环境目录下再按功能域拆分,每个目录对应一个独立的 state 和一组 Terraform 配置。这样做的好处是降低 blast radius——生产环境的网络变更不会影响其他环境,而且每个目录的 plan 和 apply 可以独立执行,减少锁竞争。
在 CI/CD 集成方面,很多团队会直接在流水线里执行 terraform plan 和 apply。但真正落地时,要特别注意这两点:一是 plan 结果必须作为 artifact 上传,apply 时再下载,避免因为代码变更导致 plan 和 apply 不一致;二是敏感变量必须通过环境变量或加密的变量文件注入,绝不能硬编码或在日志中输出。下面是一个简化的 GitLab CI 片段,展示如何将 plan 文件保存并在 apply 阶段复用:
plan:
stage: plan
script:
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- tfplan
apply:
stage: apply
when: manual
script:
- terraform apply tfplan
dependencies:
- plan
这种模式通过保存 plan 文件,保证了 apply 时执行的就是之前审查过的变更,避免中间引入新变动。
不只是工具选择:Terraform 与其他 IaC 方案的比较
在云原生基础设施领域,Terraform 并不是唯一的选择。CloudFormation 是 AWS 的原生方案,与 AWS 服务集成度最高,但仅限于 AWS;Pulumi 允许用通用编程语言编写配置,对开发人员更友好,但生态成熟度目前略低于 Terraform;Ansible 虽然也能管理基础设施,但它的过程式执行模型和 Terraform 的声明式模型有本质区别,更适合配置管理而非资源编排。
对不同团队而言,选择的关键不是“哪个最好”,而是“哪个现阶段最匹配”。以下是一个简化的对比:
- 小型团队、单一云平台:CloudFormation 或 AWS CDK 可能更简单,因为不需要额外维护状态后端。
- 多团队、混合云或多云需求:Terraform 的多 provider 能力优势明显,同时社区模块丰富,能减少重复造轮子。
- 策略即代码和合规要求高:Terraform 的 Sentinel(企业版)或 OPA 集成可以做到事前策略检查,而非事后审计。
很多团队从 CloudFormation 迁移到 Terraform 的触发点,往往是需要管理 AWS 之外的服务,比如 Datadog 监控、Cloudflare DNS 或 GitHub 仓库配置。一旦 infrastructure 的范围超出单一云厂商,Terraform 的统一语言和状态管理就体现出价值。
在生产环境里最容易踩的坑
除了前面提到的状态和安全问题,还有几个高频误区值得单独拿出来说。
第一是盲目使用 terraform destroy。新手喜欢在练习环境里 destroy 一切重来,但生产环境里 destroy 会真正删除数据库、存储卷等资源。即使有备份,恢复时间也可能很长。更安全的做法是通过 Terraform 管理资源生命周期,用 prevent_destroy 生命周期规则保护关键资源,或者通过 IAM 权限限制 destroy 操作。
第二是忽略 provider 版本锁定。很多团队在 provider 配置里不指定版本,或者用乐观的版本约束,结果某天 CI 突然失败,因为 provider 发布了不兼容的大版本更新。锁定 provider 版本和模块版本,是保证可重现性的基础。
第三是对 Terraform 能力的过度期待。Terraform 擅长管理基础设施的静态配置,但不太适合处理动态扩缩容、实时监控等运行时行为。试图用 Terraform 管理一切,往往会让配置变得极其复杂且难以维护。合理的做法是让 Terraform 负责基础设施的“骨架”,应用层交给 Kubernetes 或自动伸缩组等工具。
从入门到企业级:一条可行的演进路径
对于一个刚接触 Terraform 的团队,我通常建议不要一开始就追求完美的企业级架构,而是分阶段演进。
第一阶段:在单个环境里用本地 state 熟悉基本命令和 HCL 语法,完成一个最小可用的基础设施定义,比如一个包含网络、计算和数据库的简单项目。这个阶段的目标是理解声明式模型和资源依赖关系。
第二阶段:将 state 迁移到远程后端(如 S3),配置锁机制,并引入目录分离来管理多个环境(开发、生产)。同时开始抽取第一个内部模块,比如 VPC 模块,并引入版本管理。这个阶段能解决多人协作和状态安全的问题。
第三阶段:集成 CI/CD,实现自动化 plan 和手动审批的 apply。同时引入策略检查,比如 Terraform validate、fmt、tflint 以及 Sentinel 规则,确保提交的代码符合安全和合规要求。此时目录结构已经比较清晰,可以支撑多个团队并行工作。
第四阶段:当基础设施规模进一步扩大,可以考虑引入 Terragrunt 来减少重复代码,或者使用 Terraform Cloud/Enterprise 来统一管理状态、策略和运行。同时建立模块注册中心,推动跨团队复用。
每个阶段不需要严格按顺序,但核心逻辑是:先解决可靠性问题(状态、锁),再解决协作效率问题(模块化、CI/CD),最后才考虑治理和平台化。很多团队一上来就设计复杂的模块和平台,结果连基础的状态管理都没做好,反而增加了故障风险。
Terraform 的本质是把基础设施管理变成可审查、可重复、可协同的工程实践。它不神秘,但也不容轻视。真正用好它,需要团队对底层云资源有足够的了解,对状态和配置有严谨的管理习惯,以及愿意持续迭代的心态。当这些基础打牢之后,Infrastructure as Code 才会真正成为团队的能力,而不是负担。
原创文章,作者:fudengji,如若转载,请注明出处:https://fudengji.cn/article/437/