「大规模基础设施性能」

优化大规模 Terraform 基础设施的性能:并发与并行度调优、refresh 与 plan 优化、state 分区与拆分、remote state 治理,以及依赖图与缓存优化。

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 拆分与依赖图瘦身解决结构性问题——当仓库跨过千级资源门槛,这套方法论比任何单点调参都更持久有效。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「terraform」更多文章

  1. Helm Provider 与应用发布:值注入与回滚
  2. 模块注册表与分发:版本、文档与测试
  3. DNS 与证书编排:托管区域与自动验证