Terraform状态管理
tfstate状态管理
状态文件
即期望的线上环境状态;
代码有 + 状态无 = 需要新增资源 代码无 + 状态有 = 需要删除资源
状态文件内记录着代码和输入的全部信息, 也包含输入的全部明文密码;
文件安全
- 支持加密存储的远程后端
- 敏感信息分级, 避免非常敏感的数据被存入状态
管理cli
# 迁移状态文件到 新定义的后端, 数据不会丢失
terraform init -migrate-state
# 重新配置后端, 不会迁移状态文件
terraform init -reconfigure -backend-config="conn_str=$PGURL"terraform plan
+ create # 创建资源
- del # 删除资源
~ update in-place # 原地更新资源属性, 安全
-/+ destroy and then create replacement # 删除后创建漂移处理
场景: 未通过 tf 修改了云上资源
# 生成计划查看当前状态与配置的差异
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
# 默认的配置
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 项目对应一个唯一的名称
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
注解
- schema 默认是 terraform_remote_state 用于区分不同项目
- 表名固定为 states
创建库
CREATE DATABASE tfstate_db OWNER wait;terraform {
backend "pg" {
conn_str = "postgres://wait:passw0rd@192.168.5.9:5432/tfstate?sslmode=disable"
# schema_name = "terraform_remote_state"
}
}pg 后端支持的入参方式
-
backend.conn_str 固定
-
init -backend-config 时传递
terraform init -backend-config="conn_str=postgres://user:pass@db:5432/db?sslmode=disable" -
环境变量传递
export PG_CONN_STR=postgres://wait:passw0rd@192.168.5.9:5432/tfstate_db?sslmode=disable -
使用外部配置文件 (.tfbackend)
# secret.pg.tfbackend conn_str = "postgres://user:pass@db:5432/db?sslmode=disable"terraform init -backend-config=secret.pg.tfbackend -
vault
# 从 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 原生锁
- 会通过自动创建和删除 .tflock 文件进行状态锁定
- 桶必须在创建时就启用对象锁(Object Lock)功能和版本功能
原生锁的原理 通过 If-None-Match 机制在文件不存在时才创建, 第一个任务执行成功时获取到了文件锁, 执行完成后会删除此文件; 但是如果执行到一半退出, 不一定会释放成功;
对象锁: 目的为了避免意外删除;
# 创建启用对象锁的存储桶
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"}'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 锁
# 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_REQUESTterraform {
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
启用版本控制时的 快速回滚
# 查看状态版本历史
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. 查看资源锁定状态
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
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=""
}
}export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=HCP Terraform
官方远程状态管理
免费额度: 500元, 按资源数计费
terraform {
cloud {
organization = "my-org"
workspaces {
name = "dev-workspace"
}
}
}阿里云 oss
- 创建 alicloud_oss_bucket, alicloud_ots_instance, alicloud_ots_table 资源
- 将代码配置添加到一个名为 backend.tf 的新 Terraform 配置文件中, 接着运行 terraform init
- 不需要考虑冲突时, 也可以不配置 tablestore_endpoint 和 tablestore_table
表格存储: 实例和表需要提前创建好, 表里必须有一个主键列, 名称为 LockID, 类型为字符串(String)
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 是专门为远程后端设计的数据源
一般数据源方式
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 远程文件是否是最新状态
data "terraform_remote_state" "base" {
backend = "http"
config = {
address = "http://localhost:8080/terraform.tfstate"
}
}