Terraform状态管理

docker

tfstate状态管理

状态文件

即期望的线上环境状态;

代码有 + 状态无 = 需要新增资源
代码无 + 状态有 = 需要删除资源

状态文件内记录着代码和输入的全部信息, 也包含输入的全部明文密码;

文件安全

  1. 支持加密存储的远程后端
  2. 敏感信息分级, 避免非常敏感的数据被存入状态

管理cli

1
2
3
4
5
# 迁移状态文件到 新定义的后端, 数据不会丢失
terraform init -migrate-state

# 重新配置后端, 不会迁移状态文件
terraform init -reconfigure -backend-config="conn_str=$PGURL"
1
2
3
4
5
6
terraform plan

  + create                                  # 创建资源
  - del                                     # 删除资源
  ~ update in-place                         # 原地更新资源属性, 安全
-/+ destroy and then create replacement     # 删除后创建

漂移处理

场景: 未通过 tf 修改了云上资源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 生成计划查看当前状态与配置的差异
terraform plan -out=tfplan

# 方法 1: 以本地为准
terraform apply

# 方法 2: 以云上为准, 逐个确认
# 生成一个计划, 告诉哪些地方发生了"漂移", 并询问是否要将这些变化写入状态文件
# 注意: 只是合并到了状态文件, tf 代码还得手动写一份
terraform plan -refresh-only

# 确保没有任何差异
terraform plan

状态管理实践

1阶段, 单人维护某项目, 本地存储 terraform.tfstate
2阶段, 状态文件可以提交到 git 但是需要扫描避免泄漏密钥等信息
3阶段, cicd串行执行 apply, 避免人工执行变更
4阶段, 远程存储+锁机制, 在加cicd串行
5阶段, 权限控制, 只允许cdcd进行 apply

企业内网环境: 可以使用 pg
gitlib/gitea 等仓库存储, 适合统一规范时

后端

其它支持: gcs oss,

local

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 默认的配置
terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}

# 数据源查询
data "terraform_remote_state" "base" {
  backend = "local"  # 使用 local 后端

  config = {
    # 相对于当前执行目录的路径
    path = "../01-base/terraform.tfstate"
  }
}

gitea

1.26+ 开始支持

Settings -> Applications 中生成一个 Access Token

state_name: 状态文件的名称(例如 pve-cluster-prod); 建议每个独立的 .tf 项目对应一个唯一的名称

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
terraform {
  backend "http" {
    # Gitea 地址, 所有者和仓库名
    address        = "https://git.services.wait/api/v1/packages/chenwx/terraform/state/local-pve"
    lock_address   = "https://git.services.wait/api/v1/packages/chenwx/terraform/state/local-pve/lock"
    unlock_address = "https://git.services.wait/api/v1/packages/chenwx/terraform/state/local-pve/lock"

    # 鉴权配置
    username = "chenwx"
    password = "564ad2cd0112723ed85730e85a4aa2115b8fedc8" # 填入生成的 Token
  }
}

postgres

注解

  1. schema 默认是 terraform_remote_state 用于区分不同项目
  2. 表名固定为 states

创建库

1
CREATE DATABASE tfstate_db OWNER wait;
1
2
3
4
5
6
terraform {
  backend "pg" {
    conn_str = "postgres://wait:passw0rd@192.168.5.9:5432/tfstate?sslmode=disable"
    # schema_name = "terraform_remote_state"
  }
}

pg 后端支持的入参方式

  1. backend.conn_str 固定

  2. init -backend-config 时传递

    1
    
    terraform init -backend-config="conn_str=postgres://user:pass@db:5432/db?sslmode=disable"
    
  3. 环境变量传递

    1
    
    export PG_CONN_STR=postgres://wait:passw0rd@192.168.5.9:5432/tfstate_db?sslmode=disable
    
  4. 使用外部配置文件 (.tfbackend)

    1
    2
    
    # secret.pg.tfbackend
    conn_str = "postgres://user:pass@db:5432/db?sslmode=disable"
    
    1
    
    terraform init -backend-config=secret.pg.tfbackend
    
  5. vault

    1
    2
    
    # 从 vault 内获取连接字符串
    export PG_CONN_STR=$(vault kv get -field=conn_str homelab/infra/postgres/tf)
    

s3

方案 1: s3 + DynamoDB
方案 2: s3 + s3 原生锁

HashiCorp 官方已经宣布 DynamoDB 做状态锁的方式会被废弃, 未来推荐用 S3 原生锁

s3 原生锁

  1. 会通过自动创建和删除 .tflock 文件进行状态锁定
  2. 桶必须在创建时就启用对象锁(Object Lock)功能和版本功能

原生锁的原理
通过 If-None-Match 机制在文件不存在时才创建, 第一个任务执行成功时获取到了文件锁, 执行完成后会删除此文件;
但是如果执行到一半退出, 不一定会释放成功;

对象锁: 目的为了避免意外删除;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 创建启用对象锁的存储桶
aws s3api create-bucket --bucket your-terraform-state-bucket --region us-east-1 \
  --object-lock-enabled-for-bucket

# 启用版本控制 (原生锁的强依赖)
aws s3api put-bucket-versioning --bucket your-terraform-state-bucket \
  --versioning-configuration Status=Enabled

# 对于已经存在的桶-启用对象锁
aws s3api put-object-lock-configuration --bucket your-existing-bucket \
    --object-lock-configuration '{"ObjectLockEnabled": "Enabled"}'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
terraform {
  backend "s3" {
    bucket         = "your-terraform-state-bucket"
    # 多环境可以共用一个桶, 但 key 需要区分开
    key            = "path/to/your/statefile.tfstate"
    region         = "us-east-1"
    encrypt        = true
    use_lockfile   = true   # 启用 S3 原生锁定
  }
}

DynamoDB 锁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# create bucket
aws s3 mb s3://my-terraform-state --region ap-northeast-1

# 启用版本控制
aws s3api put-bucket-versioning --bucket my-terraform-state \
    --versioning-configuration Status=Enabled

# DynamoDB 表必须预先创建并配置主键 LockID
aws dynamodb create-table \
    --table-name terraform-state-locks \
    --attribute-definitions AttributeName=LockID,AttributeType=S \
    --key-schema AttributeName=LockID,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    region         = "ap-northeast-1"
    key            = "prod/vpc/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "terraform-state-locks"    # 状态锁表
    encrypt        = true                       # 启用加密
	# kms_key_id     = "alias/terraform-kms"
	# profile        = "terraform" 		    # 可选
    # 可选, 使用 assume role
    # role_arn       = "arn:aws:iam::ACCOUNT_ID:role/TerraformRole"
  }
}

s3 管理cli

启用版本控制时的 快速回滚

1
2
3
4
5
6
7
8
9
# 查看状态版本历史
aws s3api list-object-versions --bucket my-terraform-state \
    --prefix network/terraform.tfstate

# 回滚到特定版本
aws s3api get-object --bucket my-terraform-state \
    --key network/terraform.tfstate \
    --version-id <version-id> \
    terraform.tfstate

意外锁定时的处理流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

# 1. 查看资源锁定状态
aws ec2 describe-vpc-attribute --vpc-id vpc-0abcd1234ef567890 \
    --attribute enableDhcpOptions

# 2. 如果是 Terraform 状态锁定, 检查 DynamoDB
aws dynamodb get-item --table-name terraform-locks \
    --key '{"LockID": {"S": "project-terraform"}}'

# 子目录时
aws dynamodb get-item --table-name terraform-state-lock \
  --key '{"LockID":{"S":"my-terraform-state-bucket/prod/terraform.tfstate-md5"}}'


# 扫描 DynamoDB 表中 LockID
aws dynamodb scan --table-name terraform-lock

# 3. 强制解锁(仅在确认无其他 Terraform 进程时)
terraform force-unlock <lock-id>

minio

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
terraform {
  backend "s3" {
    bucket         = "terraform"
    key            = "homeLab/proxmox/terraform.tfstate"
    region         = "us-east-1"
    endpoints = {
        s3 = "https://minio.chenwx.top"
    }
    skip_requesting_account_id = true       # 不尝试获取 AWS 账号 ID
    skip_credentials_validation = true
    skip_metadata_api_check     = true

    # MinIO 通常需要使用路径风格, 即 bucket 在 路径中而不是子域名里面
    force_path_style            = true
    use_lockfile                = true      # 原生文件锁
    encrypt                     = false     # 本地没有加密

    access_key=""
    secret_key=""
  }
}
1
2
export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=

HCP Terraform

官方远程状态管理

免费额度: 500元, 按资源数计费

1
2
3
4
5
6
7
8
terraform {
  cloud {
    organization = "my-org"
    workspaces {
      name = "dev-workspace"
    }
  }
}

阿里云 oss

  1. 创建 alicloud_oss_bucket, alicloud_ots_instance, alicloud_ots_table 资源
  2. 将代码配置添加到一个名为 backend.tf 的新 Terraform 配置文件中, 接着运行 terraform init
  3. 不需要考虑冲突时, 也可以不配置 tablestore_endpoint 和 tablestore_table

表格存储: 实例和表需要提前创建好, 表里必须有一个主键列, 名称为 LockID, 类型为字符串(String)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
terraform {
  backend "oss" {
    bucket = "bucket-for-terraform-state"
    prefix   = "tf/state"
    key   = "pro.tfstate"
    region = "cn-beijing"

    tablestore_endpoint = "https://terraform-remote.cn-hangzhou.ots.aliyuncs.com"
    tablestore_table = "statelock"
  }
}

读取另一个项目的状态数据文件

terraform_remote_state 是专门为远程后端设计的数据源

一般数据源方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
data "terraform_remote_state" "base" {
  backend = "oss"
  config = {
    bucket = "my-terraform-state"
    key    = "dev/01-base/terraform.tfstate"
    region = "cn-beijing"
  }
}

resource "alicloud_instance" "web" {
  vswitch_id = data.terraform_remote_state.base.outputs.vswitch_id
  # ...
}

http

http 远程后端 - 只读;

需要自己去维护这个 http 远程文件是否是最新状态

1
2
3
4
5
6
7
data "terraform_remote_state" "base" {
  backend = "http"

  config = {
    address = "http://localhost:8080/terraform.tfstate"
  }
}
Licensed under CC BY-NC-SA 4.0
转载或引用本文时请遵守许可协议,知会作者并注明出处
不得用于商业用途!
最后更新于 2025-09-20 00:00 UTC