Terraform позволяет описывать облачную инфраструктуру OpenStack в виде кода и управлять ее жизненным циклом через единый набор команд. Вместо ручного создания ресурсов в панели можно заранее описать сеть, подсеть, Security Group, виртуальную машину и Floating IP, а затем применить конфигурацию через Terraform.
В этом руководстве настроим OpenStack Provider, покажем безопасную авторизацию без публикации учетных данных и последовательно выполним основные команды:
terraform init
terraform plan
terraform apply
terraform output
terraform destroy
В результате Terraform создаст:
- Приватную сеть;
- Подсеть;
- Security Group с необходимыми правилами;
- Виртуальную машину;
- Floating IP для внешнего доступа.
Отдельно разберем terraform.tfstate — файл состояния, в котором Terraform хранит информацию о созданных ресурсах и их связи с конфигурацией. Его нельзя бездумно публиковать в Git или редактировать вручную, поскольку state может содержать чувствительные данные и критически важен для корректного управления инфраструктурой.
В конце удалим созданные ресурсы через terraform destroy и разберем последствия этой команды: виртуальная машина, сеть, Floating IP и другие управляемые Terraform ресурсы будут удалены из OpenStack, поэтому перед подтверждением destroy необходимо внимательно проверять план изменений.
Как Terraform управляет инфраструктурой OpenStack
Terraform позволяет описывать инфраструктуру декларативно: в конфигурации указывается желаемое состояние, а сам Terraform определяет, какие действия нужно выполнить в OpenStack, чтобы привести реальные ресурсы к этому состоянию.
Вместо последовательности ручных действий в панели — создать сеть, подсеть, Security Group, виртуальную машину и Floating IP — эти ресурсы можно описать в .tf-файлах и затем применять одной командой.
Что такое Infrastructure as Code
Infrastructure as Code, или IaC, — это подход, при котором инфраструктура описывается в виде конфигурационных файлов.
Например, вместо ручного создания сети можно заранее определить ее в Terraform:
resource "openstack_networking_network_v2" "private" {
name = "terraform-network"
admin_state_up = true
}
Аналогично описываются:
- Подсети;
- Виртуальные машины;
- Security Group;
- Floating IP;
- Дополнительные сетевые параметры.
Главное преимущество такого подхода — повторяемость. Одна и та же конфигурация может быть применена повторно в другом проекте или окружении с минимальными изменениями.
Кроме того, Terraform позволяет заранее увидеть предполагаемые изменения и уменьшает количество ручных операций в облачной панели.
Как Terraform взаимодействует с OpenStack
Terraform сам по себе не создает ресурсы напрямую. Для взаимодействия с конкретной платформой используются providers.
В нашем случае понадобится OpenStack Provider: terraform-provider-openstack/openstack
Terraform передает provider информацию из конфигурации, а тот выполняет соответствующие запросы к OpenStack API.
Упрощенно схема выглядит так:

Поэтому для работы Terraform понадобятся параметры авторизации в OpenStack и доступ к соответствующим API.
При выполнении terraform plan Terraform анализирует конфигурацию и сравнивает ее с текущим состоянием.
При выполнении terraform applyprovider создает, изменяет или удаляет ресурсы через OpenStack API.
Какие ресурсы создадим в этом руководстве
В рамках примера создадим минимальную облачную инфраструктуру для одной виртуальной машины.
Она будет включать:

Дополнительно создадим Security Group с правилами для нужных входящих подключений.
В Terraform будут описаны следующие типы ресурсов:
openstack_networking_network_v2
openstack_networking_subnet_v2
openstack_networking_secgroup_v2
openstack_networking_secgroup_rule_v2
openstack_compute_instance_v2
openstack_networking_floatingip_v2
В зависимости от конкретной OpenStack-инфраструктуры для привязки Floating IP также может использоваться отдельный ресурс association или настройка порта.
После выполнения terraform apply Terraform должен самостоятельно создать зависимости между этими объектами в правильном порядке.
Как Terraform отслеживает состояние инфраструктуры
Terraform должен понимать, какие реальные объекты OpenStack соответствуют ресурсам в конфигурации.
Для этого используется state.
По умолчанию локальное состояние хранится в файле: terraform.tfstate
В нем Terraform сохраняет идентификаторы созданных ресурсов и другую служебную информацию.
Например, после создания сети state будет содержать связь между openstack_networking_network_v2.private и фактическим ID сети в OpenStack.
Благодаря этому при следующем запуске Terraform понимает, нужно ли создавать новый объект, изменить существующий или оставить его без изменений.
State особенно важен при командах:
terraform plan
terraform apply
terraform destroy
Если потерять state или заменить его неправильной версией, Terraform может перестать корректно сопоставлять конфигурацию с уже существующей инфраструктурой. Как следствие, если инфраструктура управляется при помощи Terraform, вручную изменения вносить через веб-интерфейс управления облаком не стоит - state также будет не согласован.
Поэтому к хранению state вернемся отдельно после создания ресурсов.
Подготовка окружения для Terraform

Перед описанием OpenStack-ресурсов установим Terraform, создадим рабочий каталог и подготовим безопасный способ авторизации.
Установка Terraform
Для Ubuntu Terraform можно установить из официального APT-репозитория HashiCorp.
Сначала установим необходимые пакеты:
sudo apt update
sudo apt install -y gnupg software-properties-common curl
Добавим ключ HashiCorp:
wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null
Добавим репозиторий:
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com \
$(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
Обновим индекс пакетов и установим Terraform:
sudo apt update
sudo apt install -y terraform
Проверим: terraform version
В ответ должна отображаться установленная версия Terraform.
Подготовка проекта и файлов конфигурации
Создадим отдельный каталог проекта:
mkdir -p ~/terraform-openstack
cd ~/terraform-openstack
Для удобства разделим конфигурацию на несколько файлов:
providers.tf
network.tf
security.tf
instance.tf
outputs.tf
variables.tf
Terraform автоматически читает все .tf-файлы внутри текущего рабочего каталога, поэтому не требуется вручную подключать каждый из них.
Создадим заготовки: touch providers.tf network.tf security.tf instance.tf outputs.tf variables.tf
Также сразу создадим .gitignore: nano .gitignore
Добавим:
.terraform/
*.tfstate
*.tfstate.*
*.tfvars
crash.log
State и локальные файлы переменных не должны случайно попасть в Git-репозиторий.
Получение параметров подключения к OpenStack
Для авторизации Terraform потребуются параметры OpenStack.
В зависимости от облачного провайдера они могут предоставляться в нескольких форматах:
- OpenRC-файл;
- Clouds.yaml;
- Application credentials;
- Отдельный набор переменных окружения.
Типичный OpenRC содержит переменные вроде:
OS_AUTH_URL
OS_USERNAME
OS_PASSWORD
OS_PROJECT_NAME
OS_USER_DOMAIN_NAME
OS_PROJECT_DOMAIN_NAME
OS_REGION_NAME
Например:
export OS_AUTH_URL=https://openstack.example.com:5000/v3
export OS_USERNAME=terraform-user
export OS_PASSWORD=...
export OS_PROJECT_NAME=project-name
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_DOMAIN_NAME=Default
export OS_REGION_NAME=RegionOne
Реальные учетные данные не следует добавлять непосредственно в Terraform-конфигурацию.
Если провайдер выдает готовый OpenRC-файл, обычно достаточно загрузить его: source openrc.sh
После этого переменные будут доступны текущей shell-сессии.
Авторизация без хранения учетных данных в Terraform-коде
В providers.tf не будем указывать пароль, username и другие секреты.
OpenStack Provider умеет использовать стандартные переменные окружения OS_*, поэтому конфигурация может оставаться без учетных данных.
Например:
provider "openstack" {
}
А нужные параметры передаются через окружение.
Проверить, что переменные загружены, можно без отображения их значений: env | grep '^OS_' | sed 's/=.*$/=<set>/'
Получится примерно:
OS_AUTH_URL=<set>
OS_USERNAME=<set>
OS_PASSWORD=<set>
OS_PROJECT_NAME=<set>
OS_REGION_NAME=<set>
Так можно подтвердить наличие параметров авторизации, не показывая реальные значения.
Для application credentials подход аналогичен: секрет передается через переменную окружения, а не записывается непосредственно в .tf-файл.
Настройка Terraform Provider для OpenStack
После подготовки окружения настроим OpenStack Provider и выполним первоначальную инициализацию проекта.
Создание файла providers.tf
Откроем: nano providers.tf
Добавим блок terraform:
terraform {
required_version = ">= 1.6.0"
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "~> 3.0"
}
}
}
provider "openstack" {
}
Здесь Terraform получает информацию о том, какой provider необходимо загрузить.
Сам блок:
provider "openstack" {
}
остается пустым, потому что параметры авторизации будут взяты из окружения.
Указание версии OpenStack Provider
В required_providers используется ограничение: version = "~> 3.0"
Оно позволяет Terraform использовать совместимые релизы указанной major-линейки, не переходя автоматически на потенциально несовместимую следующую major-версию.
После terraform init точная выбранная версия provider также будет зафиксирована в: .terraform.lock.hcl
Этот файл обычно стоит хранить в Git, поскольку он помогает использовать одинаковые версии providers в разных окружениях.
Инициализация рабочего каталога через terraform init
Теперь выполним: terraform init
Terraform:
- Прочитает providers.tf;
- Найдет OpenStack Provider;
- Загрузит необходимую версию;
- Создаст каталог .terraform;
- Сформирует или обновит .terraform.lock.hcl.
При успешной инициализации появится сообщение: Terraform has been successfully initialized!
Команда init не создает облачные ресурсы. Она только подготавливает локальный Terraform-проект к дальнейшей работе.
Проверка загруженного provider
Посмотреть providers текущего проекта можно командой: terraform providers
В выводе должен присутствовать: registry.terraform.io/terraform-provider-openstack/openstack
Дополнительно можно проверить содержимое lock-файла: grep -A 5 'terraform-provider-openstack/openstack' .terraform.lock.hcl
После успешной инициализации окружение готово к описанию сети, подсети, Security Group, виртуальной машины и Floating IP.
Создание сети и подсети OpenStack

После настройки provider можно описать базовую сетевую инфраструктуру. Для виртуальной машины создадим отдельную приватную сеть и подсеть, через которую она будет получать внутренний IP-адрес.
Такой подход удобнее, чем подключать VM напрямую к случайно выбранной существующей сети, поскольку вся структура становится частью Terraform-конфигурации и управляется вместе с остальными ресурсами.
Описание приватной сети в Terraform
Откроем файл: nano network.tf
Добавим ресурс сети:
resource "openstack_networking_network_v2" "private" {
name = "terraform-network"
admin_state_up = true
}
Параметр name = "terraform-network" задает имя сети в OpenStack.
А admin_state_up = true означает, что сеть должна быть включена после создания.
Terraform будет обращаться к этой сети внутри проекта по логическому имени: openstack_networking_network_v2.private
Это имя используется только в конфигурации Terraform и не обязано совпадать с названием ресурса в OpenStack.
Создание подсети и CIDR
Теперь добавим подсеть:
resource "openstack_networking_subnet_v2" "private" {
name = "terraform-subnet"
network_id = openstack_networking_network_v2.private.id
cidr = "192.168.50.0/24"
ip_version = 4
}
Главная связь здесь: network_id = openstack_networking_network_v2.private.id
Terraform получает ID сети после ее создания и использует его при создании подсети.
В примере используется диапазон: 192.168.50.0/24
Он предоставляет адреса внутри приватной сети.
Виртуальная машина позже получит один из этих адресов, например: 192.168.50.10
Конкретный IP обычно назначает OpenStack автоматически.
Настройка DNS и параметров сети
При необходимости для подсети можно дополнительно указать DNS-серверы.
Например:
resource "openstack_networking_subnet_v2" "private" {
name = "terraform-subnet"
network_id = openstack_networking_network_v2.private.id
cidr = "192.168.50.0/24"
ip_version = 4
dns_nameservers = [
"1.1.1.1",
"8.8.8.8"
]
}
В реальном проекте вместо публичных DNS могут использоваться внутренние resolver-адреса облачной инфраструктуры.
При необходимости можно также явно задавать DHCP, gateway и allocation pool. Например: enable_dhcp = true
Но если дополнительные параметры не требуются, OpenStack может использовать стандартные значения.
На этом этапе конфигурация содержит приватную сеть и подсеть, которые позже будут использованы виртуальной машиной.
Создание Security Group
Следующий шаг — определить правила доступа к VM.
Security Group в OpenStack работает как набор сетевых правил, которые разрешают или блокируют определенный входящий и исходящий трафик.
Для примера создадим отдельную группу и разрешим SSH, ICMP и HTTP/HTTPS.
Описание группы безопасности
Откроем: nano security.tf
Создадим Security Group:
resource "openstack_networking_secgroup_v2" "vm" {
name = "terraform-vm-sg"
description = "Security group for Terraform VM"
}
Теперь у нас есть отдельная группа: terraform-vm-sg
Правила доступа добавляются отдельными ресурсами.
Разрешение SSH-доступа
Добавим правило для TCP-порта 22:
resource "openstack_networking_secgroup_rule_v2" "ssh" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.vm.id
}
Параметр direction = "ingress" означает входящий трафик.
А диапазон 22 → 22 разрешает только SSH.
В демонстрационном примере используется: 0.0.0.0/0
то есть подключение разрешено с любого IPv4-адреса.
Для реальной инфраструктуры безопаснее ограничить SSH конкретным административным IP или подсетью, например: 203.0.113.25/32
Добавление правил для ICMP или веб-трафика
Для проверки доступности VM можно разрешить ICMP:
resource "openstack_networking_secgroup_rule_v2" "icmp" {
direction = "ingress"
ethertype = "IPv4"
protocol = "icmp"
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.vm.id
}
Если сервер будет использоваться как веб-сервер, добавим HTTP:
resource "openstack_networking_secgroup_rule_v2" "http" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 80
port_range_max = 80
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.vm.id
}
И HTTPS:
resource "openstack_networking_secgroup_rule_v2" "https" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 443
port_range_max = 443
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.vm.id
}
В результате Security Group будет разрешать только нужные типы входящего трафика.
Почему не стоит открывать лишние порты
Security Group лучше строить по принципу минимально необходимого доступа.
Если VM использует только SSH и HTTPS, нет смысла заранее разрешать:
3306
5432
6379
8080
9000
или другие служебные порты для всего интернета.
Каждое дополнительное правило увеличивает поверхность атаки.
Поэтому в реальном проекте стоит:
- Разрешать только действительно используемые порты;
- Ограничивать административный доступ доверенными IP;
- Не публиковать базы данных напрямую;
- Регулярно проверять список правил.
Terraform упрощает такую проверку, поскольку вся Security Group описана в коде и изменения хорошо видны через terraform plan.
Создание виртуальной машины

Теперь можно описать compute-инстанс.
VM будет подключена к созданной сети, получит Security Group и позже — Floating IP для внешнего доступа.
Выбор image и flavor
Перед созданием VM нужно определить image и flavor, доступные в конкретном OpenStack-проекте.
Image определяет операционную систему, например: Ubuntu 24.04
Flavor задает вычислительные ресурсы:
vCPU
RAM
Disk
В конфигурации можно использовать имена:
image_name = "Ubuntu 24.04"
flavor_name = "standard-1"
Однако реальные названия зависят от облачного провайдера.
Поэтому перед запуском Terraform их нужно проверить через панель OpenStack или CLI.
Например openstack image list и openstack flavor list.
Подключение VM к созданной сети
Откроем: nano instance.tf
Создадим виртуальную машину:
resource "openstack_compute_instance_v2" "vm" {
name = "terraform-vm"
image_name = "Ubuntu 24.04"
flavor_name = "standard-1"
network {
uuid = openstack_networking_network_v2.private.id
}
}
Здесь uuid = openstack_networking_network_v2.private.id связывает VM с сетью, которую Terraform создает в этом же проекте.
При выполнении terraform apply Terraform понимает, что сначала нужно создать сеть, а уже затем передать ее ID в конфигурацию VM.
Подключение SSH-ключа и Security Group
Для SSH-доступа лучше использовать заранее созданный OpenStack keypair.
Например: key_pair = "terraform-key"
Также добавим Security Group:
security_groups = [
openstack_networking_secgroup_v2.vm.name
]
Итоговый ресурс:
resource "openstack_compute_instance_v2" "vm" {
name = "terraform-vm"
image_name = "Ubuntu 24.04"
flavor_name = "standard-1"
key_pair = "terraform-key"
security_groups = [
openstack_networking_secgroup_v2.vm.name
]
network {
uuid = openstack_networking_network_v2.private.id
}
}
Теперь VM будет создана в нужной сети и сразу получит заданные правила доступа.
Если keypair еще не существует в OpenStack, его можно либо создать заранее, либо также описать отдельным Terraform-ресурсом.
Зависимости между ресурсами Terraform
Terraform автоматически строит граф зависимостей на основе ссылок между ресурсами.
Например network_id = openstack_networking_network_v2.private.id создает зависимость: Network → Subnet
А uuid = openstack_networking_network_v2.private.id создает Network → VM
Аналогично Security Group должна существовать до того, как она будет назначена виртуальной машине.
Упрощенная цепочка выглядит так:

Terraform сам определяет порядок создания ресурсов и обычно не требует ручного depends_on.
Явный depends_on нужен только в ситуациях, когда зависимость существует логически, но не выражена через ссылки между аргументами.
После добавления сети, подсети, Security Group и VM конфигурация уже описывает основную часть инфраструктуры. Далее можно создать Floating IP и связать его с виртуальной машиной.
Подключение Floating IP
Приватная сеть позволяет виртуальной машине взаимодействовать с другими ресурсами внутри OpenStack, но для подключения из интернета нужен публичный адрес. В OpenStack эту роль обычно выполняет Floating IP.
Terraform может автоматически запросить адрес из внешней сети и привязать его к созданной VM.
Создание Floating IP из внешней сети
Floating IP создается из внешнего пула адресов OpenStack. Название внешней сети зависит от конкретного облачного провайдера и может выглядеть, например, так:
public
external
ext-net
Перед использованием желательно проверить доступные сети: openstack network list --external
В Terraform создадим Floating IP отдельным ресурсом. Добавим в instance.tf:
resource "openstack_networking_floatingip_v2" "vm" {
pool = "public"
}
Параметр pool = "public" нужно заменить на реальное имя внешней сети OpenStack.
После terraform apply OpenStack выделит свободный публичный IP из указанного пула.
Привязка Floating IP к виртуальной машине
Создать Floating IP недостаточно — его нужно связать с портом или экземпляром VM.
Один из вариантов — использовать отдельный ресурс association:
resource "openstack_compute_floatingip_associate_v2" "vm" {
floating_ip = openstack_networking_floatingip_v2.vm.address
instance_id = openstack_compute_instance_v2.vm.id
}
Здесь Terraform берет openstack_networking_floatingip_v2.vm.address из созданного Floating IP и связывает его с openstack_compute_instance_v2.vm.id
Так появляется еще одна зависимость:

На практике точный способ привязки может зависеть от версии OpenStack Provider и сетевой схемы облака. В некоторых конфигурациях удобнее работать через порт VM и ресурс openstack_networking_floatingip_associate_v2.
Принцип остается одинаковым: публичный IP должен быть связан с сетевым интерфейсом созданной виртуальной машины.
Вывод публичного IP через output
Чтобы не искать адрес вручную в панели OpenStack, добавим output.
Откроем: nano outputs.tf
Добавим:
output "floating_ip" {
description = "Public Floating IP of the Terraform VM"
value = openstack_networking_floatingip_v2.vm.address
}
Можно также вывести приватный адрес VM:
output "private_ip" {
description = "Private IP of the Terraform VM"
value = openstack_compute_instance_v2.vm.access_ip_v4
}
После успешного terraform apply Terraform автоматически покажет outputs в конце выполнения.
Позже их можно получить отдельно: terraform output
Или только Floating IP: terraform output floating_ip
Outputs удобны для адресов, ID и других результатов, которые понадобятся после создания инфраструктуры.
Проверка плана Terraform

Перед созданием ресурсов Terraform позволяет проверить конфигурацию и увидеть будущие изменения. Этот этап особенно важен для облачной инфраструктуры, поскольку ошибка в коде может привести не только к сбою команды, но и к созданию или удалению платных ресурсов.
Форматирование и проверка конфигурации
Сначала приведем .tf-файлы к стандартному форматированию: terraform fmt
Команда автоматически исправляет отступы и формат HCL.
Проверить, какие файлы были изменены, можно: terraform fmt -check
Если команда завершается без вывода и ошибок, форматирование корректно.
Перед дальнейшими действиями также полезно посмотреть список файлов: ls -la
В каталоге должны находиться как минимум:
providers.tf
network.tf
security.tf
instance.tf
outputs.tf
variables.tf
Идём дальше.
Выполнение terraform validate
Теперь проверим синтаксис и внутреннюю согласованность конфигурации: terraform validate
При корректной конфигурации Terraform вернет: Success! The configuration is valid.
validate проверяет структуру HCL и допустимость аргументов provider, но не гарантирует, что указанные image, flavor, external network или keypair реально существуют в конкретном OpenStack-проекте.
Эти ошибки могут проявиться уже на этапе plan или apply.
Просмотр изменений через terraform plan
Создадим план: terraform plan
Terraform:
- Загрузит текущее состояние;
- Обратится к OpenStack API;
- Сравнит реальные ресурсы с конфигурацией;
- Построит список предполагаемых изменений.
Так как инфраструктура пока отсутствует, большинство ресурсов будет отмечено: + create
В конце появится итог примерно такого вида: Plan: 8 to add, 0 to change, 0 to destroy.
Количество ресурсов зависит от того, сколько отдельных Security Group rules и association-ресурсов описано в конфигурации.
Для более контролируемого применения план можно сохранить в файл: terraform plan -out=tfplan
После этого именно этот проверенный план применяется командой: terraform apply tfplan
Так между просмотром и применением Terraform не будет повторно строить другой план.
Какие ресурсы Terraform собирается создать
В выводе terraform plan должны присутствовать ресурсы основных типов:
openstack_networking_network_v2.private
openstack_networking_subnet_v2.private
openstack_networking_secgroup_v2.vm
openstack_networking_secgroup_rule_v2.ssh
openstack_compute_instance_v2.vm
openstack_networking_floatingip_v2.vm
Если добавлены HTTP, HTTPS и ICMP rules, они также будут показаны отдельными объектами.
Для каждого ресурса Terraform показывает будущие параметры. Например, для сети: + name = "terraform-network"
для подсети: + cidr = "192.168.50.0/24"
для VM: + name = "terraform-vm"
а адрес Floating IP на этапе plan обычно еще неизвестен и отображается как значение, которое будет определено после apply.
Перед продолжением стоит внимательно проверить:
- Имя image;
- Flavor;
- Keypair;
- CIDR сети;
- Security Group rules;
- External network;
- Количество создаваемых ресурсов.
Если plan содержит неожиданный destroy или массовое пересоздание ресурсов, apply запускать не следует, пока причина не будет понятна.
Создание инфраструктуры через terraform apply
После проверки плана можно перейти к созданию ресурсов.
Terraform будет обращаться к OpenStack API и выполнять операции в порядке, рассчитанном на основе зависимостей между объектами.
Запуск terraform apply
Если ранее был сохранен план: terraform plan -out=tfplan
применим именно его: terraform apply tfplan
Если отдельный plan-файл не создавался, можно выполнить: terraform apply
В этом случае Terraform снова построит план и покажет его перед подтверждением.
Для инфраструктурных изменений предпочтительнее сначала отдельно выполнить terraform plan, изучить результат и только после этого запускать apply.
Подтверждение изменений
При обычном: terraform apply
Terraform попросит подтверждение: Do you want to perform these actions?
Для продолжения необходимо ввести: yes
После этого начинается создание ресурсов.
В выводе будут последовательно появляться строки:
Creating...
Still creating...
Creation complete
Terraform может создавать независимые объекты параллельно. Например, Security Group и приватная сеть не обязательно должны ждать друг друга.
При этом ресурсы с явными зависимостями будут созданы в правильном порядке.
Проверка созданных ресурсов
После успешного выполнения Terraform сообщит итог: Apply complete!
Например: Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
Теперь инфраструктуру можно проверить через Terraform: terraform state list
В списке должны находиться созданные ресурсы.
Дополнительно при установленном OpenStack CLI можно проверить их непосредственно через API:
openstack network list
openstack server list
openstack floating ip list
Это позволяет убедиться, что Terraform state соответствует реальным объектам OpenStack.
Получение outputs после завершения apply
В конце terraform apply Terraform автоматически отображает значения из outputs.tf.
Например:
Outputs: floating_ip = "203.0.113.50"
Адрес в реальном проекте будет назначен OpenStack автоматически.
Получить outputs повторно можно командой: terraform output
Ожидаемый результат: floating_ip = "203.0.113.50"
Если нужно получить значение без дополнительного форматирования: terraform output -raw floating_ip
Это удобно для передачи результата в shell-скрипты: ssh ubuntu@$(terraform output -raw floating_ip)
Таким образом, после terraform apply Terraform не только создает всю описанную инфраструктуру, но и сразу предоставляет основные значения, необходимые для дальнейшего подключения и проверки VM.
Проверка созданной инфраструктуры
После terraform apply важно проверить не только итоговое сообщение Terraform, но и фактическое состояние ресурсов в OpenStack. Это позволяет убедиться, что сеть создана, виртуальная машина получила нужные интерфейсы, Floating IP привязан корректно, а Security Group действительно применяется.
Проверка ресурсов через OpenStack
Если установлен OpenStack CLI и переменные окружения уже загружены, можно получить список созданных ресурсов напрямую через API.
Проверим сети: openstack network list
В списке должна присутствовать: terraform-network
Проверим подсети: openstack subnet list
Ожидаем: terraform-subnet
Проверим виртуальные машины: openstack server list
В списке должна отображаться: terraform-vm
Проверим Floating IP: openstack floating ip list
И Security Group: openstack security group list
Если ресурсы присутствуют и их параметры соответствуют конфигурации, значит Terraform успешно создал инфраструктуру через OpenStack API.
Подключение к виртуальной машине по Floating IP
Получим публичный адрес через output: terraform output -raw floating_ip
После этого можно подключиться по SSH: ssh -i ~/.ssh/terraform-key ubuntu@$(terraform output -raw floating_ip)
Если используется другой пользователь образа, например:
debian
centos
rocky
его нужно заменить в команде.
При успешном подключении появится shell созданной виртуальной машины.
Для дополнительной проверки можно выполнить уже внутри VM:
hostname
ip addr
ip route
Так можно убедиться, что машина действительно получила адрес из приватной сети и имеет корректный маршрут.
Проверка сети и Security Group
Чтобы проверить сетевую конфигурацию, сначала посмотрим адреса VM: openstack server show terraform-vm
В информации о сервере должны отображаться приватный IP и, если association выполнен корректно, Floating IP.
Также можно проверить порт: openstack port list --server terraform-vm
И правила Security Group: openstack security group rule list terraform-vm-sg
В списке должны присутствовать созданные Terraform правила, например:
TCP 22
TCP 80
TCP 443
ICMP
На практике полезно отдельно убедиться, что разрешены только нужные порты.
Если SSH работает, а другие административные сервисы не опубликованы, значит Security Group настроена в соответствии с ожидаемой схемой.
Как Terraform хранит состояние инфраструктуры
После создания ресурсов Terraform должен помнить, какие реальные объекты OpenStack соответствуют каждому resource-блоку в конфигурации.
Для этого используется state.
Что находится в terraform.tfstate
По умолчанию локальное состояние хранится в: terraform.tfstate
Это структурированный файл, в котором Terraform сохраняет информацию о ресурсах, которыми управляет.
В state могут находиться:
- ID сети;
- ID подсети;
- ID виртуальной машины;
- ID Security Group;
- Адрес Floating IP;
- Атрибуты ресурсов;
- Зависимости между объектами;
- Outputs;
- Метаданные provider.
Например, Terraform связывает логический ресурс: openstack_compute_instance_v2.vm
с конкретным ID виртуальной машины в OpenStack.
Именно поэтому при следующем terraform plan Terraform понимает, что VM уже существует и не должна создаваться повторно.
Почему state-файл нельзя редактировать вручную
terraform.tfstate — не обычный конфигурационный файл.
Хотя технически это JSON, вручную менять его содержимое не стоит. Ошибка в ID или структуре может привести к тому, что Terraform перестанет правильно сопоставлять конфигурацию с реальной инфраструктурой.
Последствия могут быть разными:
- Terraform решит создать дубликат ресурса;
- Существующий объект перестанет отслеживаться;
- Plan покажет неожиданное пересоздание;
- Destroy не затронет нужный ресурс;
- Состояние проекта станет несогласованным.
Для управляемых изменений state существуют специальные команды:
terraform state list
terraform state show RESOURCE
terraform state mv
terraform state rm
Их тоже нужно использовать осторожно, но они корректно изменяют структуру state.
Почему state-файл может содержать чувствительные данные
State содержит фактические значения атрибутов ресурсов, а не только названия объектов.
Поэтому в нем могут оказаться:
- IP-адреса;
- Внутренние идентификаторы;
- Metadata;
- Конфигурационные значения;
- Outputs;
- Параметры ресурсов;
- Некоторые значения, переданные через provider или resource arguments.
Даже если значение в Terraform помечено как: sensitive = true
это в первую очередь скрывает его из обычного CLI-вывода. Само значение при необходимости все равно может присутствовать в state.
Поэтому state нужно рассматривать как потенциально чувствительный файл.
Где хранить state при командной работе
Для одиночного тестового проекта локальный terraform.tfstate допустим.
Но при командной работе локальное хранение быстро создает проблемы:
- У разных участников появляются разные версии state;
- Два человека могут одновременно запустить apply;
- State легко потерять;
- Сложно организовать централизованные права доступа.
Поэтому в рабочей инфраструктуре обычно используют remote backend.
Он позволяет хранить state централизованно и, в зависимости от backend, использовать дополнительные механизмы вроде блокировки состояния.
Конкретный backend зависит от инфраструктуры команды. Это может быть совместимое объектное хранилище, специализированный Terraform backend или другой поддерживаемый сервис.
Главное требование — state должен храниться централизованно, с контролем доступа и резервным копированием.
Почему terraform.tfstate нельзя публиковать в Git
State не является исходным кодом проекта.
Его нельзя хранить в публичном Git-репозитории, потому что:
- Он может содержать чувствительные значения;
- State регулярно изменяется;
- Git не решает проблему конкурентного доступа;
- Старая чувствительная информация остается в истории коммитов даже после удаления из текущей версии.
Поэтому в .gitignore мы заранее добавили:
*.tfstate
*.tfstate.*
При этом файл .terraform.lock.hcl наоборот, обычно стоит хранить в репозитории, поскольку он фиксирует выбранные версии providers.
Проверим, какими ресурсами Terraform управляет сейчас: terraform state list
Пример:
openstack_compute_floatingip_associate_v2.vm
openstack_compute_instance_v2.vm
openstack_networking_floatingip_v2.vm
openstack_networking_network_v2.private
openstack_networking_secgroup_rule_v2.http
openstack_networking_secgroup_rule_v2.https
openstack_networking_secgroup_rule_v2.icmp
openstack_networking_secgroup_rule_v2.ssh
openstack_networking_secgroup_v2.vm
openstack_networking_subnet_v2.private
Этот список особенно полезен перед изменением или удалением инфраструктуры.
Изменение существующей инфраструктуры

Terraform не предназначен только для первоначального создания ресурсов. Та же конфигурация используется и для последующих изменений.
Достаточно изменить желаемое состояние в .tf-файле и снова выполнить terraform plan.
Как Terraform определяет изменения
Предположим, что изначально Security Group разрешает SSH, ICMP и HTTPS.
Позже понадобится добавить HTTP.
В security.tf появляется новый ресурс:
resource "openstack_networking_secgroup_rule_v2" "http" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 80
port_range_max = 80
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.vm.id
}
Terraform сравнит:
- Конфигурацию;
- State;
- Фактическое состояние OpenStack.
После этого определит, что существующие объекты сохраняются, а создать нужно только одно новое правило.
Повторный запуск terraform plan
После изменения конфигурации снова выполним:
terraform fmt
terraform validate
И: terraform plan
Если изменение касается только нового HTTP rule, итог может выглядеть так: Plan: 1 to add, 0 to change, 0 to destroy.
Это одна из самых полезных особенностей Terraform: перед применением можно увидеть, затронет ли изменение уже работающие ресурсы.
Если вместо ожидаемого изменения появляется: -/+ destroy and then create replacement
нужно внимательно проверить причину. Некоторые атрибуты ресурсов нельзя изменить без пересоздания.
Применение изменений без пересоздания всей инфраструктуры
Если plan соответствует ожиданиям, применим изменения: terraform apply
Terraform изменит только те объекты, состояние которых отличается от конфигурации.
Например, добавление одного Security Group rule не требует заново создавать:
Network
Subnet
VM
Floating IP
Они останутся без изменений.
После выполнения можно снова проверить terraform state list и terraform plan
Если инфраструктура полностью соответствует конфигурации, Terraform сообщит:
No changes. Your infrastructure matches the configuration.
Так Terraform позволяет использовать одни и те же .tf-файлы для создания и последующего управляемого изменения облачной инфраструктуры, не пересоздавая все ресурсы при каждой правке.
Удаление инфраструктуры через Terraform
Terraform управляет не только созданием и изменением ресурсов, но и их удалением. Для этого используется команда terraform destroy.
Она особенно опасна в рабочей инфраструктуре, потому что Terraform удаляет реальные облачные ресурсы, которые находятся под его управлением. Поэтому перед подтверждением необходимо внимательно проверить план.
Просмотр ресурсов перед удалением
Сначала посмотрим, какие объекты находятся в state: terraform state list
Например:
openstack_compute_floatingip_associate_v2.vm
openstack_compute_instance_v2.vm
openstack_networking_floatingip_v2.vm
openstack_networking_network_v2.private
openstack_networking_secgroup_rule_v2.http
openstack_networking_secgroup_rule_v2.https
openstack_networking_secgroup_rule_v2.icmp
openstack_networking_secgroup_rule_v2.ssh
openstack_networking_secgroup_v2.vm
openstack_networking_subnet_v2.private
Это позволяет заранее понять, какие ресурсы Terraform считает частью проекта.
Для предварительного просмотра удаления можно выполнить: terraform plan -destroy
Terraform построит план, в котором ресурсы будут отмечены как удаляемые: - destroy
В конце появится итог примерно такого вида: Plan: 0 to add, 0 to change, 10 to destroy.
Количество зависит от фактической конфигурации проекта.
Выполнение terraform destroy
Для удаления управляемой инфраструктуры используем: terraform destroy
Terraform еще раз покажет план и запросит подтверждение: Do you really want to destroy all resources?
Для продолжения необходимо явно ввести: yes
После этого начнется удаление.
В консоли будут появляться сообщения:
Destroying...
Still destroying...
Destruction complete
Terraform учитывает зависимости между объектами и старается удалить ресурсы в подходящем порядке.
Например, Floating IP association должен исчезнуть до удаления самой VM, а сеть нельзя удалить, пока к ней остаются связанные ресурсы.
Какие ресурсы будут удалены
terraform destroy затрагивает все ресурсы текущей конфигурации, которыми Terraform управляет через state.
В нашем примере это:
- Виртуальная машина;
- Floating IP и его association;
- Security Group;
- Правила Security Group;
- Приватная сеть;
- Подсеть.
Если в проект позже добавить дополнительные диски, порты, routers или другие OpenStack-объекты, они также будут включены в destroy, если находятся под управлением этого Terraform state.
Перед подтверждением особенно важно обращать внимание на итоговую строку: 0 to add, 0 to change, N to destroy
Если число удаляемых объектов неожиданно велико, операцию лучше отменить и сначала разобраться с конфигурацией и state.
Последствия удаления виртуальной машины, сети и Floating IP
Удаление инфраструктуры имеет реальные последствия.
При уничтожении VM исчезает сам compute instance. Данные, находившиеся только на ее локальном ephemeral-диске, после удаления могут быть потеряны.
Если отдельный persistent volume также описан в Terraform и включен в destroy, он тоже может быть удален в зависимости от конфигурации ресурса.
При удалении Floating IP адрес освобождается и возвращается в пул провайдера. После повторного создания инфраструктуры OpenStack может выдать уже другой публичный IP.
Удаление приватной сети и подсети разрывает сетевую конфигурацию проекта. Если к этим объектам вручную подключены ресурсы, которые Terraform не отслеживает, удаление может завершиться ошибкой из-за существующих зависимостей.
Поэтому terraform destroy нельзя воспринимать как обычную очистку локального проекта. Команда изменяет реальную облачную инфраструктуру.
Что происходит со state после destroy
После успешного удаления Terraform обновляет state и удаляет из него записи об уничтоженных объектах.
Если все ресурсы проекта были удалены, команда terraform state list не должна возвращать управляемые объекты.
В конце выполнения появится сообщение: Destroy complete! Resources: 10 destroyed.
Сам файл terraform.tfstate при этом может остаться в рабочем каталоге. Он просто будет отражать новое состояние, в котором созданные ранее ресурсы больше не существуют.
Удалять state вручную перед terraform destroy нельзя. Если Terraform потеряет связь с ресурсами до их удаления, облачные объекты могут остаться существовать, но перестать управляться текущим проектом.
Безопасная работа с Terraform и OpenStack

Terraform получает возможность создавать и удалять реальные ресурсы, поэтому безопасность проекта включает не только защиту OpenStack-аккаунта, но и правильную работу с конфигурацией, state и планами изменений.
Хранение учетных данных вне .tf-файлов
Не следует записывать пароль OpenStack прямо в provider:
provider "openstack" {
user_name = "admin"
password = "secret-password"
}
Такой файл легко случайно отправить в Git, архив или систему резервного копирования.
Вместо этого лучше использовать:
- Переменные окружения OS_*;
- OpenRC;
- Application credentials;
- Защищенный clouds.yaml;
- Secret manager в более крупной инфраструктуре.
В результате Terraform-код остается пригодным для публикации и командной работы без захардкоженых учетных данных.
При использовании application credentials также стоит создавать отдельные credentials с минимально необходимыми правами, а не использовать основной административный аккаунт.
Использование .gitignore для state и локальных переменных
Минимальный .gitignore Terraform-проекта может выглядеть так:
.terraform/
*.tfstate
*.tfstate.*
*.tfvars
crash.log
При необходимости туда также добавляют локальные override-файлы и другие временные данные.
Например:
override.tf
override.tf.json
*_override.tf
*_override.tf.json
При этом .terraform.lock.hcl обычно сохраняют в Git.
Таким образом в репозитории остаются конфигурация и зафиксированные версии providers, но не локальный state и секретные значения.
Защита state-файла и резервных копий
Поскольку state может содержать чувствительные данные, доступ к нему должен быть ограничен.
Для локального проекта права можно проверить: ls -l terraform.tfstate
При необходимости ограничить: chmod 600 terraform.tfstate
Если state хранится удаленно, следует использовать:
- Приватный backend;
- Разграничение прав доступа;
- Шифрование;
- Versioning или резервное копирование;
- Блокировку state, если backend ее поддерживает.
Резервные копии state также считаются чувствительными. Нельзя защищать основной файл и при этом оставлять копии вроде terraform.tfstate.backup в общедоступном каталоге.
Проверка plan перед apply и destroy
Главное правило работы с Terraform — не применять инфраструктурные изменения вслепую.
Перед созданием или изменением: terraform plan
Перед удалением: terraform plan -destroy
Нужно проверить:
- Какие ресурсы добавятся;
- Какие изменятся;
- Какие будут уничтожены;
- Есть ли replacement;
- Изменяются ли ожидаемые параметры;
- Не появился ли неожиданный destroy.
Особенно внимательно следует относиться к обозначению: -/+
Оно означает, что существующий объект будет уничтожен и создан заново.
Для виртуальной машины, диска или публичного IP это может привести к downtime или потере данных.
Обслуживание Terraform-проекта
После первоначального развертывания Terraform-проект продолжает использоваться для проверки, обновления и изменения инфраструктуры.
Просмотр текущего состояния через terraform state
Получить список управляемых объектов можно: terraform state list
Посмотреть подробности конкретного ресурса: terraform state show openstack_compute_instance_v2.vm
Команда покажет известные Terraform параметры VM.
Также полезна: terraform show
Она выводит текущее состояние проекта в более полном виде.
Однако state-команды предназначены прежде всего для диагностики. Повседневные изменения инфраструктуры лучше выполнять через .tf-конфигурацию и terraform apply, а не непосредственно манипулировать state.
Обновление provider
Версии providers фиксируются в: .terraform.lock.hcl
Проверить используемые providers можно: terraform providers
Если требуется обновить их в пределах разрешенных ограничений: terraform init -upgrade
После обновления необходимо снова выполнить terraform validate и terraform plan.
Перед применением изменений нужно убедиться, что новая версия provider не предлагает неожиданного пересоздания ресурсов.
В рабочей инфраструктуре обновления providers лучше сначала проверять на тестовом проекте.
Что делать при расхождении state и реальной инфраструктуры
Расхождение может появиться, если ресурс изменить или удалить вручную через OpenStack Dashboard, минуя Terraform.
Например, если вручную удалить VM, но запись о ней остается в state, следующий terraform plan обнаружит различие.
В зависимости от ситуации Terraform может предложить восстановить удаленный объект или выполнить другое изменение для возвращения инфраструктуры к описанной конфигурации.
Поэтому ручные изменения ресурсов, которыми управляет Terraform, лучше избегать.
Если существующий OpenStack-объект нужно добавить под управление Terraform, применяется импорт, например: terraform import RESOURCE_ADDRESS OPENSTACK_ID
После импорта конфигурация .tf должна соответствовать реальным параметрам ресурса.
Если же объект нужно перестать отслеживать, но оставить в OpenStack, существует: terraform state rm RESOURCE_ADDRESS
Такую операцию нужно выполнять только после проверки последствий: Terraform забудет ресурс, но не удалит его из облака.
Что делать, если terraform apply завершился ошибкой
Ошибка terraform apply не обязательно означает, что ничего не было создано.
Terraform выполняет операции постепенно, поэтому часть ресурсов может успеть появиться до возникновения ошибки.
Сначала следует посмотреть сообщение Terraform и определить проблемный ресурс.
После этого проверить state: terraform state list
И снова выполнить: terraform plan
Terraform сравнит текущую конфигурацию с тем, что уже было создано, и покажет оставшиеся действия.
Если ошибка связана, например, с неправильным именем flavor или external network, достаточно исправить конфигурацию и повторно выполнить: terraform apply
Terraform не должен заново создавать ресурсы, которые уже успешно появились и корректно записаны в state.
При проблемах с авторизацией следует проверить наличие переменных: env | grep '^OS_' | sed 's/=.*$/=<set>/'
Если ошибка касается конкретного OpenStack-ресурса, полезно дополнительно проверить его состояние через OpenStack CLI или панель провайдера.
Главное — не удалять terraform.tfstate в попытке «начать заново». Это может только усугубить ситуацию и оставить уже созданные облачные объекты без связи с Terraform.
Заключение

Terraform позволяет описывать инфраструктуру OpenStack как код и управлять всем ее жизненным циклом через единый проект. Вместо ручного создания ресурсов в панели конфигурация определяет желаемое состояние, а OpenStack Provider выполняет необходимые операции через API. Сам Terraform при этом использует state, чтобы сопоставлять объекты из .tf-файлов с реальными ресурсами облака.
В рамках руководства мы описали приватную сеть и подсеть, Security Group, виртуальную машину и Floating IP, а затем рассмотрели последовательность terraform init, plan, apply, output и destroy. Такой подход позволяет заранее увидеть предполагаемые изменения и применять их контролируемо, а при необходимости — полностью удалить созданную Terraform инфраструктуру.
Особое внимание нужно уделять terraform.tfstate. Этот файл необходим Terraform для управления ресурсами и может содержать чувствительные значения, поэтому его нельзя бездумно публиковать в Git. Для командной работы лучше использовать защищенный remote backend с разграничением доступа и, если backend это поддерживает, блокировкой состояния.
Наконец, terraform destroy следует считать полноценной инфраструктурной операцией, а не очисткой локального проекта. Команда удаляет реальные ресурсы, находящиеся под управлением текущего state. Поэтому перед подтверждением удаления необходимо просматривать destroy plan и учитывать возможную потерю VM, адресов и данных, которые не были сохранены отдельно.
FAQ
Зачем Terraform нужен state-файл?
Terraform использует state для сопоставления ресурсов из конфигурации с реальными объектами инфраструктуры. В нем хранится информация, необходимая для определения последующих изменений. Без корректного state Terraform может потерять связь с ранее созданными ресурсами.
Можно ли удалить terraform.tfstate и просто снова выполнить terraform apply?
Так делать не следует. Если реальные ресурсы OpenStack продолжают существовать, а state потерян, Terraform больше не будет знать, что именно он создавал. В результате новый plan может предложить создать дополнительные объекты вместо управления уже существующими.
Если state поврежден или инфраструктура разошлась с ним, сначала нужно разобраться с текущим состоянием и при необходимости использовать terraform import или команды terraform state.
Почему terraform.tfstate нельзя хранить в публичном Git?
Локальный Terraform state хранится в plaintext и может содержать секретные или другие чувствительные значения. Даже значения, помеченные как sensitive, могут присутствовать в state, хотя Terraform скрывает их в стандартном выводе CLI. Поэтому state необходимо исключать из Git и защищать как чувствительный файл.
Можно ли хранить .terraform.lock.hcl в Git?
Да. В отличие от state, dependency lock file предназначен для фиксации выбранных версий providers и обычно сохраняется вместе с конфигурацией. Это помогает разным окружениям использовать одинаковые зависимости.
Зачем выполнять terraform plan перед terraform apply?
terraform plan показывает, какие действия Terraform намерен выполнить для приведения инфраструктуры к описанной конфигурации. Это позволяет заметить неожиданное создание, изменение, пересоздание или удаление ресурсов до фактического изменения облака. terraform apply затем выполняет операции, предложенные планом.
Чем terraform plan -destroy отличается от terraform destroy?
terraform plan -destroy только показывает предполагаемый план удаления и не изменяет инфраструктуру. terraform destroy выполняет удаление управляемых ресурсов. Поэтому перед уничтожением инфраструктуры полезно сначала просмотреть отдельный destroy plan.
Удалит ли terraform destroy все ресурсы OpenStack-проекта?
Нет. Команда ориентируется на текущую Terraform-конфигурацию и state. Она удаляет ресурсы, которыми управляет данный Terraform-проект, а не произвольные объекты всего OpenStack-проекта.
Что произойдет с Floating IP после terraform destroy?
Если Floating IP создан как Terraform resource и входит в текущий state, Terraform запросит его удаление вместе с остальной инфраструктурой. После освобождения адрес может вернуться в пул OpenStack, поэтому при следующем создании инфраструктуры необязательно будет назначен тот же IP.
Можно ли передавать OpenStack credentials через переменные окружения?
Да. OpenStack Provider поддерживает стандартные переменные окружения, включая параметры проекта, пользователя и application credentials. Это позволяет не размещать пароли и secrets непосредственно в .tf-файлах.
Где лучше хранить state при работе команды?
Для общего Terraform-проекта предпочтителен remote backend с контролем доступа. Backends отвечают за хранение state и могут предоставлять механизм state locking. Если locking поддерживается, Terraform использует его для предотвращения одновременной записи состояния несколькими операциями.
Можно ли совмещать ручное управление и IAC?
Если инфраструктура управляется через Terraform, ручные изменения через веб-интерфейс или API OpenStack приведут к рассинхронизации зафиксированного состояния и фактической инфраструктуры. В свою очередь это может привести к ошибкам или удалениям ручных изменений, поэтому ручные изменения нежелательны. В тоже время, если через Terraform управляются виртуальные машины, а сети и маршрутизация - нет, то ручные изменения сетей не повлияют на state и являются допустимыми.
Что делать, если terraform apply завершился ошибкой на середине?
Не следует удалять state и начинать проект заново. Часть ресурсов могла уже успешно создаться и попасть в state. Сначала нужно изучить сообщение об ошибке, выполнить terraform state list и новый terraform plan, исправить проблемную конфигурацию и затем повторить terraform apply.
Terraform применяет действия из плана, поэтому после частично выполненной операции следующий plan помогает определить, какие изменения еще остаются необходимыми.


