Terraform обнаруживает неожиданные изменения при изменении региона AWS провайдера - PullRequest
0 голосов
/ 17 января 2020

Встреча с проблемой, когда у меня есть нулевой ресурс, который запускает скрипт для создания части конфигурации. Весь TF должен быть запущен в каждом регионе учетной записи, поэтому я настроил все, чтобы использовать регион в качестве переменной.

Это приводит к тому, что terraform уничтожает исходную нулевую конфигурацию ресурса в области 1, прежде чем создает новую конфигурацию в области 2. Другие ресурсы terraform просто создают новые ресурсы в области 2 и оставляют ресурсы в области 1 в место. Есть ли способ заставить нулевой ресурс вести себя так же, как другие ресурсы?

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}"
  }
}

1 Ответ

2 голосов
/ 21 января 2020

Terraform внутренне отслеживает отношения между объектами, которые вы описываете в вашей конфигурации, и объектами в удаленной системе, используя «состояние» , которое представляет собой структуру данных, которая сохраняется либо на локальном диске, либо на удаленная система для сохранения данных между запусками Terraform.

После первого запуска Terraform Terraform сохранит снимок состояния, который включает два основных типа информации:

  • Копия значения, которые вы устанавливаете в конфигурации, например, значение triggers в ваших null_resource.
  • идентификаторах и другие данные, которые были выбраны удаленной системой, например, сгенерированный сервером id из aws_instance или ha sh Документа SSM, созданного вашим ресурсом aws_ssm_document.

Используя эту информацию, будущие запуски Terraform будут выполнять следующие дополнительные действия, которые не выполняются на первый запуск:

  • Для любого типа ресурса, который соответствует данным в удаленной системе, например aws_ssm_document, Terraform ( точнее: провайдер AWS) будет искать объект, используя API удаленной системы, чтобы убедиться, что он все еще существует, и, если это так, обновить данные состояния, чтобы они соответствовали текущим значениям в удаленном объекте.
  • После этого Terraform будет сравнивать то, что вы написали в конфигурации, с тем, что хранится в состоянии, чтобы определить, нужно ли предпринимать какие-либо действия по обновлению или замене для того, чтобы состояние и удаленные объекты соответствовали конфигурации.

Ключевое различие между типом ресурса, таким как aws_ssm_document, и типом, подобным null_resource, заключается в том, что объект документа SSM в состоянии - это просто прокси, представляющий «реальный» объект в AWS SSM API в то время как null_resource существует только в состоянии Terraform и не имеет соответствующего вышестоящего объекта. Поэтому Terraform может заметить, что созданный им документ SSM больше не существует, и действовать в соответствии с этим предположением (планирует создать новый), но он не может сделать такое автоматическое c определение для null_resource экземпляра.

* 1032 В частности, для AWS, когда вы изменяете аргумент region в конфигурации провайдера, вы фактически предоставляете провайдеру AWS доступ к совершенно другому набору конечных точек. Например, если вы изначально создали документ SSM в us-east-1, то в этом состоянии будет записана информация об этом объекте. Если затем вы измените region на us-west-1, Terraform спросит us-west-1 конечных точек API , существует ли этот объект, и будет сказано, что его нет, потому что AWS SSM использует регион-специфицированный c namespaces.

Если вы примете план создания нового документа SSM в us-west-1, тогда Terraform полностью потеряет отслеживание исходного объекта в us-east-1, потому что, насколько это касается Terraform, там это только один удаленный объект на экземпляр ресурса, и вы только что сказали Terraform искать и управлять этим объектом в другом AWS регионе, а не управлять новым.


За исключением всего этого фонового контекста, ключевая проблема здесь заключается в том, что вы не можете просто изменить аргумент region в поставщике AWS для дублирования объектов в новом регионе, потому что Terraform не отслеживает отдельные объекты на регион. Вместо этого вам нужно организовать отслеживание объектов в каждом регионе как отдельных объектов в Terraform.

Есть несколько способов выполнить sh, в зависимости от того, как это вписывается в вашу общую систему. Один из способов - написать несколько различных AWS конфигураций провайдера и написать отдельный набор resource блоков для каждого из них:

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
}

Если ваша цель - иметь одно и то же созвездие объектов в каждом регионе, вы можете избежать дублирования, выделив блоки resource в отдельный многократно используемый модуль , а затем вызвать этот модуль один раз для каждого региона с другим экземпляром поставщика 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
  }
}

При использовании этого шаблона выше все блоки resource в модуле ./modules/per-region не должны иметь явных аргументов provider и поэтому будут связаны с конфигурацией поставщика aws по умолчанию этого модуля. Блоки module, показанные выше, гарантируют, что каждый экземпляр модуля наследует свою конфигурацию провайдера aws.

Ключевой момент, который необходимо знать в обоих вышеупомянутых подходах, заключается в том, что они приведут к одной Terraform. состояние, содержащее отдельный набор объектов на регион. Если вы выбрали первый подход - просто объявите дубликаты ресурсов встроенными, тогда эти объекты будут иметь такие адреса:

  • aws_ssm_document.us-east-1
  • aws_ssm_document.us-west-1
  • null_resource.us-east-1
  • null_resource.us-west-1

Если вы используете подход с общим модулем, представляющим всю общую инфраструктуру для региона, то вместо этого они будут определены следующим образом:

  • module.us-east-1.aws_ssm_document.example
  • module.us-east-1.null_resource.example
  • module.us-west-1.aws_ssm_document.example
  • module.us-west-1.null_resource.example

В любом случае, Terraform будет отслеживать все объекты по отдельным адресам, отслеживая отдельные данные в состоянии для каждого. Когда вы повторно запустите Terraform после первоначального создания, он прочитает данные об обоих документах SSM из API AWS и проверит оба объекта null_resource по отдельности, чтобы увидеть, изменились ли их triggers.

Такая конфигурация, как две выше, имеет важное значение: каждый раз, когда вы запускаете Terraform, вы читаете данные и, возможно, применяете изменения ко всем объектам во всех регионах. Если в каком-то конкретном регионе в настоящий момент происходит сбой, вам может быть запрещено применять изменения в другом регионе. Если вы используете регионы как часть стратегии изоляции рабочей нагрузки, может быть нежелательно, чтобы любое изменение, внесенное в этот общий модуль, всегда применялось сразу ко всем регионам.

По этой причине существует другая схема что вы можете использовать, что добавляет дополнительную сложность рабочего процесса, но гарантирует, что вы работаете с каждым регионом отдельно: напишите отдельную конфигурацию для каждого региона, каждый из которых включает в себя только блок provider и один блок module, вызывающий общий модуль для этого область. Например, вот конфигурация для us-east-1:

provider "aws" {
  region = "us-east-1"
}

module "per_region" {
  source = "./modules/per-region"

  # This time we're just using the default provider
  # configuration throughout, so we don't need any
  # special provider configuration overrides.
}

В этой модели вы будете работать отдельно с каждым регионом. Например:

  • cd us-east-1
  • terraform apply
  • cd ../us-west-1
  • terraform apply

Внедрение одного и того же изменения в нескольких регионах теперь включает больше шагов, хотя вы можете выбрать автоматизацию этого для общего случая, когда вы do хотите применить их все каждый раз. Разделение их - это компромисс гибкости рабочего процесса: теперь вы можете выбирать работу только с одним регионом за раз, когда это необходимо, либо потому, что вы хотите выполнить постепенное развертывание рискованного изменения, либо потому, что (как отмечено выше) один регион в настоящее время имеет отключение, и вам необходимо внести изменения в другие регионы, чтобы компенсировать это.

Важным моментом в этом многоконфигурационном подходе является то, что каждая конфигурация теперь будет иметь свое собственное состояние . Это другой способ прийти в ситуации, когда объекты для каждого региона отслеживаются как отдельные объекты в Terraform: вместо того, чтобы помещать их в пространство имен внутри single Terraform, мы можем вместо этого отслеживать их в отдельных снимках состояния в целом достижения того же результата, когда они различаются, но на более детальном уровне детализации.


Ответа "magi c bullet" не существует, поэтому вам придется рассмотреть варианты самостоятельно и решить, какой подход имеет наибольшее значение для вашей конкретной проблемы. За исключением деталей, главное, что здесь следует иметь в виду, это то, что повторное выполнение той же конфигурации с измененным AWS регионом поставщика не является правильным шаблоном использования, за исключением некоторых весьма специфических c необычных случаев. Вы всегда захотите убедиться, что Terraform отслеживает все ваши объекты отдельно в состоянии, даже если объекты дублируются между регионами.

Этот же подход применяется ко всему другому в конфигурации провайдера, которая контролирует, какой набор Конечные точки API, которые использует Terraform. Для AWS использование учетных данных, относящихся к другой учетной записи AWS, часто приводит к одной и той же ситуации, поскольку многие объекты в AWS имеют пространство имен для каждой учетной записи или для каждой области. Обычно Terraform не может определить разницу между удаленным объектом в удаленной системе и тем, что он теперь настроен для запроса другой конечной точки, где объект не существует.

...