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

Что значит импортировать существующий ресурс в ваше состояние Terraform? Существующий ресурс — это просто ресурс, созданный с помощью средств, отличных от вашей текущей конфигурации Terraform, в которую вы хотите импортировать ресурс. Ресурс мог быть создан с помощью другого инструмента «инфраструктура как код», он мог быть создан с помощью «укажи и щелкни» в графическом интерфейсе или даже с использованием другой конфигурации Terraform. Независимо от того, как он был создан, вам может быть интересно, чтобы новая конфигурация Terraform, которую вы пишете, управляла ресурсом вместо вас.

Здесь на сцену выходит операция импорта. Проще говоря, когда вы импортируете ресурс, вы создаете место для ресурса в файле состояния и заполняете данные, соответствующие импортированному ресурсу. Также необходимо написать соответствующий HCL для работы с ресурсом. Конечным результатом является то, что Terraform создал ресурс для начала. Теперь вы можете делать все, что Terraform может делать с вашим импортированным ресурсом.

В Terraform версии 1.5 появился новый способ импорта ресурсов. Спойлер: им довольно легко пользоваться, и он хорошо работает! До Terraform версии 1.5 вам приходилось использовать Terraform CLI для импорта ресурса в ваше состояние. Это также хорошо работает, но в конечном итоге это императивная операция, и ее может быть трудно выполнить. В новом интерфейсе используется новый блок импорта, который делает процесс импорта декларативным по своей природе.

В этой статье я сравню традиционный способ импорта ресурсов в ваше состояние Terraform с новым и улучшенным способом использования блока импорта!

Что мы будем импортировать

Как всегда, легче что-то продемонстрировать, если сложность примера остается низкой, поэтому я и здесь буду к этому стремиться. Я буду работать с поставщиком Azure для Terraform и создам два примера ресурсов:

  • Группа ресурсов
  • Учетная запись хранения

Если вы новичок в Azure и Terraform, вам нужно будет выполнить множество настроек вашей среды, подробности которых я не буду здесь приводить.

Вы можете использовать любой метод для настройки двух ресурсов, которые будут импортированы, я буду использовать Azure CLI. Сначала я создам группу ресурсов:

$ az group create \
    --name rg-terraform-import \
    --location swedencentral

Эта команда возвращает следующий вывод:

{
  "id": "/subscriptions/<sub id>/resourceGroups/rg-terraform-import",
  "location": "swedencentral",
  "name": "rg-terraform-import",
  ...
  "type": "Microsoft.Resources/resourceGroups"
}

Вывод немного урезан, но я сохранил важные части. id особенно важен, потому что он понадобится нам, чтобы определить, какой ресурс импортировать. Далее я создам учетную запись хранения в своей группе ресурсов:

$ az storage account create \
    --name sttfimport \
    --resource-group rg-terraform-import \
    --location swedencentral \
    --sku Standard_LRS

Эта команда возвращает следующий вывод:

{
  "id": "/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport",
  "kind": "StorageV2",
  "location": "swedencentral",
  "name": "sttfimport",
  "resourceGroup": "rg-terraform-import",
  "sku": {
    "name": "Standard_LRS",
    "tier": "Standard"
  },
  ...
  "type": "Microsoft.Storage/storageAccounts"
}

Я еще раз сократил вывод, но сохранил важные детали. Опять же, id необходим во время операции импорта.

Шаги до терраформирования

Теперь мы переходим от Azure CLI к Terraform. Я определю свою конфигурацию Terraform в одном файле с именем main.tf. Общим для всех моих примеров в этом посте является то, что мне нужно указать, что я хочу использовать поставщика Azure (с именем azurerm):

// main.tf

terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
    }
  }
}

provider "azurerm" {
  features {}
}

Я запускаю terraform init для инициализации конфигурации, а затем убеждаюсь, что мое состояние пусто, запуская terraform state list:

$ terraform state list

Выходные данные не возвращаются, как и ожидалось.

Импорт ресурсов с помощью CLI

Теперь мы готовы начать импорт ресурсов. Сначала воспользуемся традиционным методом с использованием CLI.

У вас должна быть цель в вашей конфигурации Terraform при импорте ресурса. Цель — это блок resource, соответствующий ресурсу, который вы хотите импортировать. Для этого я добавляю два блока resource, один для моей группы ресурсов и один для моей учетной записи хранения:

// main.tf

// ...

resource "azurerm_resource_group" "rg" {
  name     = "rg-terraform-import"
  location = "swedencentral"
}

resource "azurerm_storage_account" "st" {
  name                     = "sttfimport"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

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

Чтобы импортировать ресурсы с помощью CLI, я буду использовать команду terraform import. Общий формат команды:

$ terraform import [options] ADDR ID

Две важные части этой команды следующие:

  • ADDR — это адрес в вашей конфигурации Terraform, где находится HCL для этого ресурса. Это цель, о которой я говорил ранее. У каждого ресурса есть адрес в вашей конфигурации Terraform. Для ресурсов в вашем корневом модуле адрес просто resource_type.symbolic_name. Возьмем, к примеру, мою учетную запись хранения, адрес azurerm_storage_account.st.
  • ID – это идентификатор ресурса, который вы хотите импортировать. Этот идентификатор соответствует идентификатору в «реальном мире». Вот как Terraform узнает, на какой ресурс вы хотите нацелиться. Вот почему поле id в выводе команд Azure CLI было важным. Это id соответствует ID в команде terraform import

Имея эти знания в нашем рюкзаке, мы можем выполнить импорт. Я начинаю с моей группы ресурсов:

$ terraform import azurerm_resource_group.rg /subscriptions/<sub id>/resourceGroups/rg-terraform-import 

Получается следующий вывод:

azurerm_resource_group.rg: Importing from ID "/subscriptions/<sub id>/resourceGroups/rg-terraform-import"...
azurerm_resource_group.rg: Import prepared!
  Prepared azurerm_resource_group for import
azurerm_resource_group.rg: Refreshing state... [id=/subscriptions/<sub id>/resourceGroups/rg-terraform-import]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

Вывод показывает, что импорт выполнен успешно! Я продолжаю работу с моей учетной записью хранения:

$ terraform import azurerm_storage_account.st /subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport

На этот раз производится следующий вывод:

azurerm_storage_account.st: Importing from ID "/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport"...
azurerm_storage_account.st: Import prepared!
  Prepared azurerm_storage_account for import
azurerm_storage_account.st: Refreshing state... [id=/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

И снова импорт прошел успешно! Давайте посмотрим, что содержит наше состояние, используя terraform state list, как раньше:

azurerm_resource_group.rg
azurerm_storage_account.st

Поскольку у нас есть ресурсы в нашем состоянии, что произойдет, если мы запустим terraform plan? Возвращается следующий вывод:

azurerm_resource_group.rg: Refreshing state... [id=/subscriptions/<sub id>/resourceGroups/rg-terraform-import]
azurerm_storage_account.st: Refreshing state... [id=/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport]

Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # azurerm_storage_account.st will be updated in-place
  ~ resource "azurerm_storage_account" "st" {
      + cross_tenant_replication_enabled  = true
        id                                = "/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport"
      ~ min_tls_version                   = "TLS1_0" -> "TLS1_2"
        name                              = "sttfimport"
        tags                              = {}
        # (35 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Прежде всего, в плане написано 0 to add. Это хорошо! Это означает, что Terraform знает о группе ресурсов и учетной записи хранения, поэтому не будет пытаться их создать. Однако мы также видим 1 to change. Это обычное явление при импорте ресурсов. Это связано со значениями по умолчанию, используемыми Azure CLI, и значениями по умолчанию, используемыми поставщиком Azure для Terraform. Между ними есть разница, поэтому Terraform исправит эту разницу — отсюда и 1 to change для учетной записи хранения. В общем, принимать изменения безопасно, но рекомендуется просмотреть, какие изменения есть, просто чтобы быть уверенным.

Вы могли заметить, что я не использовал флаг -dry-run или что-то подобное, когда импортировал свои ресурсы. Это потому, что он не существует для этой команды. Он либо работает, либо нет. Не забудьте сделать резервную копию файла состояния перед импортом ресурсов! Забавно, как я дождался конца этого раздела, прежде чем добавить это очень важное предупреждение. Пожалуйста!

Импорт ресурсов с помощью блока ресурсов

Теперь давайте посмотрим на новый опыт импорта с использованием блока import.

Как и в предыдущем примере, мне нужно иметь цель для моего ресурса, поэтому я еще раз добавляю группу ресурсов и учетную запись хранения в main.tf:

// main.tf

// ...

resource "azurerm_resource_group" "rg" {
  name     = "rg-terraform-import"
  location = "swedencentral"
}

resource "azurerm_storage_account" "st" {
  name                     = "sttfimport"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

Следующим шагом будет добавление двух блоков import:

// main.tf

// ...

import {
  id = "/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport"
  to = azurerm_storage_account.st
}

import {
  id = "/subscriptions/<sub id>/resourceGroups/rg-terraform-import"
  to = azurerm_resource_group.rg
}

Мы видим сходство с командой terraform import CLI. Я предоставляю id для идентификации ресурса в Azure и свойство to для указания целевого ресурса в моей конфигурации Terraform. По сути, мы переместили императивную команду terraform import в декларативный код в блоках import.

Теперь мы следуем обычному рабочему процессу Terraform и запускаем terraform plan:

azurerm_resource_group.rg: Preparing import... [id=/subscriptions/<sub id>/resourceGroups/rg-terraform-import]
azurerm_resource_group.rg: Refreshing state... [id=/subscriptions/<sub id>/resourceGroups/rg-terraform-import]
azurerm_storage_account.st: Preparing import... [id=/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport]
azurerm_storage_account.st: Refreshing state... [id=/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport]

Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # azurerm_resource_group.rg will be imported
    resource "azurerm_resource_group" "rg" {
        id       = "/subscriptions/<sub id>/resourceGroups/rg-terraform-import"
        location = "swedencentral"
        name     = "rg-terraform-import"
        tags     = {}
    }

  # azurerm_storage_account.st will be updated in-place
  # (imported from "/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport")
  ~ resource "azurerm_storage_account" "st" {
        access_tier                       = "Hot"
        account_kind                      = "StorageV2"
        account_replication_type          = "LRS"
        account_tier                      = "Standard"
        (... output truncated ...)
    }

Plan: 2 to import, 0 to add, 1 to change, 0 to destroy.

Я удалил часть вывода, потому что его было много! Важно то, что мы видим 2 to import в сводке внизу. Теперь мы безопасно подтвердили, что импорт должен работать без запуска самого импорта. Если мы удовлетворены планом, мы можем продолжить и применить изменения, используя terraform apply:

(... output truncated ...)

Apply complete! Resources: 2 imported, 0 added, 1 changed, 0 destroyed.

Я могу убедиться, что мои ресурсы импортированы, взглянув на свое текущее состояние с помощью terraform state list:

azurerm_resource_group.rg
azurerm_storage_account.st

Итак, теперь мы импортировали наши ресурсы. Что нам делать с import блоками, которые мы добавили? Что произойдет, если мы снова запустим terraform apply? При желании безопасно оставить блоки в нашем коде. Они могут служить документацией, показывающей, что эти ресурсы были импортированы. Вы можете безопасно удалить блоки импорта, если вам не нужна такая документация. С импортированными ресурсами ничего не случится.

Создание конфигурации Terraform для импортированных ресурсов

Есть еще одна функция (фактически экспериментальная), которую вы можете использовать с новым блоком import. Есть новый флаг для команды terraform plan, который позволяет нам позволить Terraform генерировать блоки resource для импортируемых ресурсов. Чтобы попробовать это, я удаляю свои блоки resource из main.tf, но оставляю блоки import как есть:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "3.64"
    }
  }
}

provider "azurerm" {
  features {}
}

import {
  id = "/subscriptions/<sub id>/resourceGroups/rg-terraform-import/providers/Microsoft.Storage/storageAccounts/sttfimport"
  to = azurerm_storage_account.st
}

import {
  id = "/subscriptions/<sub id>/resourceGroups/rg-terraform-import"
  to = azurerm_resource_group.rg
}

Затем я запускаю terraform plan и добавляю флаг -generate-config-out:

$ terraform plan -generate-config-out=imported.tf

Terraform создает файл с именем imported.tf (имя указывается для флага -generate-config-out), содержащий два импортированных ресурса. Затем он выполняет обычную операцию plan.

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

Краткое содержание

Импорт ресурсов иногда является необходимым злом, поэтому хорошо знать, что его довольно легко выполнить. Новый блок import превращает предыдущий императивный интерфейс с использованием terraform import в декларативный интерфейс, который следует обычному рабочему процессу Terraform.

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