terraform

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Terraform & OpenTofu

Terraform & OpenTofu

File Organization & Naming

文件组织与命名规范

FilePurpose
terraform.tf
Terraform + provider version requirements
providers.tf
Provider configurations
main.tf
Primary resources and data sources
variables.tf
Input variables (alphabetical)
outputs.tf
Output values (alphabetical)
locals.tf
Local values
  • Lowercase with underscores:
    web_api
    , not
    webAPI
    or
    web-api
  • Descriptive nouns excluding resource type:
    aws_instance.web_api
    not
    aws_instance.web_api_instance
  • Singular, not plural
  • this
    for singleton resources (one of that type per module)
  • Contextual variable prefixes:
    vpc_cidr_block
    not
    cidr
文件用途
terraform.tf
Terraform及provider版本要求
providers.tf
Provider配置
main.tf
核心资源与数据源
variables.tf
输入变量(按字母排序)
outputs.tf
输出值(按字母排序)
locals.tf
本地变量
  • 使用小写加下划线:
    web_api
    ,而非
    webAPI
    web-api
  • 描述性名词,不含资源类型:
    aws_instance.web_api
    而非
    aws_instance.web_api_instance
  • 使用单数形式,而非复数
  • 单例资源(每个模块中仅一个该类型资源)使用
    this
    命名
  • 变量添加上下文前缀:
    vpc_cidr_block
    而非
    cidr

Block Ordering

代码块排序规则

Resources:
count
/
for_each
(blank line after) → arguments → nested blocks →
tags
depends_on
lifecycle
(last)
Variables:
description
type
default
validation
nullable
Every variable needs
type
+
description
. Every output needs
description
. Mark secrets
sensitive = true
.
资源块:
count
/
for_each
(后接空行)→ 参数 → 嵌套块 →
tags
depends_on
lifecycle
(放在最后)
变量块:
description
type
default
validation
nullable
每个变量必须包含
type
description
。每个输出必须包含
description
。敏感数据需标记
sensitive = true

Module Structure

模块结构

TypeScopeExample
Resource ModuleSingle logical groupVPC + subnets, SG + rules
Infrastructure ModuleCollection of resource modulesNetworking + compute for one region
CompositionComplete infrastructureSpans regions/accounts
module-name/
├── main.tf, variables.tf, outputs.tf, versions.tf
├── examples/
│   ├── minimal/
│   └── complete/
└── tests/
    └── defaults.tftest.hcl
Keep modules small (single responsibility).
examples/
double as documentation and integration test fixtures. Semantic versioning for all published modules.
类型范围示例
资源模块单一逻辑组VPC+子网、安全组+规则
基础设施模块资源模块集合单区域内的网络+计算资源
组合模块完整基础设施跨区域/跨账号部署
module-name/
├── main.tf, variables.tf, outputs.tf, versions.tf
├── examples/
│   ├── minimal/
│   └── complete/
└── tests/
    └── defaults.tftest.hcl
保持模块轻量化(单一职责)。
examples/
目录同时作为文档和集成测试夹具。所有发布的模块遵循语义化版本规范。

count vs for_each

count vs for_each

ScenarioUse
Boolean toggle (create or skip)
count = condition ? 1 : 0
Named/keyed items that may reorder
for_each = toset(list)
or
map
Fixed identical replicas
count = N
Default to
for_each
-- removing a middle item from a
count
list recreates all subsequent resources. Use
count
only for boolean conditionals or truly identical replicas.
场景适用方式
布尔开关(创建或跳过)
count = condition ? 1 : 0
带名称/键且可能重排的条目
for_each = toset(list)
map
固定数量的相同副本
count = N
优先使用
for_each
——从
count
列表中移除中间项会导致后续所有资源重新创建。仅在布尔条件或完全相同的副本场景下使用
count

Testing

测试方案

SituationApproach
Quick validation
terraform fmt -check && terraform validate
Pre-commit+
tflint
+
trivy config .
/
checkov -d .
Logic validation (1.6+)Native
terraform test
with
command = plan
Cost-free unit tests (1.7+)Native tests +
mock_provider
Real infra validationNative tests with
command = apply
, or Terratest (Go)
Native test essentials (
.tftest.hcl
in
tests/
):
  • command = plan
    for fast unit tests;
    command = apply
    for integration (default)
  • assert { condition = expr; error_message = "..." }
    -- multiple per run block
  • expect_failures = [var.name]
    for negative testing (validate rejection of bad input)
  • mock_provider "aws" { mock_resource "..." { defaults = { ... } } }
    -- plan-mode only, no credentials, fast CI
  • variables {}
    at file level (all runs) or within a
    run
    block (override)
  • Reference prior run outputs:
    run.setup.vpc_id
  • parallel = true
    on independent runs with separate state -- creates sync point at next sequential run
  • state_key = "name"
    required for
    parallel = true
    runs with independent state
  • File naming:
    *_unit_test.tftest.hcl
    (plan mode) vs
    *_integration_test.tftest.hcl
    (apply mode)
  • A
    module {}
    block inside a
    run
    accepts local paths and registry modules only -- not git or HTTP sources. Repos consuming git-sourced modules must vendor or localize them before they can be tested.
  • After a test file completes, resources are destroyed in reverse run-block order. Order dependent runs accordingly (create the bucket before the run that puts objects in it), or the destroy fails and leaves billable resources behind. There is no CLI flag to skip cleanup -- inspect a failure with
    -verbose
    .
Running them:
bash
terraform test                                     # all *.tftest.hcl under tests/
terraform test -filter=vpc_unit_test.tftest.hcl    # one test FILE (not a run-block name)
terraform test -verbose                            # show the plan/apply per run block
terraform test -test-directory=path                # non-default test dir
Split by cost in CI: plan-mode unit tests on every PR, apply-mode integration tests on merge only.
场景实施方法
快速验证
terraform fmt -check && terraform validate
预提交检查新增
tflint
+
trivy config .
/
checkov -d .
逻辑验证(1.6+版本)原生
terraform test
搭配
command = plan
无成本单元测试(1.7+版本)原生测试 +
mock_provider
真实基础设施验证原生测试搭配
command = apply
,或使用Terratest(Go语言)
原生测试核心要点
tests/
目录下的
.tftest.hcl
文件):
  • command = plan
    用于快速单元测试;
    command = apply
    用于集成测试(默认值)
  • assert { condition = expr; error_message = "..." }
    ——每个运行块可包含多个断言
  • expect_failures = [var.name]
    用于负面测试(验证对非法输入的拒绝)
  • mock_provider "aws" { mock_resource "..." { defaults = { ... } } }
    ——仅支持plan模式,无需凭证,CI执行速度快
  • variables {}
    可放在文件级别(所有运行块生效)或
    run
    块内(覆盖全局配置)
  • 引用之前运行块的输出:
    run.setup.vpc_id
  • 独立运行块且状态分离时,设置
    parallel = true
    ——会在后续顺序运行块处创建同步点
  • 使用
    parallel = true
    且状态分离的运行块必须设置
    state_key = "name"
  • 文件命名规范:
    *_unit_test.tftest.hcl
    (plan模式) vs
    *_integration_test.tftest.hcl
    (apply模式)
  • run
    块内的
    module {}
    仅接受本地路径和注册表模块——不支持git或HTTP源。使用git源模块的仓库必须先将其本地化或 vendoring 后才能测试。
  • 测试文件执行完成后,资源会按运行块逆序销毁。需按依赖顺序编排运行块(先创建存储桶,再执行向桶中存入对象的运行块),否则销毁会失败并留下产生费用的资源。目前没有CLI flag可跳过清理——可使用
    -verbose
    查看失败详情。
运行测试命令:
bash
terraform test                                     # 运行tests/下所有*.tftest.hcl文件
terraform test -filter=vpc_unit_test.tftest.hcl    # 运行单个测试文件(而非运行块名称)
terraform test -verbose                            # 显示每个运行块的plan/apply详情
terraform test -test-directory=path                # 指定非默认测试目录
在CI中按成本拆分:每个PR执行plan模式单元测试,仅在合并时执行apply模式集成测试。

Version Pinning

版本固定策略

ComponentStrategyExample
TerraformPin minor
required_version = "~> 1.9"
ProvidersPin major
version = "~> 5.0"
Modules (prod)Pin exact
version = "5.1.2"
Modules (dev)Allow patch
version = "~> 5.1"
Key modern features:
moved
blocks (1.1+),
optional()
with defaults (1.3+), native testing (1.6+), mock providers (1.7+), cross-variable validation (1.9+), write-only arguments (1.11+). Stacks (HCP -- check current release status): orchestrates multiple configs as a single deployment unit -- evaluate for multi-environment patterns.
组件策略示例
Terraform固定小版本
required_version = "~> 1.9"
Providers固定大版本
version = "~> 5.0"
生产环境模块固定精确版本
version = "5.1.2"
开发环境模块允许补丁版本更新
version = "~> 5.1"
关键现代特性:
moved
块(1.1+)、带默认值的
optional()
(1.3+)、原生测试(1.6+)、mock providers(1.7+)、跨变量验证(1.9+)、只写参数(1.11+)。 Stacks(HCP——请查看当前发布状态):将多个配置作为单个部署单元编排——可评估用于多环境模式。

State & Security

状态与安全

  • Remote backend with locking: S3 with
    use_lockfile = true
    (1.10+), Azure Blob, GCS, or Terraform Cloud. Never local state for shared infrastructure. DynamoDB-based S3 locking (
    dynamodb_table
    ) is deprecated and slated for removal -- prefer
    use_lockfile
    ; both may be set at once while migrating an existing table off.
  • Encrypt state at rest. Never commit
    .tfstate
    ,
    .terraform/
    , or
    *.tfplan
    . Always commit
    .terraform.lock.hcl
    .
  • default_tags
    on provider for consistent resource tagging.
  • Encryption at rest on all storage. Private networking by default -- public access is opt-in.
  • Least-privilege security groups. No
    0.0.0.0/0
    ingress without explicit justification.
  • Never hardcode credentials -- use assume_role, OIDC, or secrets managers.
  • Pre-commit: auto-format first (
    terraform fmt -recursive
    -- rewrites files), then verify (
    terraform validate && tflint && trivy config .
    )
  • moved { from = old; to = new }
    for refactoring resource names/modules without destroy-recreate. Remove block after apply.
  • 带锁机制的远程后端:S3搭配
    use_lockfile = true
    (1.10+)、Azure Blob、GCS或Terraform Cloud。共享基础设施绝不能使用本地状态。基于DynamoDB的S3锁(
    dynamodb_table
    )已被弃用并将被移除——优先使用
    use_lockfile
    ;迁移现有表期间可同时设置两者。
  • 静态加密状态文件。绝不要提交
    .tfstate
    .terraform/
    *.tfplan
    文件。必须提交
    .terraform.lock.hcl
    文件。
  • 在provider上设置
    default_tags
    以实现一致的资源标记。
  • 所有存储开启静态加密。默认使用私有网络——公网访问为可选配置。
  • 遵循最小权限原则配置安全组。无明确理由时禁止使用
    0.0.0.0/0
    的入站规则。
  • 绝不硬编码凭证——使用assume_role、OIDC或 secrets managers。
  • 预提交检查:先自动格式化(
    terraform fmt -recursive
    ——自动改写文件),再进行验证(
    terraform validate && tflint && trivy config .
  • 重构资源名称/模块时使用
    moved { from = old; to = new }
    ,避免销毁重建。apply完成后移除该块。

Troubleshooting

故障排查

  • State lock stuck:
    terraform force-unlock <ID>
    -- only after confirming no other operation running
  • Resource drift:
    terraform plan -refresh-only
    to detect,
    terraform apply -refresh-only
    to accept
  • Replace tainted:
    terraform apply -replace=ADDR
    (not deprecated
    terraform taint
    )
  • Import existing:
    import
    blocks (1.5+) for declarative import, or
    terraform import ADDR ID
  • 状态锁卡住:
    terraform force-unlock <ID>
    ——仅在确认无其他操作运行时使用
  • 资源漂移:
    terraform plan -refresh-only
    检测漂移,
    terraform apply -refresh-only
    接受漂移
  • 替换污染资源:
    terraform apply -replace=ADDR
    (替代已弃用的
    terraform taint
  • 导入现有资源:使用
    import
    块(1.5+)声明式导入,或
    terraform import ADDR ID
    命令

Dependency Management

依赖管理

Use
locals
with
try()
to control deletion ordering without explicit
depends_on
:
hcl
locals {
  vpc_id = try(aws_vpc_ipv4_cidr_block_association.this[0].vpc_id, aws_vpc.this.id, "")
}
This forces Terraform to destroy subnets before CIDR associations -- prevents deletion errors.
  • cidrsubnet(var.vpc_cidr, 8, count.index)
    for calculated subnet CIDRs -- never hardcode subnets
  • Multi-region:
    provider "aws" { alias = "eu_west_1" }
    +
    providers = { aws = aws.eu_west_1 }
    in module blocks
使用
locals
搭配
try()
控制删除顺序,无需显式
depends_on
hcl
locals {
  vpc_id = try(aws_vpc_ipv4_cidr_block_association.this[0].vpc_id, aws_vpc.this.id, "")
}
这会强制Terraform先销毁子网,再销毁CIDR关联——避免删除错误。
  • 使用
    cidrsubnet(var.vpc_cidr, 8, count.index)
    计算子网CIDR——绝不硬编码子网
  • 多区域部署:
    provider "aws" { alias = "eu_west_1" }
    + 在模块块中设置
    providers = { aws = aws.eu_west_1 }

Verify

最终验证

Run before declaring done:
bash
terraform fmt -check && terraform validate && tflint && trivy config .
All commands must pass with zero errors. Where plan-mode tests exist, add
terraform test -filter=<unit-test-file>
-- restrict this to plan-mode suites, since apply-mode tests stand up real infrastructure and do not belong in a pre-completion check.
完成前运行以下命令:
bash
terraform fmt -check && terraform validate && tflint && trivy config .
所有命令必须零错误通过。若存在plan模式测试,需添加
terraform test -filter=<unit-test-file>
——仅限plan模式套件,因为apply模式测试会创建真实基础设施,不属于完成前检查环节。