Terraform 是一个非常强大的工具,强大到有时候有些可怕。想象一下,假如我们在虚拟机中安装了一个 NOSQL 数据库并存储了一些数据,结果在后续的代码修改中我们不小心错误地修改了虚拟机的可用区参数的值,这将会导致一个意外的虚拟机删除重建操作,代价则是存储的数据全部不可逆地丢失,这是一个运维所能想象到最恐怖的场景了。虽然我们可以制定相关的流程,规定必须由专人审核变更计划,通过后方可执行来避免这种意外发生,但只要是人类把关的环节,终究还是有疏失的可能性。
我们在讲解资源的章节中介绍了lifecycle
中的prevent_destroy
参数。这个参数是一个保险措施,只要它被设置为true
时,Terraform 会拒绝执行任何可能会销毁该基础设施资源的变更计划。该功能非常适合用来防止执行错误的变更计划导致关键资源被删除的问题。
例如,我们有这样一段简单的 Terraform 代码:
data "ucloud_images" "centos" {
name_regex = "^CentOS 7"
availability_zone = var.az
}
resource "ucloud_instance" "nosql" {
availability_zone = var.az
image_id = data.ucloud_images.centos.images[0].id
instance_type = "n-basic-1"
charge_type = "dynamic"
lifecycle {
prevent_destroy = true
}
}
复制代码
我们在资源的lifecycle
当中添加了prevent_destroy = true
的声明。让我们执行以下命令来创建资源:
$ terraform apply -var 'az=cn-bj2-03' -auto-approve
ucloud_instance.nosql: Creating...
ucloud_instance.nosql: Still creating... [10s elapsed]
ucloud_instance.nosql: Creation complete after 18s [id=uhost-4p4j3wdb]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
复制代码
我们在命令行中显式指定az
为cn-jb2-03
,执行后成功创建资源。接下来我们再次执行apply
,只不过这一次我们修改一下az
的值:
$ terraform apply -var 'az=cn-bj2-04' -auto-approve
ucloud_instance.nosql: Refreshing state... [id=uhost-4p4j3wdb]
Error: Instance cannot be destroyed
on main.tf line 41:
41: resource "ucloud_instance" "nosql" {
Resource ucloud_instance.nosql has lifecycle.prevent_destroy set, but the plan
calls for this resource to be destroyed. To avoid this error and continue with
the plan, either disable lifecycle.prevent_destroy or reduce the scope of the
plan using the -target flag.
复制代码
Terraform 成功地发现,az 的变化会引发被保护资源的删除操作,所以果断拒绝了执行。
但是我们又面临着另外一个问题,那就是当我们真的想要删除资源时,会由于prevent_destroy
而失败:
$ terraform destroy -force
Error: Instance cannot be destroyed
on main.tf line 41:
41: resource "ucloud_instance" "nosql" {
Resource ucloud_instance.nosql has lifecycle.prevent_destroy set, but the plan
calls for this resource to be destroyed. To avoid this error and continue with
the plan, either disable lifecycle.prevent_destroy or reduce the scope of the
plan using the -target flag.
复制代码
在自动化环境中,修改代码删除prevent_destroy
参数或是设置为false
都是非常麻烦的,幸好我们有重载文件。我们可以在项目中创建名为override.tf.ondestroy
的文件:
resource "ucloud_instance" "nosql" {
lifecycle {
prevent_destroy = false
}
}
复制代码
在文件中我们重载了资源的prevent_destroy
的值到false
,但由于平时该文件后缀名是 ondestroy,所以不会影响到资源声明上的prevent_destroy
保险;当我们确信要删除基础设施资源时,我们可以先把.ondestry
后缀名去掉,将文件重命名为override.tf
,随后执行 destory:
$ terraform destroy -force
ucloud_instance.nosql: Destroying... [id=uhost-4p4j3wdb]
ucloud_instance.nosql: Still destroying... [id=uhost-4p4j3wdb, 10s elapsed]
ucloud_instance.nosql: Destruction complete after 13s
Destroy complete! Resources: 1 destroyed.
复制代码
删除成功。
那如果我们是在模块中声明了重要资源呢?例如我们把上文描述的代码放在 github 上,然后通过模块来引用:
provider "ucloud" {
public_key = var.public_key
private_key = var.private_key
project_id = var.project_id
region = var.region
}
module "nosql" {
source = "github.com/lonegunmanb/sample_for_prevent_destroy_and_override"
az = "cn-bj2-03"
}
复制代码
在执行了terraform apply
操作后,模块代码中的prevent_destroy
就开始如预期般保护我们不会在后续的apply
中错误删除资源。该项目中也同时包含有一个override.tf.ondestroy
文件。如果我们希望执行destroy
操作,我们可以创建这样一个名为before_destroy.sh
的脚本文件:
for file in $(find . -name 'override.tf.ondestroy')
do
mv $file $(echo "$file" | sed -r 's|.tf.ondestroy|.tf|g')
done
复制代码
执行该脚本会递归检查当前目录及所有子目录下的override.tf.ondestroy
文件,将之重命名为override.tf
,然后执行terraform destroy
:
$ terraform destroy -force
module.nosql.ucloud_instance.nosql: Destroying... [id=uhost-mnklkfwa]
module.nosql.ucloud_instance.nosql: Still destroying... [id=uhost-mnklkfwa, 10s elapsed]
module.nosql.ucloud_instance.nosql: Destruction complete after 12s
Destroy complete! Resources: 1 destroyed.
复制代码
即可成功删除。
评论