Перейти к основному содержимому

Terraform

Разработчику Архитектору Инженеру

Play ITЗагрузка интерактивного демо…

Место Terraform в DevOps

Terraform создаёт и меняет облачные ресурсы (серверы, сети, БД); настройку ОС и пакетов часто делают Ansible.

План и apply в CI — в GitHub Actions или GitLab CI (1.035 / 501).

Альтернатива на Python/TypeScript — Pulumi.


Terraform

Общее определение и назначение

Terraform — это программа, которая позволяет описать всю вашу инфраструктуру в текстовых файлах, а потом одной командой создать её в облаке или локально.

Представьте, что вы пишете рецепт:

— 1 сервер с 2 CPU и 4GB RAM
— 1 база данных PostgreSQL
— 1 сеть с диапазоном 10.0.0.0/16
— 1 firewall, который пускает только 80 и 443 порты

Разбор:

  • Список — декларативное описание желаемого состояния инфраструктуры, а не пошаговых команд.
  • ВМ с CPU/RAM, PostgreSQL, VPC 10.0.0.0/16 и firewall — типичные зависимые ресурсы в облаке.
  • Terraform сам выстроит порядок создания (сеть → ВМ → БД → правила firewall) по графу зависимостей.

А потом говорите: "Сделайте мне это". И Terraform идёт в AWS/GCP/Azure/Proxmox/куда угодно и создаёт всё в точности по рецепту. Вы описываете что должно быть, а не как это сделать. Программа сама догадывается, что создавать первым, что вторым, что удалить, что изменить.

Например:

  • для создания dev-окружения придётся полдня тыкаться;
  • чтобы узнать, что сейчас в проде, надо дёргать сеньора;
  • чтобы восстановить после падения дата-центра, надо молиться и плакать два дня;
  • чтобы понять, что изменилось за последнюю неделю, надо сравнивать скриншоты;
  • чтобы удалить всё и не оставить мусора, нужно долго копаться;
  • а в команде из пяти человек кто последний, тот и правит консоль.

С Terraform инфраструктура превращается в код.

А код можно:

  • выполнять командой;
  • хранить в Git;
  • проверять через код-ревью;
  • откатывать;
  • копировать;
  • автоматически тестировать в CI.

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

Основная задача Terraform — унифицировать управление разнородными системами через единый интерфейс. Поддержка осуществляется через провайдеры — специализированные плагины, которые взаимодействуют с API конкретных платформ. Благодаря этому один и тот же рабочий процесс применим к AWS, Azure, Google Cloud, Kubernetes, базам данных, сетевому оборудованию и многим другим компонентам.

Для начала работы его нужно просто установить, добавить terraform в PATH и всё. Это единый бинарник, никаких серверов, баз данных и дополнительных зависимостей.

Ваш компьютер/CI-сервер

├── main.tf (ваши рецепты)
├── terraform.tfstate (файл с памяткой "что уже создано")

└── Terraform (один бинарник)

├── идёт в AWS API → создаёт EC2, S3, VPC...
├── идёт в GCP API → создаёт Compute Engine, Cloud SQL...
├── идёт в Kubernetes API → создаёт Pod, Service, Ingress...
└── идёт в Proxmox API → создаёт VM

Разбор:

  • main.tf — HCL-конфигурация "рецепта"; terraform.tfstate — память о уже созданных ресурсах.
  • Бинарник Terraform на CI или ноутбуке вызывает API провайдеров (AWS, GCP, Kubernetes, Proxmox).
  • Нет обязательного облачного SaaS от HashiCorp: управление идёт из вашего контура с вашими credentials.

Terraform просто запускается на вашей машине (или в CI), ходит по API туда, куда вы ему скажете, и делает ресурсы. Нет никакого облачного сервера HashiCorp, куда бы вы слали команды.

Есть локальный режим (для одного человека или учёбы):

terraform init # скачать плагины провайдеров
terraform plan # посмотреть, что будет сделано
terraform apply # сделать это
terraform destroy # всё удалить

Разбор:

  • terraform init — скачивает провайдеры и настраивает backend для каталога.
  • terraform plan — diff "текущее состояние ↔ желаемый HCL" без изменений в облаке.
  • terraform apply — применяет план (создание/изменение/удаление ресурсов).
  • terraform destroy — удаляет все ресурсы, описанные в конфигурации и учтённые в state.
  • Локальный tfstate рядом с кодом подходит для учёбы; в команде state выносят в S3 с блокировкой.

Файл состояния terraform.tfstate лежит рядом с кодом. Так делают новички.

А есть удалённое состояние, для команды. Файл состояния хранится в общем месте (S3 bucket, GCS bucket, Consul, PostgreSQL, HTTP backend). Тогда все члены команды видят одно и то же состояние, и Terraform не даст двоим одновременно применить изменения.

# backend.tf
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "production/network/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-locks"
}
}

Разбор:

  • Блок terraform { backend "s3" { ... } } — удалённое хранение state в бакете S3.
  • key — путь к файлу state для конкретного стека (например, сеть prod).
  • dynamodb_table — таблица для блокировки: два apply одновременно не испортят state.
  • region — регион AWS для бакета и DynamoDB.

Тогда, при выполнении команды apply Terraform:

  • Берёт блокировку в DynamoDB
  • Загружает текущее состояние из S3
  • Вычисляет изменения
  • Применяет через API
  • Сохраняет новое состояние обратно в S3
  • Отпускает блокировку

Всё идёт через CI:

Git push в main


GitLab CI / GitHub Actions / Jenkins

├── terraform init
├── terraform plan -out=tfplan
├── (человек ревьюит план в pull request)
├── terraform apply tfplan
└── записывает состояние обратно в S3

Разбор:

  • Git push — триггер pipeline; изменения IaC проходят review как код приложения.
  • terraform plan -out=tfplan — артефакт плана для согласования в PR.
  • Ревью человеком — gate перед apply в shared-аккаунте.
  • terraform apply tfplan — применяется ровно согласованный план, без сюрпризов.
  • Обновление state в S3 — единый источник правды для всей команды.

Минимальные правила безопасной работы с Terraform в команде:

  • plan выполняется в каждом pull request, apply только после ревью.
  • State хранится удалённо и с блокировкой.
  • В main не допускаются ручные apply с локальной машины без runbook.
  • Любое изменение провайдера или модуля фиксируется по версии и проходит тест в staging.

Архитектурные принципы

Декларативный стиль описания

Конфигурации Terraform написаны на языке HCL (HashiCorp Configuration Language) или в формате JSON. В них пользователь указывает желаемое состояние системы, а не последовательность команд для его достижения. Например, вместо "создай виртуальную машину, затем настрой сеть, потом подключи диск" пишется: "должна существовать виртуальная машина с такими параметрами, сетевой интерфейс с такими настройками и диск такого объёма". Terraform сам определяет порядок действий на основе зависимостей.

Пример простого описания:

resource "aws_instance" "web" {
ami = "ami-0c02fb55956c7d316"
instance_type = "t3.micro"
}

Разбор:

  • resource "aws_instance" "web" — объявление ресурса типа EC2 с локальным именем web в state.
  • ami — идентификатор образа ОС в AWS.
  • instance_type — размер ВМ (t3.micro — малый burstable инстанс).
  • Terraform сравнит блок с state и API: создаст, изменит тип или удалит при destroy.

Этот блок говорит: "в облаке AWS должна существовать EC2-инстанция с указанным AMI и типом". Terraform проверит текущее состояние, сравнит его с описанием и при необходимости создаст, изменит или удалит ресурс.


Иммутабельность и идемпотентность

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

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


Граф зависимостей

При загрузке конфигурации Terraform строит ориентированный ациклический граф, где узлы — это ресурсы, а рёбра — зависимости между ними. Зависимости могут быть явными (через ссылки на атрибуты других ресурсов) или неявными (через использование depends_on).

Пример явной зависимости:

resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}

Разбор:

  • aws_vpc.main — сначала в графе создаётся VPC с CIDR 10.0.0.0/16.
  • aws_subnet.public с vpc_id = aws_vpc.main.id — явная зависимость: подсеть без VPC не создаётся.
  • cidr_block подсети — поддиапазон внутри VPC.
  • Terraform выполняет create/update в порядке DAG, подставляя id VPC после её создания.

Здесь подсеть зависит от VPC, потому что использует её идентификатор. Terraform сначала создаст VPC, дождётся его появления, получит id, а затем создаст подсеть. Такой порядок гарантирует корректность развёртывания.


Состояние (State)

Назначение состояния

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

Без состояния Terraform не смог бы отличить отсутствие ресурса от его наличия. Например, если в конфигурации описан бакет S3, но в облаке его нет, Terraform создаст его. Если бакет уже существует и совпадает с описанием — ничего не произойдёт. Состояние делает этот процесс возможным.


State — приватный API Terraform

Файл terraform.tfstate — JSON-связка между адресами в HCL (aws_instance.web) и реальными ID в облаке (i-0abc…). Вывод terraform plan — расхождение кода на диске и инфраструктуры в мире (по ID из state).

Не правьте state вручную

Формат state меняется между версиями Terraform и предназначен только для движка. Для переноса ресурсов используйте terraform import, блок moved, команды terraform state mv/rm — см. справочник.

В командной работе локальный state создаёт три проблемы:

  • Общий источник правды — у всех должна быть одна актуальная копия state.
  • Блокировка — два одновременных apply без lock могут повредить state.
  • Изоляция окружений — правки staging не должны затрагивать prod.

State нельзя коммитить в Git по тем же причинам, что и секреты: в state часто лежат пароли БД и ключи в открытом виде; клон репозитория размножает копии на каждой машине и CI-runner.


Локальное и удалённое хранение

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

Для решения этой проблемы Terraform поддерживает удалённое хранение состояния через бэкенды. Наиболее распространённые варианты — Amazon S3, Azure Blob Storage, Google Cloud Storage, HashiCorp Consul. Удалённое состояние автоматически блокируется во время применения, предотвращая параллельные изменения.

Пример настройки бэкенда для S3:

terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "prod/web/terraform.tfstate"
region = "us-west-2"
}
}

Разбор:

  • Повтор настройки backend "s3" — state в бакете my-terraform-state-bucket, ключ prod/web/terraform.tfstate.
  • Отдельный key на стек — несколько окружений/компонентов не делят один файл state.
  • region — где физически лежит state (не путать с регионом ресурсов в provider).

Такая конфигурация перенаправляет все операции со состоянием в указанный бакет. Terraform будет использовать его как единственный источник правды о текущей инфраструктуре.

Bootstrap backend (отдельный стек, один раз): справочник Terraform — S3 и DynamoDB.


Workspaces и изоляция окружений

Workspaces (terraform workspace select prod) — несколько state-файлов на одном backend при одном и том же HCL. Удобно для быстрых экспериментов; имя workspace доступно как terraform.workspace.

Для prod чаще выбирают отдельные каталоги и ключи state:

live/
├── stage/services/webserver-cluster/
│ ├── main.tf # module "webserver" { ... }
│ └── backend.tf # key = "stage/webserver/terraform.tfstate"
└── prod/services/webserver-cluster/
├── main.tf
└── backend.tf # key = "prod/webserver/terraform.tfstate"
modules/
└── services/webserver-cluster/ # переиспользуемый код

Разбор:

  • Один модуль в modules/ — stage и prod вызывают его с разными .tfvars.
  • Разный key в backend — изоляция state: apply в stage не трогает prod.
  • Layout modules/ + live/ — распространённая практика в крупных IaC-репозиториях; подробнее в Модули и структура репозитория.

Чувствительность состояния

Файл состояния может содержать конфиденциальные данные — пароли, ключи доступа, IP-адреса. По этой причине его нельзя хранить в открытых репозиториях. Terraform предоставляет механизмы для шифрования состояния (например, через AWS KMS при использовании S3) и исключения чувствительных полей из вывода (sensitive = true в переменных).


Провайдеры

Роль провайдеров

Провайдер — это плагин, который реализует взаимодействие с API конкретной платформы. Terraform не содержит встроенного кода для работы с AWS или Kubernetes. Вместо этого он загружает нужные провайдеры динамически и вызывает их функции для создания, чтения, обновления и удаления ресурсов (операции CRUD).

Каждый провайдер определяет набор ресурсов и источников данных (Данные sources). Ресурсы управляются Terraform, источники данных только читаются. Например, можно запросить информацию о существующем образе ОС без попытки его изменения.


Настройка и версионирование

Провайдеры требуют конфигурации — учётные данные, регион, endpoint и другие параметры. Эта информация передаётся в блоке provider.

Пример:

provider "aws" {
region = "eu-central-1"
profile = "production"
}

Разбор:

  • provider "aws" — подключает AWS-провайдер для всех ресурсов aws_* в каталоге.
  • region — регион API по умолчанию (например eu-central-1).
  • profile — именованный профиль из ~/.aws/credentials (разделение prod/dev на одной машине).

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

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}

Разбор:

  • required_providers — объявляет обязательные плагины и их версии.
  • source = "hashicorp/aws" — адрес модуля в Terraform Registry.
  • version = "~> 5.0" — допустимы патч- и минор-обновления 5.x, но не 6.0 (семантическое ограничение).
  • terraform init скачает версию, зафиксированную в .terraform.lock.hcl.

Это гарантирует, что все участники проекта используют одинаковую версию провайдера, избегая расхождений в поведении.


Модули

Композиция и переиспользование

Модуль — это пакет конфигураций Terraform, который можно использовать как единый блок. Любой каталог с .tf-файлами является модулем, но термин обычно применяют к переиспользуемым компонентам. Модули позволяют инкапсулировать сложные паттерны — например, "полностью настроенная веб-инфраструктура с балансировщиком, группой инстансов и мониторингом".

Модуль принимает входные переменные и возвращает выходные значения. Это делает его похожим на функцию в программировании.

Пример вызова модуля:

module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"

name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-central-1a", "eu-central-1b"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}

Разбор:

  • module "vpc" — вызов переиспользуемого модуля вместо десятков ресурсов вручную.
  • source + version — пин версии модуля из Registry (воспроизводимые сборки).
  • name, cidr, azs, public_subnets — входные параметры модуля (интерфейс "функции").
  • Внутри модуля создаются VPC, подсети, route tables — скрытая сложность инкапсулирована.

Этот вызов использует официальный модуль от сообщества AWS, который создаёт полноценную VPC с подсетями, таблицами маршрутизации, шлюзами и другими компонентами. Без модуля пришлось бы писать десятки блоков вручную.


Иерархия и организация

Проекты на Terraform часто организуются в виде дерева модулей. Корневой модуль вызывает дочерние, те — свои, и так далее. Такая структура упрощает поддержку: изменения в одном компоненте не затрагивают другие, если интерфейс остаётся неизменным.

Хорошая практика — выделять модули по доменным границам — сеть, вычисления, хранилище, безопасность. Это соответствует принципам разделения ответственности и упрощает тестирование.


Жизненный цикл управления

Инициализация

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

terraform init

Разбор:

  • Подготавливает каталог — провайдеры, модули, backend state.
  • Запускают после git clone и при смене required_providers или backend.

Планирование

Команда terraform plan анализирует конфигурацию и текущее состояние, строит план изменений и выводит его в человекочитаемом виде. План показывает, какие ресурсы будут созданы, изменены или удалены, и почему. Это ключевой этап для проверки перед применением.

terraform plan
terraform plan -out=plan.out

Разбор:

  • terraform plan — показывает diff без изменений в облаке.
  • -out=plan.out — сохраняет бинарный план для согласованного apply (типично в CI).
  • В логе видно + create, ~ update, - destroy по каждому ресурсу.

План можно сохранить в файл и передать на утверждение. Это особенно полезно в CI/CD-конвейерах, где человек должен подтвердить изменения до их выполнения.


Применение

Команда terraform apply выполняет ранее сгенерированный план. Она отправляет запросы к API провайдеров, дожидается завершения операций и обновляет файл состояния. Все действия атомарны — либо весь план применяется успешно, либо возвращается ошибка, и состояние остаётся прежним.

terraform apply
terraform apply plan.out

Разбор:

  • terraform apply без файла — интерактивное подтверждение плана (локально).
  • terraform apply plan.out — применяет ровно сохранённый план (без расхождения с ревью).
  • После успеха обновляется terraform.tfstate (локально или в S3).

Если во время применения происходит сбой (например, недоступен API), Terraform помечает ресурс как "зависший" и предлагает восстановить состояние вручную. Это редкая, но важная ситуация, требующая внимания оператора.


Уничтожение

Команда terraform destroy удаляет все управляемые ресурсы. Она строит план, аналогичный plan, но с единственной целью — полное уничтожение. Эта команда используется при завершении проекта или очистке тестовых окружений.

terraform destroy

Разбор:

  • Удаляет все ресурсы, управляемые текущей конфигурацией и записанные в state.
  • Используют для teardown dev/staging; в prod — с prevent_destroy и отдельным approve.
  • Необратимо для данных без бэкапов вне Terraform.

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


Переменные и выходные значения

Гибкость конфигураций

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

Объявление переменной:

variable "instance_count" {
description = "Number of EC2 instances to launch"
type = number
default = 2
}

Разбор:

  • variable — входной параметр модуля/корня.
  • type = number — строгая типизация HCL.
  • default = 2 — значение, если не передали в .tfvars или -var.

Использование:

resource "aws_instance" "app" {
count = var.instance_count
# ...
}

Разбор:

  • count = var.instance_count — создаёт N однотипных ресурсов (индексы aws_instance.app[0]…).
  • var.instance_count — ссылка на объявленную переменную.
  • Один HCL-шаблон для dev (count=1) и prod (count=10) через разные .tfvars.

Такой подход делает конфигурацию универсальной — одну и ту же структуру можно использовать в dev, staging и prod, меняя только значения переменных.


Типизация и валидация

Terraform поддерживает строгую типизацию переменных — строки, числа, булевы значения, списки, карты, объекты, кортежи. Можно задавать пользовательские типы и правила валидации.

Пример валидации:

variable "region" {
type = string
validation {
condition = contains(["us-east-1", "eu-west-1", "ap-southeast-1"], var.region)
error_message = "Region must be one of the approved locations."
}
}

Разбор:

  • validation { condition = ... } — проверка до plan; неверный регион остановит Terraform.
  • contains([...], var.region) — регион должен быть из белого списка.
  • error_message — текст ошибки для CI и разработчика.

Это предотвращает ошибки на раннем этапе и обеспечивает соответствие политике безопасности.


Выходные значения

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

Пример:

output "vpc_id" {
value = aws_vpc.main.id
}

output "public_subnet_ids" {
value = aws_subnet.public[*].id
}

Разбор:

  • output — экспорт значений после apply (в консоль и для -json).
  • vpc_id — ID VPC для передачи в другой модуль или Ansible.
  • aws_subnet.public[*].id — splat: список ID всех подсетей из ресурса с count.

Эти значения появятся в выводе apply и могут использоваться внешними системами, например, для настройки CI/CD-пайплайнов.


Безопасность и управление доступом

Учётные данные и секреты

Terraform требует учётных данных для взаимодействия с API провайдеров. Эти данные могут включать ключи доступа, токены, сертификаты и другие формы аутентификации. Хранение таких данных в открытом виде в конфигурационных файлах недопустимо. Terraform предоставляет несколько механизмов для безопасной передачи секретов:

  • Переменные окружения: большинство провайдеров автоматически читают значения из переменных вроде AWS_ACCESS_KEY_ID, AZURE_CLIENT_SECRET и подобных.
  • Внешние источники — можно использовать специализированные инструменты — HashiCorp Vault, AWS Secrets Manager, Azure Key Vault — и запрашивать секреты через Данные-блоки или внешние модули.
  • Локальные файлы с исключением из контроля версий: например, secrets.auto.tfvars добавляется в .gitignore.

Пример безопасного использования:

provider "aws" {
region = "us-east-1"
# credentials не указаны явно — будут взяты из ~/.aws/credentials или ENV
}

Разбор:

  • Провайдер без access_key в HCL — ключи из переменных окружения или IAM role на CI runner.
  • region — единственный явный параметр в примере.
  • Секреты не попадают в Git и в plan-логи в открытом виде.

Явное указание секретов в коде считается грубой ошибкой, даже если репозиторий приватный.


Принцип минимальных привилегий

Учётные данные, используемые Terraform, должны обладать только теми правами, которые необходимы для выполнения задач. Например, если конфигурация создаёт только EC2-инстансы и VPC, то IAM-роль не должна иметь разрешений на удаление S3-бакетов или изменение IAM-политик. Это снижает потенциальный ущерб при компрометации состояния или плана.

Провайдеры часто предоставляют возможность ограничить действия через политики. В AWS это делается через IAM; в Azure — через RBAC и управляемые идентификаторы. Terraform сам по себе не проверяет полномочия — он лишь передаёт запросы, поэтому ответственность за безопасность лежит на администраторе.


Защита состояния

Файл состояния может содержать чувствительные данные — IP-адреса, DNS-имена, пароли баз данных (если они не помечены как sensitive). Чтобы минимизировать риски:

  • Используется шифрование на уровне бэкенда (например, AWS KMS для S3).
  • Включается опция encrypt = true в конфигурации бэкенда.
  • Все чувствительные выходные значения помечаются как sensitive = true, что скрывает их из вывода apply и plan.

Пример:

output "db_password" {
value = aws_db_instance.main.password
sensitive = true
}

Разбор:

  • output с паролем БД — удобно для передачи в CI, но опасно в логах.
  • sensitive = true — Terraform скрывает значение в выводе plan/apply.
  • Пароль всё равно может быть в tfstate — state шифруют и ограничивают доступ.

Это не предотвращает сохранение значения в состоянии, но защищает от случайного раскрытия в логах.


Расширенные возможности языка HCL

Локальные значения

Локальные значения (locals) позволяют вычислять производные данные внутри модуля без необходимости объявлять их как переменные или выходы. Это упрощает чтение и поддержку сложных выражений.

Пример:

locals {
common_tags = {
Environment = var.env
Owner = "dev-team"
ManagedBy = "terraform"
}
}

resource "aws_instance" "web" {
tags = merge(local.common_tags, { Name = "web-server" })
}

Разбор:

  • locals — вычисляемые значения внутри модуля (не inputs/outputs).
  • common_tags — единый набор меток для compliance и cost allocation.
  • merge(local.common_tags, { Name = ... }) — объединение карт тегов для конкретного ресурса.

Такой подход централизует общую логику и избегает дублирования.


Условные выражения и циклы

HCL поддерживает условную логику через тернарный оператор и функции вроде can(), try(). Циклы реализуются через мета-аргумент for_each и count.

Пример с for_each:

Код ITЗагрузка примера кода…

Разбор:

  • for_each = var.subnets — по одному ресурсу на ключ карты (public_a, public_b).
  • each.value — CIDR подсети; each.key — имя/AZ в примере.
  • Добавление ключа в var.subnets создаёт новую подсеть без копипасты блоков resource.

Этот подход масштабируется: добавление нового элемента в subnets автоматически создаст новую подсеть без изменения структуры кода.


count и for_each — когда что

МеханизмКогда использоватьПодводный камень
count = NN однотипных ресурсов по индексуВставка элемента в середину списка сдвигает индексы → Terraform пересоздаёт ресурсы
for_each = map/setРесурсы с стабильным ключом (имя пользователя, AZ)Ключи должны быть известны на этапе plan
for в localsВычислить список/map до resource

Пример: три IAM-пользователя — предпочтительнее map, а не count:

variable "user_names" {
type = set(string)
default = ["neo", "trinity", "morpheus"]
}

resource "aws_iam_user" "example" {
for_each = var.user_names
name = each.key
}

Разбор:

  • for_each = var.user_names — адрес в state aws_iam_user.example["neo"], не зависит от порядка в set.
  • Добавление "smith" создаёт одного пользователя; удаление ключа из set — destroy только его.
  • С count переименование или reorder списка часто даёт -/+ (destroy + create) в plan.

Развёртывание без простоя — create_before_destroy

Некоторые атрибуты ресурса immutable (тип EC2, subnet CIDR). Terraform заменяет ресурс: сначала destroy, потом create — с простоем. Блок lifecycle меняет порядок:

resource "aws_instance" "web" {
ami = var.ami
instance_type = var.instance_type

lifecycle {
create_before_destroy = true
}
}

Разбор:

  • Сначала создаётся новый инстанс с новым AMI/типом, затем удаляется старый.
  • Работает, когда зависимости (ALB, ASG) допускают временное дублирование.
  • Для ASG/ALB zero-downtime нужна согласованная схема (rolling update, health checks) — один lifecycle на ресурсе не гарантирует бесшовность всего стека.

Функции и выражения

HCL включает богатую библиотеку встроенных функций — работа со строками (lower, replace), списками (concat, distinct), картами (lookup, merge), преобразование типов (jsonencode, yamldecode) и другие. Это позволяет строить динамические конфигурации без внешних скриптов.

Пример:

locals {
instance_names = [for i in range(var.instance_count) : "app-${i}"]
}

Разбор:

  • List comprehension в HCL: [for i in range(n) : expr].
  • range(var.instance_count) — 0..n-1 для имён app-0, app-1, …
  • Результат — список строк в local.instance_names для count или for_each.

Такие конструкции делают код гибким и адаптивным к изменяющимся требованиям.


Интеграция с CI/CD и автоматизация

Рабочие процессы

Terraform хорошо встраивается в CI/CD (GitHub Actions, GitLab CI, Jenkins). Типичный пайплайн:

terraform init
terraform validate
terraform fmt -check
terraform plan -out=tfplan
# утверждение плана
terraform apply tfplan

Разбор:

  • validate — синтаксис и ссылки в HCL без облака.
  • fmt -check — стиль форматирования (часто gate в CI).
  • plan -out=tfplan → approve → apply tfplan — безопасный IaC-цикл в команде.

Блокировки и совместная работа

При использовании удалённого состояния Terraform автоматически блокирует файл состояния во время apply. Это предотвращает одновременные изменения от нескольких пользователей или пайплайнов. Если блокировка не поддерживается бэкендом (например, при использовании локального файла), возможны конфликты и повреждение состояния.

Некоторые бэкенды, такие как Consul или DynamoDB, обеспечивают надёжную блокировку через атомарные операции. Это критически важно в production-средах.


Мониторинг и уведомления

Результаты plan и apply можно направлять в системы мониторинга — Slack, Microsoft Teams, Email, Prometheus. Это позволяет команде отслеживать изменения в реальном времени. Например, после каждого apply в канал DevOps отправляется сообщение с перечнем изменённых ресурсов и ссылкой на коммит.


Версионирование и эволюция инфраструктуры

Управление версиями модулей

Модули могут быть опубликованы в реестрах (Terraform Registry, внутренние Git-репозитории) и использоваться с указанием конкретной версии. Это позволяет:

  • Изолировать изменения: обновление модуля в одном проекте не затрагивает другие.
  • Проводить тестирование: новая версия проверяется в staging перед переходом в prod.
  • Соблюдать семантическое версионирование: ~> 2.1 означает "любая версия >= 2.1.0, но < 3.0.0".

Пример:

module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "20.0.0"
# ...
}

Разбор:

  • Пин version = "20.0.0" модуля EKS — фиксированное поведение кластера.
  • Обновление модуля — осознанный PR с plan на staging, не случайный init -upgrade в проде.

Это гарантирует стабильность и предсказуемость.


Миграция и рефакторинг

Со временем инфраструктура меняется: появляются новые требования, устаревают старые компоненты. Terraform поддерживает миграцию через:

  • moved-блоки: позволяют переименовать или переместить ресурс без его уничтожения.
  • Импорт существующих ресурсов — связать уже созданный объект с конфигурацией:
terraform import aws_instance.web i-0abc123def456

Разбор:

  • terraform import — привязывает существующий ресурс в облаке к адресу в state (aws_instance.web).

  • ID i-0abc123def456 — реальный идентификатор EC2 из AWS.

  • После import следующий plan покажет только drift относительно HCL.

  • Ручное редактирование состояния (с осторожностью):

terraform state mv aws_instance.old_name aws_instance.new_name
terraform state rm aws_instance.orphan

Разбор:

  • state mv — переименование в state без destroy/create в облаке.
  • state rm — убрать "осиротевшую" запись из state (ресурс в облаке остаётся — осторожно).
  • Используют при рефакторинге имён ресурсов, если moved недостаточно.

Пример moved:

moved {
from = aws_instance.old_name
to = aws_instance.new_name
}

Разбор:

  • Блок moved (Terraform 1.1+) — декларативный перенос адреса ресурса в state.
  • plan не предложит пересоздать EC2 при смене имени в HCL.
  • Предпочтительнее ручного state mv — видно в коде и в review.

После этого plan покажет, что ресурс не будет удалён и создан заново, а просто переименован в состоянии.


Поддержка мультиоблачных и гибридных сред

Terraform одинаково эффективно управляет ресурсами в нескольких облаках одновременно. Один и тот же рабочий процесс применяется к AWS, Azure, GCP, Yandex Cloud, OpenStack и другим платформам. Это особенно ценно для организаций, использующих стратегию multi-cloud или hybrid-cloud.

Пример:

Код ITЗагрузка примера кода…

Разбор:

  • Два блока provider — один root-модуль управляет AWS и Azure одновременно.
  • aws_s3_bucket — бакет логов в AWS; azurerm_storage_account — учётная запись хранения в Azure.
  • У каждого провайдера свои credentials и регион; plan/apply ходят в оба API из одного pipeline.

Такая конфигурация создаёт ресурсы в двух разных облаках, но управляется единым инструментом. Это упрощает обучение, стандартизацию и аудит.


Экосистема и сообщество

Официальные и сторонние модули

HashiCorp поддерживает набор официальных провайдеров и модулей, прошедших внутреннюю проверку. Они доступны в Terraform Registry — централизованном каталоге, где можно найти готовые решения для AWS, Azure, Google Cloud, Kubernetes и других платформ. Эти модули часто соответствуют лучшим практикам — используют версионирование, имеют документацию, примеры и тесты.

Сообщество также создаёт тысячи сторонних модулей. Они могут быть полезны для нишевых задач или новых технологий, но требуют осторожности: не все из них поддерживаются регулярно, некоторые содержат уязвимости или устаревшие подходы. Перед использованием рекомендуется проверить:

  • Дату последнего обновления
  • Количество звёзд и форков на GitHub
  • Наличие issue-трекера и активность автора
  • Соответствие текущей версии Terraform

Пример вызова популярного модуля:

module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"

name = "production-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}

Разбор:

  • Повтор вызова community-модуля VPC с пином version = "5.0.0".
  • production-vpc и подсети в us-east-1 — типичный prod-стек сети из Registry вместо ручного HCL.
  • Обновление модуля — отдельный PR с diff плана по десяткам вложенных ресурсов.

Такой модуль заменяет десятки строк ручной конфигурации и гарантирует соответствие рекомендациям AWS.


Инструменты расширения

Вокруг Terraform сложилась богатая экосистема инструментов:

  • Terragrunt — надстройка для управления повторяющимися конфигурациями через DRY-принцип (Don’t Repeat Yourself). Позволяет выносить общие настройки вверх по иерархии каталогов.
  • TFLint — линтер, проверяющий стиль, безопасность и соответствие политике.
  • TFSec — сканер уязвимостей в конфигурациях (например, открытые порты, отсутствие шифрования).
  • Checkov — инструмент от Bridgecrew для проверки соответствия стандартам безопасности и compliance (CIS, PCI-DSS, HIPAA).
  • Atlantis — система автоматизации plan и apply через pull request в Git.

Эти инструменты интегрируются в CI/CD и повышают надёжность развёртываний.


Ограничения и сценарии, неподходящие для Terraform

Императивные задачи

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

Terraform управляет состоянием, а не процессами. Он не следит за работоспособностью ресурсов после их создания. Для этого нужны дополнительные инструменты — Prometheus, Datadog, CloudWatch.


Частые изменения состояния

Если инфраструктура меняется каждые несколько минут (например, в средах с высокой динамикой, таких как serverless-функции с переменной нагрузкой), использование Terraform может привести к конфликтам состояний и задержкам. В таких случаях лучше применять облачные нативные решения — AWS SAM, Azure Bicep, Google Cloud Deployment Manager.


Отсутствие обратной связи

Terraform не получает информацию о том, что произошло с ресурсом после его создания. Если кто-то вручную изменит параметры EC2-инстанса в консоли AWS, Terraform не узнает об этом до следующего plan. При следующем применении он вернёт ресурс к описанному состоянию, что может нарушить работу системы. Это называется "дрейфом конфигурации" (configuration drift).

Для борьбы с дрейфом используются:

  • Запрет ручных изменений через IAM-политики
  • Регулярные plan в CI/CD
  • Инструменты вроде Driftctl, которые сравнивают состояние в облаке и в Terraform

Сравнение с другими инструментами управления инфраструктурой

Ansible

Ansible — императивный инструмент, ориентированный на конфигурацию серверов и приложений. Он выполняет команды по порядку — "установи пакет, запусти службу, скопируй файл". Terraform декларативен и управляет самими ресурсами: "должен существовать сервер с этим ПО".

Ansible хорошо работает внутри уже созданных машин, Terraform — на уровне их создания. Часто они используются вместе: Terraform поднимает инфраструктуру, Ansible настраивает софт.


CloudFormation / ARM / Bicep

Эти инструменты являются нативными для конкретных облаков — AWS CloudFormation, Azure Resource Manager (ARM), Google Deployment Manager. Они тесно интегрированы с API и поддерживают все новые функции сразу. Однако они не переносимы между облаками.

Terraform обеспечивает унификацию: один язык, один рабочий процесс для всех платформ. Это особенно ценно в multi-cloud-средах.


Pulumi

Pulumi использует настоящие языки программирования (Python, TypeScript, Go) вместо HCL. Это даёт больше гибкости — можно писать циклы, классы, использовать библиотеки. Однако это усложняет чтение и аудит: код становится менее декларативным и более процедурным.

Terraform остаётся проще для команд, где не все участники — разработчики. HCL легко читается даже системными администраторами без опыта программирования.


Поддержка и развитие

Terraform активно развивается компанией HashiCorp. Новые версии выходят регулярно и включают:

  • Поддержку новых API облачных провайдеров
  • Улучшения производительности графа зависимостей
  • Расширение возможностей языка HCL
  • Усиление безопасности (например, встроенный анализ чувствительных данных)

Проект имеет открытый исходный код (Mozilla Public License 2.0), что позволяет сообществу вносить вклад, проверять код и форкать при необходимости. В 2023 году HashiCorp изменила лицензию на Business Source License (BSL), но основной функционал остаётся свободным для большинства пользователей.

Развитие движется в сторону:

  • Более глубокой интеграции с CI/CD
  • Улучшения работы с большими конфигурациями (модульные зависимости, частичное применение)
  • Поддержки policy-as-code через Sentinel (в Enterprise-версии) или Open Policy Agent (в open-source)

Эти направления делают Terraform всё более зрелым инструментом для enterprise-сред.


См. также


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.