3. Terraform 状态管理:本地状态 vs 远程状态、状态锁、状态迁移实战

好,咱们今天聊聊 Terraform 的状态管理。说实话,这玩意儿是 Terraform 的「命根子」。你想想看,Terraform 怎么知道你上次创建了哪些资源?怎么知道哪些要改、哪些要删?全靠这个状态文件。

我刚开始用 Terraform 的时候,觉得状态文件就是个记录,没啥大不了的。直到有一次,团队里两个人同时跑 terraform apply,结果资源乱成一锅粥。嗯,从那以后,我再也不敢轻视状态管理了。

3.1 本地状态:简单但危险

默认情况下,Terraform 把状态存在本地,就是一个 terraform.tfstate 文件。这文件长啥样?说白了就是个 JSON,记录了所有资源的属性。

{
  "version": 4,
  "terraform_version": "1.5.0",
  "resources": [
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "web",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "attributes": {
            "id": "i-0a1b2c3d4e5f67890",
            "ami": "ami-0c55b159cbfafe1f0",
            "instance_type": "t2.micro"
          }
        }
      ]
    }
  ]
}

本地状态的好处是简单,一个人玩完全够用。但问题也很明显:

  • 共享困难:你没法让同事直接拿到你的状态文件
  • 容易丢失:本地硬盘坏了,状态就没了
  • 并发冲突:两个人同时操作,状态就乱了
⚠️ 重要提醒:千万不要把 terraform.tfstate 提交到 Git 仓库!里面可能包含敏感信息,比如数据库密码、密钥对等。我见过有人把 AWS 密钥直接写在状态文件里,然后 push 到公开仓库...后果你懂的。

3.2 远程状态:团队协作的基石

为了解决本地状态的问题,Terraform 支持远程状态存储。说白了,就是把状态文件放到一个共享的地方,比如 S3、Azure Storage、Consul 等。

我个人最常用的是 S3 + DynamoDB 的组合。S3 存状态文件,DynamoDB 做状态锁。为什么这么配?因为 S3 便宜、可靠,DynamoDB 能保证原子操作。

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "prod/network/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

配置好之后,每次 terraform apply 都会先加锁,操作完再释放。这样就不会出现两个人同时改状态的问题了。

💡 小技巧:S3 桶一定要开启版本控制。万一状态文件被误删或损坏,你还能回滚到上一个版本。我曾经靠这个功能救回过一次被误删的整个生产环境状态。

3.3 状态锁:防止并发冲突

状态锁是远程状态的核心功能。它的作用很简单:同一时间只允许一个人操作状态。

为什么会需要锁?你想想看,两个人同时跑 terraform apply,都读取了同一个状态文件,然后各自修改,最后写回去。后写的人会覆盖先写的人的操作,导致资源状态不一致。

状态锁的实现方式取决于后端:

后端类型 锁机制 特点
S3 + DynamoDB DynamoDB 条件写入 强一致性,支持自动解锁
Azure Storage Blob 租约 内置锁,无需额外组件
Consul Session 锁 支持健康检查,自动释放
HTTP 自定义锁 需要自己实现锁逻辑
⚠️ 踩坑记录:我曾经遇到过 DynamoDB 锁超时的问题。当时有个 terraform apply 跑了很久,锁一直没释放,导致其他操作全部阻塞。后来我设置了合理的锁超时时间,并加了手动解锁的脚本。

3.4 状态迁移实战

状态迁移,说白了就是把状态文件从一个地方搬到另一个地方。比如从本地搬到远程,或者从一个 S3 桶搬到另一个 S3 桶。

迁移的步骤其实很简单:

  1. 备份当前状态:先把 terraform.tfstate 复制一份
  2. 修改 backend 配置:在代码里改 terraform { backend "..." }
  3. 执行 initterraform init -migrate-state
  4. 确认迁移:Terraform 会提示你是否要复制状态文件
# 从本地迁移到 S3
terraform init -migrate-state

# 从 S3 迁移到另一个 S3
terraform init -migrate-state -backend-config="bucket=new-bucket"

这里有个坑要注意:迁移过程中不要执行任何 applyplan 操作。我建议在迁移前先锁住状态,防止别人操作。

🔑 关键点:状态迁移是「复制」而不是「移动」。迁移完成后,旧的状态文件还在。建议确认新状态没问题后,再手动删除旧文件。

3.5 状态管理的避坑指南

做了这么多年运维,我总结了几条状态管理的经验:

  • 永远不要手动修改状态文件:除非你真的知道自己在做什么。手动改状态文件很容易导致 Terraform 和实际资源不一致。
  • 定期备份状态:远程存储虽然可靠,但也要定期备份到其他地方。我习惯每周把状态文件下载到本地加密存储。
  • 使用状态隔离:不同环境(dev、staging、prod)用不同的状态文件。这样不会互相影响。
  • 监控状态锁:如果发现锁长时间未释放,要能及时发现并处理。
💡 我的习惯:每次操作前先跑 terraform plan,看看 Terraform 要做什么。如果发现计划里要删除重要资源,赶紧停下来检查状态文件是不是有问题。

好了,状态管理这块就聊这么多。记住一句话:状态文件是 Terraform 的「记忆」,保护好它,你的基础设施才能稳定运行。下一章咱们聊聊 Terraform 模块化设计,到时候见。