1. 引言

在本教程中,我们将探讨在资源已经存在的情况下跳过其创建的方法。我们还将回顾这个问题的挑战以及为什么有时应该考虑其他替代方案。首先,我们需要理解:为什么一开始要解决这个问题可能并不是一个好主意。

2. 基础设施即代码(IaaC)工具的用途

众所周知,像 Terraform 这样的 IaaC(Infrastructure as Code)工具旨在通过代码来反映基础设施的当前状态。✅这是 IaaC 工具的核心目的:以可版本化、可打补丁、可审查的方式管理基础设施。

如果我们使用了 IaaC 工具,理想情况下应尽可能多地通过它来管理基础设施。这不仅是为了上述优点,更是为了避免出现一半基础设施由 IaaC 管理、另一半仍需手动管理的情况。这种混合状态会使基础设施管理变得复杂且低效。

当然,在向 IaaC 工具迁移的过程中,这种情况是可以接受的。例如,可以允许一部分安全组由 Terraform 管理,而另一部分仍手动维护。

3. 导入资源作为替代方案

因此,只有在迁移过程中才值得解决资源已存在时跳过创建的问题。即便如此,基于前面提到的原因,这种做法并不推荐。跳过资源创建(无论是因为名称冲突还是其他原因)意味着该资源并未由 Terraform 管理,而是由其他方式管理,甚至可能是手动管理。

我们真正想要的是:通过 Terraform 来管理某个特定名称的特定资源类型。例如,我们有一个最初手动创建的 AWS VPC,我们不希望跳过创建,而是希望让这个 VPC 被 Terraform 管理。

3.1. 手动导入资源

要实现这一点,我们可以使用 Terraform 提供的 import 功能。该功能通过 terraform import 命令实现,正如其名所示,它允许我们将现有资源导入 Terraform 的状态中,从而实现后续管理。

使用该功能需要完成两个步骤:

  1. 确定要导入的资源及其 ID(通常可通过云平台控制台获取);
  2. 手动编写该资源的配置块。

例如,我们希望导入一个 VPC 资源,其配置可能如下:

resource "aws_vpc" "main" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"

  tags = {
    Name = "main"
  }
}

接下来,我们需要将该资源导入到 Terraform 的状态文件中。假设其在 AWS 中的 ID 为 vpc-07dfb6a9d39845d81,则执行如下命令:

terraform import aws_vpc.main vpc-07dfb6a9d39845d81

执行完成后,Terraform 就会知道这个资源已经存在,并开始对其进行追踪。

3.2. 自动生成资源配置

手动编写资源配置不仅繁琐,而且容易出错。从 Terraform 1.5 开始,引入了 import 块功能,可以自动为我们生成资源配置。

使用方式如下:

我们先在配置文件中添加一个 import 块:

import {
  to = aws_vpc.main
  id = "vpc-07dfb6a9d39845d81"
}

然后执行以下命令,Terraform 会自动生成资源配置并输出到指定文件中:

terraform plan -generate-config-out=generated_vpc.tf

生成的资源配置会保存在 generated_vpc.tf 文件中。完成后我们可以将生成的配置移动到主配置文件中(如 main.tf),并删除 import 块。

4. 替代方案(Workarounds)

如果我们坚持不通过 Terraform 管理某个资源,并希望无论怎样都通过名称冲突来解决资源冲突问题,那么也有几种替代方案。但请记住:使用这些方案时,必须有充分的理由。

4.1. 使用 local-exec Provisioner

一种方式是使用 local-exec provisioner,并设置 on_failure = fail。该 provisioner 可以通过云平台 CLI(如 AWS CLI)检查资源是否存在。如果存在,则返回非 0 状态码,使 Terraform 失败:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    "env" = "dev"
  }

  provisioner "local-exec" {
    command = var.offending_vpc_id == null ? "true" : "! aws ec2 wait vpc-exists --vpc-ids ${var.offending_vpc_id} --cli-read-timeout 60"
    on_failure = fail
  }
}

variable "offending_vpc_id" {
  default = "vpc-07dfb6a9d39845d81"
  nullable = true
  type = string
}

⚠️ 缺点

  • 需要确保构建环境中安装了云平台 CLI;
  • 使用 provisioner 是一种反模式,违背了 IaaC 的初衷;
  • 仅适用于简单示例,不推荐在生产环境中使用。

4.2. 使用变量控制资源创建

另一种方式是使用布尔变量来控制资源是否创建。例如:

variable "create_new_vpc" {
  type = bool
  default = false
}

resource "aws_vpc" "main" {
  count = var.create_new_vpc ? 1 : 0

  cidr_block = "10.0.0.0/16"

  tags = {
    "env" = "dev"
  }
}

如果变量 create_new_vpcfalse,则资源不会被创建。

⚠️ 缺点

  • 需要提前知道是否会发生名称冲突;
  • 使用 count 会导致资源以数组形式访问,可能影响后续逻辑;
  • 不是真正的“跳过”,而是“有条件创建”。

5. 总结

在本篇文章中,我们讨论了如何在 Terraform 中跳过已存在资源的创建。遇到此类问题时,通常意味着我们正处于向 IaaC 工具迁移的过程中,或者我们的做法可能存在隐患。

最佳实践是:将资源导入 Terraform 管理,而不是跳过创建。这不仅能确保基础设施状态的一致性,还能避免后续维护中的潜在问题。

不推荐使用 local-execcount 控制资源创建,除非你有非常充分的理由,并清楚其带来的副作用。

总之,“跳过”不是解决资源冲突的正确方式,而是应该通过导入资源实现真正的基础设施统一管理。


原始标题:Skip Terraform Resource If It Exists