← Весь справочник

terraform

Инфраструктура как код — init/plan/apply/destroy, провайдеры, ресурсы, переменные, outputs и state

Terraform описывает инфраструктуру в файлах .tf на языке HCL и приводит реальный мир к описанию: создаёт, меняет и удаляет ресурсы через провайдеры (облака, DNS, Kubernetes, локальные файлы). Что уже создано, Terraform помнит в файле состояния — terraform.tfstate.

В песочнице нет доступа в интернет. Провайдер hashicorp/local (версия 2.5.1) заранее положен в локальное зеркало, поэтому terraform init работает с ним офлайн. Встроенный ресурс terraform_data не требует провайдера вовсе. Другие провайдеры (random, aws и т. д.) скачать не получится.

Синтаксис

terraform [-chdir=каталог] <команда> [опции]

Terraform читает все .tf в текущем каталоге как одну конфигурацию; порядок файлов и блоков не важен.

Команды

Основной цикл

КомандаЧто делает
initскачать провайдеры и модули, создать .terraform/ и .terraform.lock.hcl; -upgrade — обновить версии
validateпроверить конфигурацию (нужен init)
fmtвыровнять форматирование; -check — только проверить (код 3, если есть что править), -diff, -recursive
planпоказать, что будет создано (+), изменено (~), пересоздано (-/+), удалено (-)
plan -out=tf.planсохранить план; apply tf.plan применит ровно его, без вопросов
applyпоказать план и применить после ответа yes; -auto-approve — без вопроса
destroyудалить всё, чем управляет конфигурация (plan -destroy — посмотреть заранее)

Флаги plan / apply

ФлагЧто делает
-var 'имя=значение'значение переменной
-var-file=файл.tfvarsзначения из файла
-target=адресработать только с одним ресурсом (для исключительных случаев)
-replace=адреспересоздать ресурс, даже если он не изменился
-refresh-onlyтолько обновить state по реальному миру, ничего не меняя
-no-colorбез цветов — для логов и скриптов

State и вывод

КомандаЧто делает
output / output имявсе outputs / одно; -raw — без кавычек, -json — в JSON
showтекущее состояние (или сохранённый план: show tf.plan)
state listресурсы в state
state show адресатрибуты одного ресурса
state rm адресзабыть ресурс, не удаляя его в реальности
state mv старый новыйпереименовать ресурс в state (после переименования в коде)
import адрес idвзять существующий объект под управление
consoleинтерактивно вычислять выражения: echo 'upper("x")' | terraform console
providersкакие провайдеры нужны конфигурации
workspace list/new/selectнесколько независимых state для одной конфигурации
graphграф зависимостей в формате DOT

Блоки HCL

БлокДля чего
terraform { required_providers { … } }какие провайдеры и версии нужны; там же required_version и backend (где хранить state)
provider "имя" { … }настройки провайдера (регион, токены)
resource "тип" "имя" { … }объект, которым управляет Terraform; адрес — тип.имя
data "тип" "имя" { … }прочитать существующий объект, не управляя им; адрес — data.тип.имя
variable "имя" { … }входной параметр: type, default, description, sensitive, validation
output "имя" { value = … }значение, которое печатается после apply и доступно через terraform output
locals { имя = … }вычисляемые значения внутри конфигурации: local.имя
module "имя" { source = "./путь" }подключить набор .tf-файлов как модуль

Мета-аргументы ресурса

АргументЧто делает
count = 3несколько экземпляров; номер — count.index, адрес — тип.имя[0]
for_each = map или setэкземпляр на каждый элемент; each.key, each.value, адрес — тип.имя["ключ"]
depends_on = [ресурс]явная зависимость, если её не видно по ссылкам
lifecycle { … }create_before_destroy, prevent_destroy, ignore_changes

Выражения

ЗаписьЧто значит
var.имя, local.имяпеременная, локальное значение
тип.имя.атрибутатрибут другого ресурса — заодно создаёт зависимость
"${var.env}-app"подстановка в строку
path.moduleкаталог текущей конфигурации
[for x in список : upper(x)]преобразование списка
условие ? a : bвыбор значения
upper(), lower(), join(), split(), format(), length(), contains(), lookup(), file(), templatefile()встроенные функции

Типы переменных: string, number, bool, list(string), map(string), set(string), object({…}).

Откуда берутся значения переменных (от слабого к сильному): default → переменные окружения TF_VAR_имя → terraform.tfvars → *.auto.tfvars → -var и -var-file в командной строке.

Примеры

main.tf с локальным провайдером:

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

variable "greeting" {
  type        = string
  default     = "hello"
  description = "Текст для файла"
}

variable "files" {
  type    = map(string)
  default = { a = "alpha", b = "beta" }
}

resource "local_file" "note" {
  filename        = "${path.module}/out/note.txt"
  content         = "${var.greeting}\n"
  file_permission = "0644"
}

resource "local_file" "many" {
  for_each = var.files
  filename = "${path.module}/out/${each.key}.txt"
  content  = upper(each.value)
}

output "note_path" {
  value = local_file.note.filename
}
# подготовить каталог: провайдер возьмётся из локального зеркала
terraform init

# проверить и отформатировать
terraform validate
terraform fmt

# посмотреть план, сохранить и применить именно его
terraform plan -out=tf.plan
terraform apply tf.plan

# что Terraform сейчас знает о ресурсах
terraform state list
terraform state show 'local_file.many["a"]'
terraform output -raw note_path

# изменить переменную — план покажет пересоздание файла
terraform plan -var 'greeting=hi'

# значения из файла
echo 'greeting = "from file"' > terraform.tfvars
terraform apply -auto-approve

# посчитать выражение
echo 'upper(var.greeting)' | terraform console

# убрать всё созданное
terraform destroy -auto-approve

Подводные камни

  • Без init ничего не работает. plan/apply/validate в свежем каталоге падают с ошибкой про lock-файл или неустановленные провайдеры. После правки required_providers или модулей снова запусти init.
  • State — источник правды для Terraform. Если ресурс удалить руками, plan предложит создать его заново. Если удалить terraform.tfstate, Terraform «забудет» всё созданное и попробует создать заново.
  • Не редактируй state вручную. Для переименований есть state mv, для «забыть» — state rm.
  • Многие изменения — это пересоздание. У local_file смена content или filename даёт -/+ must be replaced: старый объект удаляется. В плане ищи forces replacement.
  • sensitive = true прячет значение только в выводе plan/apply. terraform output имя его покажет, а в terraform.tfstate оно лежит открытым текстом.
  • terraform.tfvars сильнее TF_VAR_…. Переменная окружения не сработает, если то же имя задано в tfvars.
  • count адресует по номеру. Удаление элемента из середины списка сдвинет индексы и пересоздаст «чужие» ресурсы. Для именованных наборов бери for_each.
  • apply без -auto-approve ждёт ответа yes. Любой другой ответ — отмена, а в скрипте без ввода apply упадёт с error asking for approval. Если изменений нет, вопроса не будет.
  • .terraform/ и terraform.tfstate не коммить: первое скачивается через init, второе может содержать секреты. .terraform.lock.hcl — коммить.

См. также

ansible · kubectl · jq · gitlab-ci