找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4098

积分

0

好友

540

主题
发表于 3 小时前 | 查看: 4| 回复: 0

Terraform 的配置文件描述了期望的云端资源,而 state 则记录了 Terraform 已知的真实对象、资源地址、依赖关系和部分属性。一次 apply 是否会创建、更新或销毁资源,完全取决于配置、远端 API 与 state 之间是否对得上。状态管理失误最可怕的不是命令直接报错,而是 Terraform 对生产对象产生了错误映射,随后可能引发重建、删除甚至接管错误资源的计划。

本文以 Terraform CLI 1.x 和 AWS S3 远端后端为例。<AWS区域><状态桶><状态对象键><环境><资源地址> 等均为占位符,请务必替换成你自己的实际值。S3 后端的锁机制随版本演进,建议先执行 terraform versionterraform init -help,采用组织认可的版本和方案。state rmstate mvimportforce-unlockstate push 以及迁移等操作均属有影响操作,必须在计划的变更窗口内执行。

State 为什么不是普通 JSON

本地 state 文件通常叫 terraform.tfstate,但它绝不应该被当作手动维护的 JSON 配置文件。Terraform 利用 state 中的资源地址来映射 HCL 对象,再从 provider 读取远端资源。输出、连接参数、密码或 token 等信息都可能出现在 state 中;sensitive 标记只能减少 CLI 显示,并不能保证 state 中不包含秘密。因此,state 同时承载了控制面数据和敏感数据,需要版本控制、访问控制、锁、备份与审计。

首先要确认当前目录是否已经初始化、使用哪个工作区、有没有意外遗留的本地 state。发现本地 state 切不可立即删除:它可能是远端迁移前的唯一副本,也可能是独立环境的状态。

# 代码 01:检查版本、provider、工作区与本地 state 文件
terraform version
terraform providers
terraform workspace show
find . -maxdepth 2 -type f \( -name '*.tfstate' -o -name '*.tfstate.*' \) -print

不要把 state、计划文件或私密变量提交到仓库。是否忽略全部 .tfvars 取决于团队是否管理无敏感参数的文件;如果要提交公共变量,应仅忽略明确的私密文件。

# 代码 02:.gitignore 的最小规则
.terraform/
*.tfstate
*.tfstate.*
*.tfplan
crash.log
crash.*.log
secrets.auto.tfvars

先建立可恢复的远端后端

远端后端应在业务资源之前完成搭建。不要在同一份配置里一边创建 S3 bucket,一边又立刻把它设为自己的 backend;bootstrap 过程应该使用独立目录和受控的临时本地 state。桶的创建属于基础设施变更:确认名称、区域和组织策略,绝不可误用已有的生产数据桶。

# 代码 03:在非 us-east-1 区域创建专用状态桶
STATE_BUCKET="<状态桶>"
AWS_REGION="<AWS区域>"

aws s3api create-bucket \
  --bucket "${STATE_BUCKET}" \
  --region "${AWS_REGION}" \
  --create-bucket-configuration LocationConstraint="${AWS_REGION}"

S3 版本控制可以恢复错误的覆盖或误推送,但不能替代访问控制。下面启用版本控制和 SSE-S3;假如组织要求 KMS,应替换为已批准的 key 并检查 CI、运维、灾难恢复主体的密钥策略。

# 代码 04:启用状态桶版本控制与默认加密
STATE_BUCKET="<状态桶>"

aws s3api put-bucket-versioning \
  --bucket "${STATE_BUCKET}" \
  --versioning-configuration Status=Enabled

aws s3api put-bucket-encryption \
  --bucket "${STATE_BUCKET}" \
  --server-side-encryption-configuration \
  '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

状态桶的访问应当最小化,并拒绝非安全传输。以下 policy 中的 <允许访问状态的IAM主体ARN> 需替换为最小权限角色;绝不能将 Principal 星号作为允许主体。生产环境部署还需与组织的显式 Deny、VPC endpoint 和跨账号策略一同校验。

// 代码 05:state-bucket-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyInsecureTransport",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::<状态桶>",
        "arn:aws:s3:::<状态桶>/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    },
    {
      "Sid": "AllowTerraformStateRole",
      "Effect": "Allow",
      "Principal": {
        "AWS": "<允许访问状态的IAM主体ARN>"
      },
      "Action": [
        "s3:ListBucket",
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::<状态桶>",
        "arn:aws:s3:::<状态桶>/*"
      ]
    }
  ]
}
# 代码 06:设置 policy 后验证版本控制与加密
aws s3api put-bucket-policy --bucket '<状态桶>' --policy file://state-bucket-policy.json
aws s3api get-bucket-versioning --bucket '<状态桶>'
aws s3api get-bucket-encryption --bucket '<状态桶>'

S3 原生锁文件需要执行角色具备对应 lock 对象的读写删除权限;权限集合应依据当前 Terraform 后端文档与实际 key 前缀来制定,不能笼统地授予 s3:*

后端、版本与环境隔离

后端定义可以写在代码中,但环境实际值通过 backend-config 传入,这样可以避免账户信息散落在 HCL 里。use_lockfile 需要当前 Terraform 版本支持;若不支持,千万不要自己造轮子实现锁方案。

# 代码 07:backend.tf
terraform {
  required_version = ">= 1.6.0, < 2.0.0"

  backend "s3" {
    encrypt      = true
    use_lockfile = true
  }
}

Provider 与模块版本会改变 state schema 和 plan 结果。限制版本并提交 .terraform.lock.hcl,可防止某台 CI worker 私自升级 provider 后产生刷新差异。

# 代码 08:versions.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
provider "aws" {
  region = var.aws_region
}
# 代码 09:初始化指定环境的远端 state
terraform init \
  -backend-config="bucket=<状态桶>" \
  -backend-config="key=<环境>/<状态对象键>" \
  -backend-config="region=<AWS区域>" \
  -reconfigure

reconfigure 会忽略已有的后端设置并重新初始化。它只适合确认后端参数变更的场景;对于已有 state 的目录,务必先备份并仔细核对迁移提示。初始化时是否复制 state,取决于源 state 与目标 key 是否属于同一个环境,不能习惯性输入 yes。

工作区只是逻辑分隔,不等于账号、网络或权限的隔离。生产和测试环境优先使用不同的账户、state key 和执行角色;即便使用 workspace,也要让 CI 显式选择。

# 代码 10:显式选择或创建工作区
terraform workspace list
terraform workspace select '<环境>' || terraform workspace new '<环境>'
terraform workspace show

日常操作:先读、再计划、后变更

查看 state 应优先使用 Terraform CLI,避免直接下载后手动修改。state showoutput 可能打印秘密,终端日志、构建日志和工单附件都需受控。

# 代码 11:列出资源地址并查看一项状态
terraform state list
terraform state show '<资源地址>'
terraform output
terraform output -json

变更前创建加密受控的本地备份。terraform state pull 会输出完整 state,备份目录权限必须收紧且禁止提交 Git。对象存储版本控制是第二道恢复能力,不能当作跳过快照的理由。

# 代码 12:保存 state 备份并记录校验值
#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="<受控备份目录>"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_FILE="${BACKUP_DIR}/terraform-${STAMP}.tfstate"

install -d -m 0700 "${BACKUP_DIR}"
umask 077
terraform state pull > "${BACKUP_FILE}"
test -s "${BACKUP_FILE}"
sha256sum "${BACKUP_FILE}" > "${BACKUP_FILE}.sha256"

plan 是配置、state 与远端对象对齐后的变更证据。生产环境的 plan 应在同一 commit、同一 workspace、同一 backend 下生成,并在短窗口内应用保存的计划;隔很久再 apply 应重新 plan。

# 代码 13:格式化、校验并生成审阅用计划
terraform fmt -check -recursive
terraform validate
terraform plan -out='<环境>.tfplan'
terraform show -no-color '<环境>.tfplan' > '<环境>.tfplan.txt'

计划文件也可能包含敏感值,不应作为公共构建产物。CI 中使用 detailed-exitcode 时,退出码 2 表示有差异而非失败,脚本需明确处理。

# 代码 14:在 CI 中正确处理 plan 退出码
#!/usr/bin/env bash
set -euo pipefail

set +e
terraform plan -detailed-exitcode -out='<环境>.tfplan'
PLAN_EXIT=$?
set -e

case "${PLAN_EXIT}" in
  0) echo '无基础设施差异' ;;
  2) echo '存在待审批差异' ;;
  *) echo "plan 失败,退出码 ${PLAN_EXIT}" >&2; exit "${PLAN_EXIT}" ;;
esac

迁移、导入与资源地址调整

将本地 state 迁移到远端之前,应先停止其他人和 CI 对同一目录的 apply,保存备份,确认 workspace 与新 key。迁移期间如果多人各自持有本地 state 会产生分叉,应由一名执行人完成并交接结果。

# 代码 15:迁移已有 state 到远端后端
terraform state pull > '<受控备份目录>/before-backend-migration.tfstate'
terraform init -migrate-state \
  -backend-config="bucket=<状态桶>" \
  -backend-config="key=<环境>/<状态对象键>" \
  -backend-config="region=<AWS区域>"
terraform state list

迁移后比较地址集合,并执行 plan。地址数量相同只能初步说明搬运完整,并不能证明资源映射正确。

# 代码 16:比较迁移前后的资源地址集合
terraform state list | sort > /tmp/state-after-addresses.txt
terraform state list -state='<受控备份目录>/before-backend-migration.tfstate' \
  | sort > /tmp/state-before-addresses.txt
diff -u /tmp/state-before-addresses.txt /tmp/state-after-addresses.txt

导入(import)会把已有云对象绑定到已有资源地址。它不会自动生成完整的 HCL,也不代表配置已对齐。导入前必须确认唯一 ID;导入后立即 plan,根据证据补全配置或修正差异。

# 代码 17:导入已确认的云资源
terraform import '<资源地址>' '<云资源唯一ID>'
terraform state show '<资源地址>'
terraform plan

模块重构优先使用 moved 块,让地址迁移随代码评审进行。它不移动云资源,只改变 Terraform 对地址的映射。

# 代码 18:在 HCL 中声明地址重构
moved {
  from = aws_security_group.app
  to   = module.network.aws_security_group.app
}

旧版本不支持 moved 时,才使用 state mv。先 dry-run、先备份,并确认远端锁已成功获得。错误地址会让后续 plan 产生危险的创建或删除。

# 代码 19:先演练再执行 state mv
terraform state mv -dry-run '<旧资源地址>' '<新资源地址>'
terraform state mv '<旧资源地址>' '<新资源地址>'
terraform plan

state rm 只是让 Terraform 忘记对象,并不删除云资源;下一次 plan 可能会尝试重新创建。只应在资源已退出 Terraform 生命周期或有明确重新导入方案时使用。

# 代码 20:仅在已批准方案中执行 state rm
terraform state rm -dry-run '<资源地址>'
terraform state rm '<资源地址>'
terraform state list | grep -F '<资源地址>' || true
terraform plan

锁、异常恢复与治理

锁等待通常意味着另一项正常作业仍在运行。首先要查操作者和 CI,不要用 lock=false 绕过并发保护。允许有限等待比频繁重试更可控。

# 代码 21:在允许等待的变更中使用 state lock
terraform plan -lock=true -lock-timeout=5m
terraform apply -lock=true -lock-timeout=5m '<环境>.tfplan'

Terraform 报出 Lock ID 时,先确认原任务已终止、目录使用同一 key、没有其它写者。force-unlock 没有 dry-run,错误解除会造成双写 state;需记录锁证据、任务 ID、确认人和回滚负责人。

# 代码 22:高风险操作,仅在确认无活动 writer 后执行
terraform force-unlock -force '<LOCK_ID>'
terraform state list
terraform plan

state 被错误覆盖时,优先从开启版本控制的对象存储中获取候选历史版本,下载至隔离目录,核对 hash、lineage、serial 与资源地址。不要因为一次 pull 失败就自动推送旧 JSON。

# 代码 23:列出状态对象历史版本并下载候选版本
aws s3api list-object-versions \
  --bucket '<状态桶>' \
  --prefix '<环境>/<状态对象键>'

aws s3api get-object \
  --bucket '<状态桶>' \
  --key '<环境>/<状态对象键>' \
  --version-id '<VersionId>' \
  '<受控备份目录>/candidate-restore.tfstate'
# 代码 24:离线审查候选 state,暂不写远端
terraform state list -state='<受控备份目录>/candidate-restore.tfstate'
terraform state show -state='<受控备份目录>/candidate-restore.tfstate' '<资源地址>'
sha256sum '<受控备份目录>/candidate-restore.tfstate'

state push 会覆盖远端 state,属于最高风险动作。只有经过双人复核、没有活跃 writer、已保存当前 state 并获得明确批准时才可能执行;推送后立即 plan 检查是否出现意外资源动作。

# 代码 25:高风险恢复,仅在书面批准后执行
terraform state push '<受控备份目录>/candidate-restore.tfstate'
terraform state list
terraform plan

跨模块共享数据时,terraform_remote_state 的消费者通常能读取完整 state,而不只是 output;若 state 含有敏感信息,会扩大访问面。应优先评估专用参数存储或服务目录,确有必要时显式声明 backend 并配置最小权限。

# 代码 26:受控读取另一份 state 的输出
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "<状态桶>"
    key    = "<环境>/network.tfstate"
    region = "<AWS区域>"
  }
}

可持续的状态管理依赖流程而不是记忆:后端 key 与环境对应、最小权限角色、版本控制和加密、计划审阅、同提交 apply、状态操作工单与定期恢复演练。做到不手改、不泄露、不并发写,并且每次高风险操作前有备份、后有 plan 验证,state 才能成为可信的基础设施记录。如果你有更多实战心得,欢迎来 云栈社区 与开发者们交流探讨。




上一篇:WorkBuddy公文写作实战:“353法”解决反复、AI味与红线难题
下一篇:垂类产品与用户自训大模型的核心差异:履约、合同与连接人的价值
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-7-30 05:19 , Processed in 0.957522 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表