1. 性能瓶颈在哪里
一句话总结: 大规模 Terraform 的慢主要集中在 refresh(逐个查 API)、串行依赖、超大 state 与网络往返四类,先量化再优化,别凭感觉调参。
一台只有几十个资源的仓库,terraform plan 十几秒正常;上千资源时可能拖到十几分钟。先定位瓶颈:-verbose 与 TF_LOG 能看到每个资源的耗时,terraform plan -parallelism 能试出并行收益。
# 量化各资源耗时
TF_LOG=INFO terraform plan -no-color 2>&1 | \
grep -E "module.*(Read|Apply)" | head -20
# 看整体耗时分布
time terraform plan -refresh=false
常见瓶颈归类:
| 瓶颈 | 现象 | 主要成因 |
|---|---|---|
| refresh 慢 | plan 几乎全花在读取 | 资源多 + provider 查询慢 |
| 串行依赖 | 前后资源卡顿 | 依赖链过长、模块互相引用 |
| state 过大 | 每次操作都全量处理 | 未分区、未拆分 |
| 网络往返 | apply 极慢 | 每个资源多次 API 调用 |
一句话:先量化「时间花在哪」,再决定用并行、缓存还是拆 state;盲目调
-parallelism可能只是掩盖结构问题。
2. 并发与并行度
一句话总结:
-parallelism控制同时操作的资源数,默认 10,调高能提速但会放大 API 限流,调低则适合保守变更,合理值是按 provider 限流试出来的。
Terraform 的并发体现在「graph 中无依赖关系的资源可同时 apply」。-parallelism 默认 10,介于「不够」与「触发限流」之间需要实测。
# 用更大并行度尝试提速
terraform plan -parallelism=30
terraform apply -parallelism=30 -auto-approve
# 保守变更用小并行度,降低风险
terraform apply -parallelism=3 -auto-approve
2.1 并行度与限流平衡
云厂商有 API 限流(如 AWS DescribeInstances 每分钟配额)。并行度过高会触发 ThrottlingException,反而更慢。策略:先 10 → 20 → 30 递增,观察错误率。
# 用循环实测不同并行度下的耗时
for p in 10 20 30 40; do
echo "== parallelism=$p =="
/usr/bin/time -f "%e s" \
terraform plan -parallelism=$p -refresh=false >/dev/null
done
# provider 侧可配置重试与限流感知
provider "aws" {
region = var.region
retry_mode = "adaptive"
max_retries = 5
}
一句话:并行度是「提速旋钮」,但不是越大越好;配合 provider 的 adaptive 重试,才能既快又不触发限流。
3. Refresh 与 plan 优化
一句话总结: refresh 是 plan 的主要成本,
-refresh=false跳过读取、-refresh-only单独校准状态,按变更类型选择合理的 refresh 策略。
terraform plan 默认先 refresh(读取真实状态对比)。如果只是改某几个资源,可以关掉 refresh 提速;但「跳过 refresh」会让状态与实况失准,需谨慎。
# 只规划目标资源,跳过全量 refresh
terraform plan -refresh=false -target=module.app
# 单独校准状态(不产生变更计划)
terraform plan -refresh-only
# 正式变更前仍建议保留 refresh
terraform plan -detailed-exitcode
3.1 refresh 的合理使用
| 场景 | 建议 |
|---|---|
| 日常大仓库 plan | 保持 refresh,接受耗时 |
| 局部小改 | -refresh=false + -target |
| 怀疑漂移 | -refresh-only 单独校准 |
| CI 计划阶段 | 用 -refresh=false 提速门禁 |
一句话:refresh 是「准确性的成本」,按场景选择保留或跳过,但「跳过」的前提是你能接受状态短暂失准。
4. State 分区与拆分
一句话总结: 单一大 state 是性能与风险的双重敌人,按「环境 × 子域」拆分成多个 state,让每次操作只碰必要的一部分。
几千个资源的单一 state,任何一次 plan 都要全量处理。拆分思路:按环境拆(dev/staging/prod 各自 state),按子域拆(network/data/app 各自 state),再通过 terraform_remote_state 共享必要数据。
terraform {
backend "s3" {
bucket = "tfstate-prod"
key = "network/terraform.tfstate" # 网络层独立 state
region = "ap-northeast-1"
}
}
# 应用层通过 remote state 读取网络层输出
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "tfstate-prod"
key = "network/terraform.tfstate"
region = "ap-northeast-1"
}
}
resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.network.outputs.app_subnet_id
}
4.1 拆分边界
| 拆分维度 | 粒度 | 示例 |
|---|---|---|
| 环境 | dev / staging / prod | 各一个 state |
| 子域 | network / data / app | 各一个 state |
| 团队 | 平台 / 业务 | 各一个 state |
| 区域 | 按 region | 各一个 state |
拆分不是一次到位:先把最大的 state 里的网络与数据资源迁到独立 backend,观察 plan 耗时变化,再逐步切分其余部分;迁移用 terraform state mv,注意先备份 state。
# 备份后把资源迁到新的独立 state
cp terraform.tfstate terraform.tfstate.bak
terraform state mv \
module.app.aws_instance.web aws_instance.web
一句话:state 拆分让「每次操作只面对小世界」,remote state 只暴露需要的输出,是规模化的必经之路。
5. Remote State 治理
一句话总结: remote state 治理解决「并发冲突、权限隔离、版本一致」三件事:锁防并发、bucket 权限防越权、key 规范防混乱。
多人协作时 remote state 的核心是锁。S3 后端用 DynamoDB 锁,apply 期间其他操作被挡;GCS 与 azurerm 后端自带锁。锁能避免「两个 apply 同时写坏 state」。
terraform {
backend "s3" {
bucket = "tfstate-prod"
key = "app/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-locks" # 分布式锁
}
}
5.1 权限隔离
每个子域 state 的读写权限最小化:网络组只碰 network key,应用组只碰 app key,避免误写他人 state。
data "aws_iam_policy_document" "state_access" {
statement {
effect = "Allow"
actions = [
"s3:GetObject",
"s3:PutObject",
"dynamodb:GetItem",
"dynamodb:PutItem",
]
resources = [
"arn:aws:s3:::tfstate-prod/network/*",
"arn:aws:dynamodb:ap-northeast-1:*:table/terraform-locks",
]
}
}
5.2 锁冲突排查
多人同时 apply 会报 Error acquiring the state lock。排查流程:找到锁表里的持有者、确认是否僵尸锁,超时等待或手动释放。
# 用 -lock-timeout 等待锁释放
terraform apply -lock-timeout=5m
# 查看 DynamoDB 锁记录
aws dynamodb scan --table-name terraform-locks
# 确认无人在跑时强制解锁
terraform force-unlock <LOCK_ID>
一句话:remote state 治理是「把后端的并发、权限、命名规范显式写出来」,否则规模一大就会出现「谁改了谁的 state」。
6. 依赖图与资源分组优化
一句话总结: 依赖图决定「哪些资源能并行」,用模块与数据引用减少虚假依赖,用
depends_on只在必要时显式声明。
Terraform 根据引用自动建图:A 引用 B 的 id,A 就必须等 B。虚假依赖(不该串行的串行了)会拖慢整个 apply。检查依赖链:
terraform graph -type=plan | dot -Tpng > graph.png
# 或文本查看依赖
terraform graph -type=plan | head -40
6.1 减少虚假依赖
- 用 data source 替代资源引用(读已有资源不产生依赖)。
- 模块间只暴露必要 output。
depends_on只用于「无引用但需顺序」的场合。
# 优先 data source 而非跨模块引用
data "aws_subnet" "shared" {
id = var.shared_subnet_id # 不依赖网络模块
}
一句话:依赖图越瘦,并行度越高;把「引用」换成本地变量或 data source,是改性能最便宜的一招。
7. Provider 缓存与网络优化
一句话总结: provider 二进制下载与插件初始化是每次 init 的固定成本,用本地镜像与缓存目录复用,网络往返则靠减小每次操作范围来压。
terraform init 每次拉 provider 插件会消耗时间。Terraform 有 provider 缓存目录(plugin_cache_dir),CI 里可预热;-upgrade=false 避免重复检查新版本。
# 启用 provider 缓存(terraformrc)
cat > ~/.terraformrc <<'EOF'
plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"
EOF
# init 时跳过升级检查
terraform init -backend=false -upgrade=false
7.1 网络与 API 优化
- 把 plan/apply 移到离目标云区域近的 Runner,减少延迟。
- 对只读 provider(如
random、local)不必做网络请求。 - 大批量同类资源优先用
for_each而非count,依赖更清晰。
resource "aws_s3_bucket" "per_env" {
for_each = toset(["dev", "staging", "prod"])
bucket = "assets-${each.key}"
}
一句话:缓存省的是「重复的固定成本」,网络优化省的是「每次调用的边际成本」,两者都值得但效果因仓库而异。
8. 总结
大规模 Terraform 的性能优化,是从「看得见、摸得着」的旋钮到结构改造的层层递进:
| 手段 | 做法 | 收益 |
|---|---|---|
| 量化 | 定位耗时分布 | 明确方向 |
| 并行度 | 调 -parallelism + adaptive 重试 | 提速 |
| refresh | 按场景跳过或单独校准 | 降耗时 |
| state 拆分 | 按环境/子域分区 | 缩小操作范围 |
| 后端治理 | 锁 + 权限隔离 + key 规范 | 防冲突 |
| 依赖图 | 消除虚假依赖 | 提升并行度 |
| 缓存 | provider 缓存 + 镜像 | 降固定成本 |
一句话收尾:性能优化不是「调大一个参数」,而是「让每次操作只面对它该面对的那部分世界」。先量化瓶颈,再用并行度与 refresh 策略拿到第一层收益,最后靠 state 拆分与依赖图瘦身解决结构性问题——当仓库跨过千级资源门槛,这套方法论比任何单点调参都更持久有效。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。