尽管已经采取了精心设计的Blast Radius最小化措施、状态分段以及生命周期防护机制,但意外仍然可能发生:一次 terraform apply 意外删除了生产资源,或者一次 terraform destroy 删除的内容超出了预期范围。
那么,当事情已经无法挽回时,我们该如何应对?
在本系列的上一期文章中,我向您介绍了如何最小化Blast Radius。在这篇续篇中,我将展示一些用于恢复受损Terraform状态文件并在事故发生后减少损害的成熟技术。
1. 心脏手术式急救:State Surgery
如果资源仍然在物理上存在,但已不再在状态文件中正确引用,那就只能通过命令行进行“手术式”干预了。所谓State Surgery,是指手动清理Terraform状态并使其与现实中的基础设施状态重新对齐的操作方式。
⚠️ 重要提示:这种做法需要对Terraform内部逻辑以及实际基础设施状态有深入了解。虽然它非常强大,但如果不加谨慎地使用,也非常危险。因此,在进行任何手动修改前,请务必完整备份当前的状态文件!
您可以使用以下CLI命令备份当前状态:
$> terraform state pull > state-backup-$(date +%Y%m%d-%H%M%S).json
之后,您可以从状态文件中有选择地移除资源,而不会删除关联的云端资源。在下列示例中,受影响的资源名为aws_instance.problematic_instance:
$> terraform state rm aws_instance.problematic_instance
接下来,请在Terraform配置文件中为恢复后的资源创建一个新的资源定义(这里我们使用名称aws_instance.recovered_instance)。这可以是一个空的资源块,也可以是旧资源的完整复制粘贴,具体取决于原有资源损坏的程度。
随后,通过其唯一ID(例如i-1234567890abcdef0),将该资源以新的名称导入回来:
$> terraform import aws_instance.recovered_instance i-1234567890abcdef0
另一种方法是将状态文件中已存在的资源迁移到新的位置。这可以是基础设施的另一个部分,也可以是不同的Terraform模块:
$> terraform state mv aws_instance.misplaced_resource aws_instance.correct_scope
2. 逐步重建:Partial Import 策略
在复杂场景中 - 例如大型状态文件中的局部故障 - 单纯地执行一次导入通常无法解决问题。在这种情况下,推荐采用结构化、逐步恢复的方式,即使用Partial Import Configurations(Terraform 1.5及以上版本提供)。
执行流程
a. 使用 -detailed-exitcode 标识受影响资源
$> terraform plan -detailed-exitcode
命令 terraform plan -detailed-exitcode 是 terraform plan 的一个变体,其返回更为详细的退出码,特别适合用于自动化和CI/CD流水线。
工作原理:
- terraform plan 通常会显示 Terraform 将对基础设施做出的变更,但并不会实际执行任何操作。
- 使用 -detailed-exitcode 参数后,退出码的含义会有所改变,使得计划结果可以更细致地解读:
退出码 | 含义 |
---|---|
0 | 无任何更改;基础设施已与配置完全一致。 |
1 | 出现错误(例如语法错误、Provider错误等)。 |
2 | 检测到需要变更;计划将对基础设施进行修改。 |
提示:退出码2表示存在变更资源,因此需要特别关注。
这个功能在一些基础教程中未被提及,但在使用Terraform进行工作流自动化的团队中应用非常广泛,可实现更稳健且可预测的基础设施变更流程。
b. 定义 Import 配置
现在请为受影响的资源创建配置:
import { to = aws_vpc.recovered_vpc id = "vpc-12345678" } import { to = aws_subnet.recovered_subnet[0] id = "subnet-12345678" }
c. 使用 -generate-config-out 进行自动化导入与配置生成
命令 terraform plan -generate-config-out=<file> 是一个从 Terraform v1.5 引入的实验性功能。它允许为配置中的 import 块中定义但尚未在当前配置中存在的资源生成 Terraform 配置文件(HCL 格式)。
该功能在希望将现有基础设施导入 Terraform 时特别有用,它可根据资源的实际状态帮助您生成所需的配置代码。
$> terraform plan -generate-config-out=generated.tf
[...] $> terraform apply
Terraform 执行以下操作:
- 扫描配置中的 import 块。
- 对于每个需要导入但尚无配置的资源,Terraform 将尽可能生成包含合适参数与值的 HCL 资源块。
- 生成的配置将写入指定的文件中(本示例为 generated.tf)。您必须提供一个新的文件路径;若指定现有文件,将导致错误。
- 命令执行完成后,Terraform 会显示资源导入计划并指出生成的配置文件存储位置。
terraform plan -generate-config-out=<file> 是一个功能强大的实验工具,可根据 import 块中定义的现有资源生成 Terraform 配置。这使得将未受管理的基础设施纳入 Terraform 管理变得更为轻松。请务必在使用前仔细检查并调整生成的代码。
d. 现实校验:使用 -refresh-only 和 -refresh=true 进行状态对齐
如果 Terraform 状态与实际情况出现偏差,但并无资源缺失,可以通过与实际基础设施的对比来校准状态。这种操作称为 Refresh,它可识别漂移,在某些情况下甚至可自动修复。
# 状态刷新,但不实施变更 $> terraform apply -refresh-only # 与实际基础设施进行比较 $> terraform plan -refresh=true
命令 terraform plan -refresh=true 会生成一个执行计划,并在计划生成前强制 Terraform 先更新其状态信息(即执行 refresh 操作)。
行为细节
- 默认情况下,Terraform 在执行 terraform plan 时会自动刷新状态文件,与实际基础设施对齐,然后再判断需要哪些变更。
- 使用 -refresh=true 参数,是明确要求 Terraform 执行这一刷新步骤。虽然这通常是多余的(因为是默认行为),但能确保所有在 Terraform 之外进行的修改(如通过云控制台手动更改)能被发现并体现在计划中。
- 在刷新过程中,Terraform 会查询所有受管资源的当前状态,并用这些数据更新状态文件。随后将其与配置文件进行比较,以指出需要执行哪些操作(添加、修改、删除)来使实际基础设施与期望配置保持一致。
与 -refresh=false 的比较
选项 | 行为 |
---|---|
-refresh=true | 状态在计划前与实际基础设施同步(默认行为)。 |
-refresh=false | 跳过状态同步,可提升执行速度,但可能忽略外部变更。 |
e. 脚本中进行配置漂移检测:使用 -detailed-exitcode
命令 terraform plan -detailed-exitcode 会创建 Terraform 的执行计划,并具有特殊的退出码行为。这种技术特别适合用于CI/CD流水线,或在正式执行 terraform apply 前作为预检查。
#!/bin/bash
terraform plan -detailed-exitcode if [ $? -eq 2 ]; then echo "Configuration drift detected – manual review required" fi
退出码说明
执行 terraform plan -detailed-exitcode 后,Terraform 将:
- 显示为使基础设施匹配配置而需要执行的计划操作(添加、修改、删除),但不会进行任何实际更改。
- 根据计划结果返回特定的退出码:
退出码 | 含义 |
---|---|
0 | 无更改;基础设施与配置一致。 |
1 | 发生错误(例如语法错误、Provider 问题等)。 |
2 | 有待执行的更改(将添加、修改或删除资源)。 |
结语
正如您在本文中所见,即便一次 terraform destroy 意外失控,也并非世界末日。
相反,在 Terraform 出现之前的 IT 时代,通常后果更加严重,因为当时缺乏 Terraform 这样集成的恢复工具。那时常常意味着:“我们现在暂时下线,花上一两天手动重建服务,然后再来看看到底发生了什么。”
但事实也同样表明,即便借助 Terraform,修复损坏的运行状态(State)以及被误配置或销毁的资源仍然是一项技术复杂度高的工作,且需要专业知识。
在本系列文章的下一篇中,我们将进一步探讨 Terraform 提供的工具,如何帮助我们提前识别并避免由于 Blast Radius 过大所带来的风险。