1. 这不是写代码,是给云上基建装上“数控机床”
你有没有经历过这样的场景:凌晨两点,线上服务突然告警,排查发现是某台数据库服务器的磁盘 I/O 队列飙高。运维同事紧急登录控制台,手动扩容了磁盘、重启了服务,问题暂时缓解。但第二天复盘时发现——这台机器压根不该用这个磁盘类型;更糟的是,它和另外三台同用途的机器配置不一致:一台用了 gp3,两台还在用老旧的 gp2,还有一台甚至误配成了 io1。没人记得当初为什么这么配,文档里没写,Git 历史里也找不到痕迹。这种“人肉运维”状态,在中等以上规模的团队里,不是例外,而是日常。
这就是 Infrastructure as Code(IaC)要解决的根本问题:把基础设施从“手工作坊式”的临时修补,升级为“现代化工厂式”的可编程、可版本化、可测试、可复现的生产流程。而 Terraform,就是目前工业界事实标准的那台“数控机床”——它不关心你用的是 AWS、Azure、阿里云还是私有 OpenStack,也不在意你最终部署的是虚拟机、Kubernetes 集群,还是一个 CDN 域名解析记录。它只认一种语言:HCL(HashiCorp Configuration Language),一种专为描述“资源状态”而生的声明式配置语言。
我从 2018 年开始在一家跨境电商公司落地 Terraform,当时团队刚从单体应用拆成十几个微服务,云上资源从几十个暴增到上千个。最初我们靠 Excel 表格管理 IP 段、用 Word 文档记录安全组规则、靠记忆维护 RDS 参数。三个月内发生了两次因配置漂移导致的跨环境数据误删。后来我们用 Terraform 重构了全部云资源交付链路,现在新环境从申请到上线平均耗时 11 分钟,所有变更都经过 CI 流水线自动执行 plan + approve + apply,每次变更都有完整的 Git 提交记录、审批人、变更前后的资源差异快照。更重要的是,新来的工程师入职第三天就能独立修改并提交一个 VPC 的 CIDR 范围调整——因为他不需要去问“这个子网掩码为什么是 /24”,他直接看代码就知道:
cidr_block = "10.100.10.0/24"
,而这个值被定义在
variables.tf
里,上游还有
terraform validate
和
tflint
的双重校验。
所以,“Infrastructure As Code With Terraform” 这个标题,说的不是“用 Terraform 写点配置”,而是建立一套让基础设施具备软件工程全部成熟实践的能力体系:版本控制、代码审查、自动化测试、持续集成、回滚机制、权限隔离。它适合三类人:第一类是正在被“配置混乱”折磨的运维/DevOps 工程师;第二类是想摆脱“环境不一致”魔咒的后端/全栈开发者;第三类是技术决策者——如果你还在为“测试环境跑得好好的,一上生产就崩”而头疼,那这不是代码 Bug,是基础设施的 Bug,而 Terraform 就是你的 Debugger。
2. 为什么是 Terraform?而不是 Ansible、Pulumi 或 CloudFormation?
选型从来不是比功能列表,而是比“谁最能扛住真实世界的脏活累活”。我带过三个不同行业的落地项目:金融支付、在线教育、智能硬件 SaaS,每个都踩过坑、换过方案、最终都稳在 Terraform 上。下面这张表,是我用真实项目数据总结出的核心维度对比:
| 维度 | Terraform | Ansible | CloudFormation | Pulumi |
|---|---|---|---|---|
| 核心范式 | 声明式(描述“终态”) | 命令式(描述“步骤”) | 声明式(AWS 专属) | 声明式(支持多语言) |
| 多云能力 | 原生支持 100+ Provider(AWS/Azure/GCP/阿里云/腾讯云/VMware/OCI 等) | 依赖社区模块,稳定性参差不齐 | 仅限 AWS,跨云需额外封装 | 支持多云,但各云 Provider 成熟度差异大 |
| 状态管理 | 独立 state 文件(本地/远程后端),精确追踪资源生命周期 | 无状态,每次运行都重试所有任务 | 与 AWS 账户强绑定,状态不可导出 | 依赖后端存储,但调试体验不如 Terraform 直观 |
| 学习曲线 | 中等(HCL 语法简洁,概念清晰) | 较低(YAML + 命令思维) | 高(JSON/YAML + AWS 专有概念) | 高(需掌握 Go/Python/TypeScript + IaC 抽象) |
| CI/CD 集成 | Plan 输出结构化 JSON,易解析;Apply 可自动审批 | Playbook 执行即生效,难做预演 | ChangeSet 机制可用,但输出不易解析 | 支持,但各语言 SDK 差异大,流水线适配成本高 |
| 企业级能力 | State 锁(Consul/S3/DynamoDB)、模块化、Workspaces、Sentinel 策略即代码 | Tower/AWX 提供 UI 和 RBAC,但策略引擎弱 | StackSets + Service Control Policies,但仅限 AWS 生态 | Policy-as-Code(基于 Rego),但生态尚不成熟 |
提示:很多团队初期会纠结“Ansible 更熟悉,为什么不用?”。我实测过:用 Ansible 创建一个包含 5 个子网、3 个安全组、2 台 EC2、1 个 RDS 的 VPC,Playbook 写了 287 行,且每次执行都必须先检查资源是否存在(因为它是命令式),稍有网络抖动就可能卡在某个 task。而同样需求的 Terraform 配置,主文件仅 92 行,
terraform plan会精准告诉你“将创建 12 个资源,修改 3 个,删除 0 个”,apply后状态自动写入 S3,下次plan会基于最新状态计算差异。这不是语法糖,是范式差异带来的确定性红利。
Terraform 的核心优势,在于它把“基础设施”真正当成了“一等公民”的软件资产来对待。它的
state
不是日志,而是权威真相源;它的
plan
不是预演,而是数学意义上的状态差分;它的
module
不是文件夹,而是可版本化、可参数化、可组合的抽象单元。举个最典型的例子:我们曾用 Terraform 模块封装了一个“合规 RDS 实例”,它内部强制要求:必须开启加密、必须启用备份、必须设置保留期 ≥7 天、必须禁止公网访问、必须打上
env=prod
标签。任何团队成员调用这个模块时,只要传入
instance_class = "db.t3.medium"
,其余所有合规项自动注入。如果有人试图绕过模块直接写
aws_db_instance
资源,我们的 CI 流水线会在
terraform validate
阶段就报错:“检测到未使用合规 RDS 模块,拒绝合并”。这就是“策略即代码”的落地——不是靠人盯,而是靠代码拦。
另一个常被低估的关键点是
Provider 生态的成熟度
。截至 2024 年中,Terraform 官方认证的 Provider 已覆盖所有主流公有云、Kubernetes、Docker、GitHub、Datadog、New Relic、甚至像 Cisco ACI、F5 BIG-IP 这样的传统网络设备。而更重要的是,这些 Provider 的更新节奏与云厂商 API 发布基本同步。比如 AWS 在 2024 年 3 月发布的新一代 Graviton3 实例类型
c7g
,HashiCorp 在 48 小时内就发布了支持该实例的
aws
Provider v5.32.0。这意味着,你的基础设施代码可以第一时间拥抱云厂商的最新能力,而无需等待某个第三方工具的适配。
3. 从零搭建一个可落地的 Terraform 工程:不是 Hello World,是生产就绪
很多教程停在
terraform init && terraform apply
就结束了,但这离真实生产环境差了至少十层防火墙。我下面带你走一遍我们团队当前正在使用的、已支撑 200+ 微服务、日均 300+ 次变更的 Terraform 工程骨架。它不追求炫技,只解决四个刚需:
环境隔离、状态安全、变更可控、权限分明
。
3.1 目录结构设计:按“环境”而非“服务”组织
这是最容易踩的第一个坑。新手常把所有代码放在一个目录下,用变量切换环境(
env = "prod"
)。这会导致:
terraform plan
时无法预知对 prod 的影响,
state
文件混在一起极易误操作。我们的方案是:
物理隔离环境目录,逻辑复用模块
。
├── environments/
│ ├── dev/
│ │ ├── main.tf # 调用 modules/vpc, modules/ec2 等
│ │ ├── terraform.tfvars # dev 专属变量:region="us-west-2", instance_count=2
│ │ └── backend.tf # 指向 S3 bucket: tf-state-dev
│ ├── staging/
│ │ ├── main.tf
│ │ ├── terraform.tfvars # staging 专属变量
│ │ └── backend.tf # 指向 S3 bucket: tf-state-staging
│ └── prod/
│ ├── main.tf
│ ├── terraform.tfvars # prod 专属变量(如 instance_count=10)
│ └── backend.tf # 指向 S3 bucket: tf-state-prod
├── modules/
│ ├── vpc/ # 独立模块:输入 cidr, azs;输出 vpc_id, public_subnets
│ ├── ec2/ # 独立模块:输入 ami, instance_type;输出 instance_ids
│ └── rds/ # 独立模块:输入 engine_version, storage_gb;输出 endpoint
└── versions.tf # 全局 provider 版本锁定
注意:
backend.tf是关键。我们绝不使用本地state。每个环境目录下的backend.tf都指向独立的 S3 存储桶,并启用 DynamoDB 锁表:terraform { backend "s3" { bucket = "tf-state-prod" key = "global/vpc/terraform.tfstate" region = "us-east-1" dynamodb_table = "tf-state-lock-prod" } }这样,当两个工程师同时对
prod/vpc执行apply时,后发起的请求会立刻收到Error: Error acquiring the state lock,而不是覆盖对方的变更。S3 的版本控制功能还能让你随时回滚到任意历史state版本——这在误删资源时是救命稻草。
3.2 模块化实战:以 VPC 模块为例,拆解如何写出“防呆”代码
一个合格的 VPC 模块,绝不能只是“创建一个 VPC”。它必须内置业务约束。以下是我们
modules/vpc
的核心设计逻辑(已脱敏):
# modules/vpc/variables.tf
variable "name" {
description = "VPC 名称,将作为所有资源的 Name 标签"
type = string
}
variable "cidr_block" {
description = "VPC CIDR,必须是 /16 或 /17,且不能与公司主干网冲突"
type = string
validation {
condition = can(regex("^10\\.(1[6-9]|2[0-9]|3[0-1])\\.", var.cidr_block)) && (length(split("/", var.cidr_block)) == 2 ? tonumber(split("/", var.cidr_block)[1]) >= 16 && tonumber(split("/", var.cidr_block)[1]) <= 17 : false)
error_message = "CIDR 必须是 10.16.0.0/16 到 10.31.0.0/17 范围内的私有地址段。"
}
}
variable "azs" {
description = "可用区列表,至少指定 2 个 AZ"
type = list(string)
validation {
condition = length(var.azs) >= 2
error_message = "必须指定至少 2 个可用区以保证高可用。"
}
}
看到这个
validation
块了吗?它不是注释,是 Terraform 0.13+ 引入的
运行时校验
。当你在
environments/prod/main.tf
中这样调用:
module "vpc" {
source = "../../modules/vpc"
name = "prod-core"
cidr_block = "10.200.0.0/16" # ✅ 合法
azs = ["us-west-2a", "us-west-2b"]
}
一切正常。但如果你不小心写成:
cidr_block = "192.168.1.0/24" # ❌ 触发 validation 错误
terraform validate
会直接报错,根本不会走到
plan
阶段。这种“防御性编程”思维,是把错误拦截在开发阶段的最有效手段。
再看它的输出设计:
# modules/vpc/outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.this.id
}
output "public_subnets" {
description = "公共子网 ID 列表"
value = aws_subnet.public[*].id
}
output "private_subnets" {
description = "私有子网 ID 列表"
value = aws_subnet.private[*].id
}
# 关键!提供一个“可直接用于其他模块”的子网映射
output "subnet_map" {
description = "按可用区组织的子网映射,格式:{ \"us-west-2a\": { \"public\": \"subnet-xxx\", \"private\": \"subnet-yyy\" } }"
value = {
for idx, az in var.azs : az => {
"public" = element(aws_subnet.public.*.id, idx)
"private" = element(aws_subnet.private.*.id, idx)
}
}
}
这个
subnet_map
输出,解决了跨模块传递子网 ID 的经典难题。比如 EC2 模块需要指定
subnet_id
,你不再需要写
module.vpc.public_subnets[0]
(硬编码索引),而是可以优雅地写:
module "ec2" {
source = "../../modules/ec2"
subnet_id = module.vpc.subnet_map["us-west-2a"].public
}
这保证了:当 VPC 模块内部调整子网创建顺序时,EC2 模块完全不受影响。这才是模块化的真正价值—— 契约稳定,实现可变 。
3.3 变更流程:从 Git 提交到生产上线的完整闭环
代码写完只是开始。真正的生产就绪,取决于你如何管理变更。我们采用的是 GitOps 驱动的三级审批流 :
-
Developer 提交 PR
:在
environments/prod目录下修改配置,提交 Pull Request。 -
CI 自动执行 Plan
:GitHub Actions 触发流水线,执行:
流水线会解析terraform init -backend-config="bucket=tf-state-prod" terraform workspace select prod terraform plan -out=tfplan -var-file=terraform.tfvars terraform show -json tfplan > plan.json # 生成结构化报告plan.json,提取出所有将被创建/修改/销毁的资源列表,并在 PR 评论中自动生成一个 Markdown 表格,清晰列出:-
Resource Type:aws_db_instance -
Action:create -
Name:prod-app-db -
Changes:allocated_storage: 100 → 200,engine_version: "13.10" → "14.5"
-
-
Team Lead 审批
:负责人查看自动生成的变更摘要,确认无高危操作(如
destroyRDS 主实例),点击 Approve。 - Security Team 二次审批(仅 prod) :安全组变更、IAM 权限提升、公网暴露等敏感操作,需安全团队在专用审批系统中二次确认。
-
CI 自动 Apply
:双审批通过后,流水线执行
terraform apply tfplan,并将执行日志、state版本号、变更时间戳写入审计日志表。
实操心得:我们曾因跳过第 4 步,在一次紧急修复中误将
aws_security_group_rule的cidr_blocks从["10.0.0.0/8"]改成了["0.0.0.0/0"](全网开放),导致数据库端口暴露。此后强制所有prod环境的aws_security_group和aws_iam_role_policy变更,必须经安全团队人工审批。这个“痛苦换来的流程”,至今保护着我们核心数据资产。
4. Terraform 的“暗礁区”:那些官方文档不会告诉你的 7 个致命陷阱
Terraform 很强大,但它的强大背后藏着一些反直觉的设计,踩中一个,轻则构建失败,重则数据丢失。这些都是我在上百次
apply
失败、数十次
state
修复后,用真金白银买来的教训。
4.1 陷阱一:
count
与
for_each
的语义鸿沟——别用
count
做动态列表
新手最爱用
count
,因为它看起来像循环:
# ❌ 危险!不要这样写
resource "aws_security_group_rule" "ingress" {
count = length(var.ingress_ports)
type = "ingress"
from_port = var.ingress_ports[count.index]
to_port = var.ingress_ports[count.index]
protocol = "tcp"
}
问题在哪?
count
是基于
索引
的。当你把
var.ingress_ports = [80, 443]
改成
[443, 8080]
时,Terraform 会认为:
index 0
的资源(原 80 端口)被销毁,
index 1
的资源(原 443 端口)被修改为 8080。结果是:80 端口规则被删,443 端口规则被改成 8080——而你本意只是“交换顺序”。
正确做法是用
for_each
,它基于
唯一键
:
# ✅ 安全!用 map 或 set 作为键
variable "ingress_rules" {
description = "入口规则列表,格式:{ \"http\": { port = 80 }, \"https\": { port = 443 } }"
type = map(object({
port = number
protocol = string
}))
}
resource "aws_security_group_rule" "ingress" {
for_each = var.ingress_rules
type = "ingress"
from_port = each.value.port
to_port = each.value.port
protocol = each.value.protocol
}
现在,无论你如何调整
ingress_rules
的顺序,Terraform 都只会根据
each.key
(
"http"
或
"https"
)来识别资源,确保语义稳定。
4.2 陷阱二:
state mv
的“幽灵资源”——移动后务必
refresh
terraform state mv
是个神命令,但它有个隐藏副作用:它只移动
state
中的记录,
不触发实际资源的任何操作
。比如,你想把一个手动创建的 S3 桶纳入 Terraform 管理:
# 假设桶名是 my-bucket-2024
terraform state mv aws_s3_bucket.manual my-bucket-2024
执行后,
state
里有了这个桶,但 Terraform 并不知道它当前的真实配置(比如是否启用了版本控制、生命周期规则)。如果你直接
terraform apply
,Terraform 会按代码里的默认值去“修正”这个桶——可能把已有的版本控制关掉,或者清空所有生命周期规则。
正确姿势是:
mv
后,立即
terraform refresh
:
terraform state mv aws_s3_bucket.manual my-bucket-2024
terraform refresh # 这一步会把桶的真实状态拉取到 state 中
terraform apply # 现在 apply 才是安全的
我们曾因此丢失过一个存有 2TB 日志的 S3 桶的版本控制,导致无法恢复被误删的文件。
refresh是mv的黄金搭档,缺一不可。
4.3 陷阱三:Provider 版本漂移——锁死版本是底线
Terraform Provider 更新很快,但新版本可能引入不兼容变更。比如
aws
Provider 从 v4.x 升级到 v5.x 时,
aws_db_instance
的
storage_encrypted
参数默认值从
false
变成了
true
。如果你的代码没显式声明这个值,升级后
plan
会显示“将修改 50 个 RDS 实例”,而你根本没打算动它们。
解决方案:在
versions.tf
中
显式锁定 Provider 版本范围
:
# versions.tf
terraform {
required_version = ">= 1.5.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.32.0" # 锁定在 5.32.x 小版本内
}
}
}
~>
符号表示“兼容版本”,即允许
5.32.0
到
5.32.999
的更新,但禁止升到
5.33.0
。这给了你充分的测试窗口:当 HashiCorp 发布
5.33.0
时,你的
terraform init
会失败,强制你评估变更。
4.4 陷阱四:
null_resource
的滥用——它不是万能胶水
很多教程教用
null_resource
+
local-exec
去执行 Shell 命令,比如“部署完 EC2 后,SSH 过去安装 Nginx”。这看似方便,实则埋雷:
-
local-exec在本地机器执行,如果本地网络不通、SSH 密钥缺失、或脚本有 bug,整个apply就卡死。 -
它破坏了 Terraform 的声明式本质:你无法
plan出这个命令的执行结果,也无法destroy它(null_resource没有销毁逻辑)。 - 它让基础设施状态变得不可信:Terraform 认为“资源已创建”,但实际 Nginx 可能根本没装上。
正解是:
把“配置”交给专门的工具
。对于 EC2,用
user_data
启动脚本;对于 Kubernetes,用
helm_release
资源;对于复杂配置,用 Ansible/Puppet/Chef,但通过 Terraform 的
null_resource
仅作为
触发器
(
triggers
),且必须配合
provisioner
的
on_failure = "fail"
和完善的错误处理。
4.5 陷阱五:
data
数据源的“缓存幻觉”
data
块(如
data "aws_ami" "ubuntu"
)用于读取已有资源。但它的值是在
plan
阶段一次性读取并缓存的。这意味着:如果你的
data
块依赖一个动态变化的值(比如一个由另一段代码生成的标签),而这个值在
plan
和
apply
之间发生了变化,
data
块读到的就可能是过期数据。
规避方法:
永远不要让
data
块的查询条件依赖于
resource
的动态属性
。如果必须,改用
resource
块(即使你不拥有它),并用
lifecycle.ignore_changes
忽略你不关心的字段。
4.6 陷阱六:
remote-exec
的 SSH 连接超时——别信默认值
remote-exec
provisioner 默认的
timeout
是 5 分钟。但在网络质量差的区域(比如跨国办公),SSH 连接建立、密钥交换、命令执行,很容易超过这个时间,导致
apply
失败。
解决方案:显式设置超时,并增加重试逻辑:
provisioner "remote-exec" {
inline = ["sudo apt-get update && sudo apt-get install -y nginx"]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
# 关键!延长超时
timeout = "15m"
# 关键!添加重试(Terraform 1.4+)
on_failure = "fail"
retry_join {
attempts = 3
delay = "30s"
}
}
4.7 陷阱七:
state
的“雪球效应”——从小处开始,拒绝大爆炸
最大的陷阱,不是技术,是心态。很多团队想“一步到位”,把所有存量资源(几百台 ECS、几十个 SLB、上百个 RDS)一次性导入 Terraform。结果:
terraform import
脚本写了一周,
state
文件导入一半失败,
plan
输出几千行变更,没人敢点
apply
,项目就此搁浅。
我的建议是: 从最小、最无风险、最易验证的模块开始 。比如:
-
先做一个
modules/tags模块,只负责给所有资源打上统一的owner和environment标签。 -
用
terraform import导入 5 个非核心资源(如几个测试用的 S3 桶)。 -
plan确认只修改了标签,apply。 - 验证标签是否生效,监控是否正常。
-
成功后,再扩展到
modules/vpc,再modules/ec2……
就像给一辆高速行驶的汽车换轮胎,你得一个轮子一个轮子来。Terraform 的威力,不在于它能管多少资源,而在于它能让每一次变更,都变得小、快、可逆、可验证。
5. 超越基础:让 Terraform 成为你团队的“基础设施操作系统”
当 Terraform 不再是“一个工具”,而成为团队默认的基础设施交互方式时,它的价值才真正爆发。我们做了三件关键的事,让 Terraform 从“配置管理”升级为“操作系统”。
5.1 构建自己的 Provider:当官方不满足时,自己造轮子
我们有一个核心业务系统,其配置项(如路由规则、黑白名单、QPS 限流阈值)必须通过一个内部 HTTP API 管理。官方没有对应的 Terraform Provider。很多人会选择
null_resource
+
curl
,但我们选择了更彻底的方案:
用 Go 编写一个自定义 Provider
。
过程并不神秘:Terraform 提供了清晰的 SDK(
github.com/hashicorp/terraform-plugin-sdk/v2
),你只需实现
Create
,
Read
,
Update
,
Delete
四个函数。例如,
Create
函数就是向你的 API 发一个
POST /rules
请求。编译后,它就是一个
.exe
文件,可以像
aws
Provider 一样被
required_providers
引用。
好处是什么?是
一致性
。现在,工程师修改一条路由规则,和创建一台 ECS,使用的是同一套语法、同一个
plan
预览、同一条
apply
流水线。API 的鉴权、重试、超时、错误码映射,全部在 Provider 内部封装。
state
里清晰地记录着每条规则的 ID 和当前状态。这消除了“一部分配置在 Terraform,一部分在后台页面”的割裂感。
5.2 Terraform Cloud 的深度定制:不只是托管 State
我们弃用了自建的 S3 + DynamoDB 后端,全面迁移到 Terraform Cloud(TFC)。但没把它当“高级版 S3”用,而是深度集成了它的三大能力:
-
工作区(Workspace)的自动生命周期管理
:我们用 TFC 的 API,为每个 Git 分支自动创建一个临时 Workspace(如
feature/login-redesign)。这个 Workspace 的state是隔离的,variables是继承自staging的副本。PR 合并后,TFC 自动销毁该 Workspace。这让我们实现了“分支即环境”,前端工程师可以在自己的分支上一键部署一套完整、隔离的测试环境,而无需申请云账号。 -
Sentinel 策略即代码
:我们编写了 12 条 Sentinel 策略,例如:
这些策略在# 禁止在 prod 环境创建 t3.micro 实例 import "tfplan" main = rule { all tfplan.resources.aws_instance as _, instances { all instances as r { r.applied.instance_type not in ["t3.micro", "t2.micro"] } } }plan阶段强制执行,违反即阻断。它比 CI 脚本更早、更准、更不可绕过。 -
Run Triggers 的跨环境联动
:
environments/staging的 Workspace 设置了 Run Trigger,监听environments/prod的成功apply。当 prod 的 VPC CIDR 更新后,staging 的 Workspace 会自动触发一次plan,确保 staging 的网络配置始终与 prod 保持兼容。这是真正的“基础设施拓扑感知”。
5.3 “Terraform First” 的文化渗透:让每个人都成为基础设施工程师
最后,也是最难的,是改变人的习惯。我们推行了三条铁律:
-
所有云上资源,必须有且仅有一个 Terraform 代码来源
。无论是 DBA 创建的 RDS,还是 SRE 创建的 ALB,都必须通过
terraform import纳入管理。新资源申请流程的第一步,就是填写一份 Terraform Module 的 Issue 模板。 -
“No CLI, Only CI”
。禁止任何人直接在自己电脑上执行
terraform apply。所有变更必须通过 PR + CI 流水线。apply按钮只存在于 GitHub Actions 的成功流水线页面上,且只有特定角色可见。 -
基础设施代码 Review,是 CR 的必选项
。我们制定了《Terraform CR Checklist》,包括:
state是否安全、validation是否完备、output是否必要、lifecycle是否合理、是否引入了新的 Provider。CR 不通过,PR 就不能合并。
效果是惊人的。过去,一个新服务上线,需要开发、测试、运维、DBA、安全五个角色开三次会。现在,开发写好
main.tf
,提 PR,CR 通过,CI 自动部署,整个过程平均 22 分钟。而那个曾经让我们夜不能寐的“配置漂移”问题,已经连续 18 个月没有发生过。
我个人在实际操作中的体会是:Terraform 的终极价值,不在于它帮你省了多少分钟,而在于它把“基础设施”这个曾经充满不确定性、依赖个人经验、难以传承的黑盒子,变成了一个可以用代码精确描述、用测试反复验证、用流程严格管控的白盒系统。当你第一次看到
terraform plan
清晰地告诉你“将创建 3 个资源,修改 1 个,销毁 0 个”,并且这个计划与你脑中的预期完全一致时,那种掌控感,是任何其他运维工具都无法给予的。它不是魔法,是工程。

6万+

被折叠的 条评论
为什么被折叠?



