Terraform在更改AWS提供程序区域时检测到意外更改

Terraform在更改AWS提供程序区域时检测到意外更改,terraform,Terraform,遇到了一个问题,我有一个空资源,它运行一个脚本来创建一个配置。整个TF需要在帐户的每个区域中运行,所以我已经将所有内容设置为使用区域作为变量 这导致terraform在区域2中创建新配置之前,先破坏区域1中的原始空资源配置。其他地形资源只是在区域2创建新资源,并保留区域1中的资源。有没有办法让null资源的行为与其他资源相同 resource "null_resource" "config-s3-remediation" { triggers = { account_name = v

遇到了一个问题,我有一个空资源,它运行一个脚本来创建一个配置。整个TF需要在帐户的每个区域中运行,所以我已经将所有内容设置为使用区域作为变量

这导致terraform在区域2中创建新配置之前,先破坏区域1中的原始空资源配置。其他地形资源只是在区域2创建新资源,并保留区域1中的资源。有没有办法让null资源的行为与其他资源相同

resource "null_resource" "config-s3-remediation" {
  triggers = {
    account_name = var.account_name
    region = var.region
  }
  depends_on = [
    aws_config_config_rule.s3_access_logging_rule,
    aws_ssm_document.s3_access_logging_ssm
  ]

  provisioner "local-exec" {
    command = "python3 ${path.module}/remediation_config.py add ${self.triggers.region}
  }

  provisioner "local-exec" {
    when    = destroy
    command = "python3 ${path.module}/remediation_config.py remove ${self.triggers.region}"
  }
}

Terraform内部跟踪您在配置中描述的对象与远程系统中使用its的对象之间的关系,its是一种数据结构,保存到本地磁盘或远程系统,以在Terraform运行之间保存数据

首次运行Terraform后,Terraform将保存一个状态快照,其中包括两种关键类型的信息:

在配置中设置的值的副本,如null_资源中的触发器值。 远程系统选择的标识符和其他数据,例如服务器生成的aws_实例id,或aws_SSM_文档资源创建的SSM文档的哈希。 使用该信息,Terraform的未来运行将执行第一次运行时未发生的以下附加操作:

对于与远程系统中的数据相对应的任何资源类型,如aws_ssm_文档,Terraform更准确:aws提供程序将使用远程系统API查找对象,以确保其仍然存在,如果存在,则更新状态数据以匹配远程对象中的当前值。 之后,Terraform将比较您在配置中写入的内容与状态中存储的内容,以确定是否需要采取任何更新或替换操作,以使状态和远程对象与配置匹配。 像aws_ssm_document这样的资源类型与像null_资源这样的资源类型之间的一个关键区别是,状态中的ssm文档对象只是代表aws ssm API中真实对象的代理,而null_资源只存在于Terraform状态中,没有对应的上游对象。因此,Terraform可以注意到它创建的SSM文档不再存在,并在该假设下继续计划创建一个新的SSM文档,但它不能自动确定空的_资源实例

特别是对于AWS,当您更改提供程序配置上的region参数时,实际上是告诉AWS提供程序访问一组完全不同的端点。例如,如果您最初在us-east-1中创建了SSM文档,则州政府将记录该对象的详细信息。如果随后将region更改为us-west-1,Terraform将询问us-west-1 API端点该对象是否存在,并将被告知不存在,因为AWS SSM使用特定于区域的名称空间

如果您接受在us-west-1中创建新SSM文档的计划,那么Terraform将完全失去us-east-1中原始对象的跟踪,因为就Terraform而言,每个资源实例只有一个远程对象,您刚刚告诉Terraform在不同的AWS区域中查找和管理该对象,而不是另外管理一个新的

抛开所有这些背景上下文不谈,这里的关键问题是,您不能仅仅更改AWS提供程序中的region参数,以在新区域中复制对象,因为Terraform不会跟踪每个区域中的单独对象。相反,您需要将每个区域中的对象作为地形中的单独对象进行跟踪

有几种方法可以实现这一点,这取决于它如何适合您的整个系统。一种方法是为每个资源块写入和写入一组单独的资源块:

provider "aws" {
  alias = "us-east-1"

  region = "us-east-1"
}

provider "aws" {
  alias = "us-west-1"

  region = "us-west-1"
}

resource "aws_ssm_document" "us-east-1" {
  # Attach the resource to the non-default (aliased) provider configuration
  provider = aws.us-east-1

  # other settings for the document in the us-east-1 region
}

resource "aws_ssm_document" "us-west-1" {
  # Attach the resource to the non-default (aliased) provider configuration
  provider = aws.us-west-1

  # other settings for the document in the us-west-1 region
}
如果您的目标是在每个区域中拥有相同的对象群,则可以通过将资源块分解为单独的可重用模块来避免重复,然后为每个区域调用该模块一次,并将AWS提供程序的不同实例传递给每个区域:

provider "aws" {
  alias = "us-east-1"

  region = "us-east-1"
}

provider "aws" {
  alias = "us-west-1"

  region = "us-west-1"
}

module "us-east-1" {
  source = "./modules/per-region"

  providers = {
    # The default (unaliased) "aws" provider configuration
    # in this instance of the module will be the us-east-1
    # configuration declared above.
    aws = aws.us-east-1
  }
}

module "us-west-1" {
  source = "./modules/per-region"

  providers = {
    # The default (unaliased) "aws" provider configuration
    # in this instance of the module will be the us-west-1
    # configuration declared above.
    aws = aws.us-west-1
  }
}
使用上述模式时,./modules/per region模块中的所有资源块都不应具有显式的提供程序参数,因此将与该模块的默认aws提供程序配置相关联。然后,上面显示的模块块确保模块的每个实例继承不同的aws提供程序配置

了解上述两种方法的关键是,它们将导致一个单一的地形状态,每个区域包含一组单独的对象。如果采用第一种方法,即仅内联声明重复资源,则这些对象的地址如下:

aws_ssm_文件.us-east-1 aws_ssm_文件us-west-1 无原因 urce.us-east-1 null_resource.us-west-1 如果您使用共享模块的方法来表示一个区域的所有公共基础设施,那么它们将被标识为:

模块.us-east-1.aws_ssm_document.example module.us-east-1.null_resource.example 模块.us-west-1.aws\U ssm\U文件.示例 module.us-west-1.null_resource.example 无论如何,Terraform将在不同的地址跟踪所有对象,跟踪每个对象状态下的不同数据。当您在初始创建后重新运行Terraform时,它将从AWS API读取关于两个SSM文档的数据,并将分别检查两个null_资源对象,以查看它们的触发器是否已更改

不过,像上面两种配置一样的配置确实有一个重要的含义:无论何时运行Terraform,您都在读取关于所有区域的数据,并可能对所有对象应用更改。如果某个特定区域当前正在发生中断,则可能会阻止您将更改应用到另一个区域。如果您使用区域作为for workload隔离策略的一部分,那么对共享模块所做的任何更改都必须同时应用于所有区域,这可能是不可取的

出于这个原因,您可以使用另一种模式,它增加了一些额外的工作流复杂性,但可以确保您单独处理每个区域:为每个区域编写一个单独的配置,每个配置仅包括一个提供程序块和一个调用该区域共享模块的模块块。例如,以下是仅针对us-east-1的配置:

在此模型下,您将分别处理每个区域。例如:

cd us-east-1 地形应用 cd../us-west-1 地形应用 现在,跨多个区域推出相同的更改有更多的步骤,尽管您可以选择将这些步骤自动化,以适用于您确实希望每次都应用它们的常见情况。但是,将它们分开是一种工作流灵活性折衷:您现在可以选择在需要时一次只使用一个区域,要么因为您希望逐步推出一个有风险的更改,要么因为如上所述,一个区域当前正在中断,您需要对其他区域进行更改以进行补偿

关于这种多配置方法的一个重要细节是,每个配置现在也将有自己的状态。在每个区域的对象作为地形中的单独对象进行跟踪的情况下,这是一种不同的方式:我们可以在单独的状态快照中完全跟踪它们,而不是在单个地形状态中命名它们,实现了相同的结果,即保持它们的独特性,但粒度更高

没有神奇的子弹答案,所以你需要自己考虑这些选项,并决定哪种方法对你的特定问题最有意义。撇开细节不谈,这里要记住的主要一点是,除了在一些非常特殊的情况下,使用更改的AWS提供程序区域重新运行相同的配置不是正确的使用模式。您将始终希望确保Terraform在该状态下单独跟踪所有对象,即使对象在区域之间重复


同样的方法也适用于提供者配置中控制Terraform正在使用哪一组API端点的任何其他内容。对于AWS,使用引用不同AWS帐户的凭据通常会导致相同的情况,因为AWS中的许多对象是按帐户或按区域按帐户命名的。一般来说,Terraform无法区分远程系统中删除的对象与现在配置为询问不存在对象的不同端点的对象之间的区别。

Terraform使用其,这是一种保存到本地磁盘或远程系统的数据结构,用于在Terraform运行之间保存数据

首次运行Terraform后,Terraform将保存一个状态快照,其中包括两种关键类型的信息:

在配置中设置的值的副本,如null_资源中的触发器值。 远程系统选择的标识符和其他数据,例如服务器生成的aws_实例id,或aws_SSM_文档资源创建的SSM文档的哈希。 使用该信息,Terraform的未来运行将执行第一次运行时未发生的以下附加操作:

对于与远程系统中的数据相对应的任何资源类型,如aws_ssm_文档,Terraform更准确:aws提供程序将使用远程系统API查找对象,以确保其仍然存在,如果存在,则更新状态数据以匹配远程对象中的当前值。 之后,Terraform将比较您在configura中编写的内容 设置状态中存储的内容,以确定是否需要执行任何更新或替换操作,以使状态和远程对象与配置匹配。 像aws_ssm_document这样的资源类型与像null_资源这样的资源类型之间的一个关键区别是,状态中的ssm文档对象只是代表aws ssm API中真实对象的代理,而null_资源只存在于Terraform状态中,没有对应的上游对象。因此,Terraform可以注意到它创建的SSM文档不再存在,并在该假设下继续计划创建一个新的SSM文档,但它不能自动确定空的_资源实例

特别是对于AWS,当您更改提供程序配置上的region参数时,实际上是告诉AWS提供程序访问一组完全不同的端点。例如,如果您最初在us-east-1中创建了SSM文档,则州政府将记录该对象的详细信息。如果随后将region更改为us-west-1,Terraform将询问us-west-1 API端点该对象是否存在,并将被告知不存在,因为AWS SSM使用特定于区域的名称空间

如果您接受在us-west-1中创建新SSM文档的计划,那么Terraform将完全失去us-east-1中原始对象的跟踪,因为就Terraform而言,每个资源实例只有一个远程对象,您刚刚告诉Terraform在不同的AWS区域中查找和管理该对象,而不是另外管理一个新的

抛开所有这些背景上下文不谈,这里的关键问题是,您不能仅仅更改AWS提供程序中的region参数,以在新区域中复制对象,因为Terraform不会跟踪每个区域中的单独对象。相反,您需要将每个区域中的对象作为地形中的单独对象进行跟踪

有几种方法可以实现这一点,这取决于它如何适合您的整个系统。一种方法是为每个资源块写入和写入一组单独的资源块:

provider "aws" {
  alias = "us-east-1"

  region = "us-east-1"
}

provider "aws" {
  alias = "us-west-1"

  region = "us-west-1"
}

resource "aws_ssm_document" "us-east-1" {
  # Attach the resource to the non-default (aliased) provider configuration
  provider = aws.us-east-1

  # other settings for the document in the us-east-1 region
}

resource "aws_ssm_document" "us-west-1" {
  # Attach the resource to the non-default (aliased) provider configuration
  provider = aws.us-west-1

  # other settings for the document in the us-west-1 region
}
如果您的目标是在每个区域中拥有相同的对象群,则可以通过将资源块分解为单独的可重用模块来避免重复,然后为每个区域调用该模块一次,并将AWS提供程序的不同实例传递给每个区域:

provider "aws" {
  alias = "us-east-1"

  region = "us-east-1"
}

provider "aws" {
  alias = "us-west-1"

  region = "us-west-1"
}

module "us-east-1" {
  source = "./modules/per-region"

  providers = {
    # The default (unaliased) "aws" provider configuration
    # in this instance of the module will be the us-east-1
    # configuration declared above.
    aws = aws.us-east-1
  }
}

module "us-west-1" {
  source = "./modules/per-region"

  providers = {
    # The default (unaliased) "aws" provider configuration
    # in this instance of the module will be the us-west-1
    # configuration declared above.
    aws = aws.us-west-1
  }
}
使用上述模式时,./modules/per region模块中的所有资源块都不应具有显式的提供程序参数,因此将与该模块的默认aws提供程序配置相关联。然后,上面显示的模块块确保模块的每个实例继承不同的aws提供程序配置

了解上述两种方法的关键是,它们将导致一个单一的地形状态,每个区域包含一组单独的对象。如果采用第一种方法,即仅内联声明重复资源,则这些对象的地址如下:

aws_ssm_文件.us-east-1 aws_ssm_文件us-west-1 null_resource.us-east-1 null_resource.us-west-1 如果您使用共享模块的方法来表示一个区域的所有公共基础设施,那么它们将被标识为:

模块.us-east-1.aws_ssm_document.example module.us-east-1.null_resource.example 模块.us-west-1.aws\U ssm\U文件.示例 module.us-west-1.null_resource.example 无论如何,Terraform将在不同的地址跟踪所有对象,跟踪每个对象状态下的不同数据。当您在初始创建后重新运行Terraform时,它将从AWS API读取关于两个SSM文档的数据,并将分别检查两个null_资源对象,以查看它们的触发器是否已更改

不过,像上面两种配置一样的配置确实有一个重要的含义:无论何时运行Terraform,您都在读取关于所有区域的数据,并可能对所有对象应用更改。如果某个特定区域当前正在发生中断,则可能会阻止您将更改应用到另一个区域。如果您使用区域作为for workload隔离策略的一部分,那么对共享模块所做的任何更改都必须同时应用于所有区域,这可能是不可取的

出于这个原因,您可以使用另一种模式,它增加了一些额外的工作流复杂性,但可以确保您单独处理每个区域:为每个区域编写一个单独的配置,每个配置仅包括一个提供程序块和一个调用该区域共享模块的模块块。例如,以下是仅针对us-east-1的配置:

在此模型下,您将分别处理每个区域。例如:

cd us-east-1 地形应用 cd../us-west-1 地形应用 现在,跨多个区域推出相同的更改需要更多步骤,尽管您可以选择自动化这些步骤 t适用于您确实希望每次都应用它们的常见情况。但是,将它们分开是一种工作流灵活性折衷:您现在可以选择在需要时一次只使用一个区域,要么因为您希望逐步推出一个有风险的更改,要么因为如上所述,一个区域当前正在中断,您需要对其他区域进行更改以进行补偿

关于这种多配置方法的一个重要细节是,每个配置现在也将有自己的状态。在每个区域的对象作为地形中的单独对象进行跟踪的情况下,这是一种不同的方式:我们可以在单独的状态快照中完全跟踪它们,而不是在单个地形状态中命名它们,实现了相同的结果,即保持它们的独特性,但粒度更高

没有神奇的子弹答案,所以你需要自己考虑这些选项,并决定哪种方法对你的特定问题最有意义。撇开细节不谈,这里要记住的主要一点是,除了在一些非常特殊的情况下,使用更改的AWS提供程序区域重新运行相同的配置不是正确的使用模式。您将始终希望确保Terraform在该状态下单独跟踪所有对象,即使对象在区域之间重复


同样的方法也适用于提供者配置中控制Terraform正在使用哪一组API端点的任何其他内容。对于AWS,使用引用不同AWS帐户的凭据通常会导致相同的情况,因为AWS中的许多对象是按帐户或按区域按帐户命名的。Terraform通常无法区分在远程系统中删除的对象与现在配置为询问不存在对象的不同端点之间的区别。

切换变量区域时是否使用不同的状态?你能解释一下你现在是如何运行计划和应用它的吗?你在切换var.region时使用的是不同的状态吗?你能解释一下你是如何运行这个计划的吗?这是一个非常彻底的答案。非常感谢,这正是我想要的信息。这是一个非常彻底的答案。非常感谢,这正是我想要的信息。