Инфраструктура как код: Terraform в Yandex Cloud

Курс посвящен переходу от ручного управления облачными ресурсами к декларативному подходу IaC. Вы освоите жизненный цикл инфраструктуры, научитесь автоматизировать развертывание сетей и виртуальных машин в Yandex Cloud и подготовите базу для управления конфигурациями через Ansible.

1. От императивного хаоса к декларативному порядку: философия IaC и архитектура Terraform

От императивного хаоса к декларативному порядку: философия IaC и архитектура Terraform

Пятница, вечер. Команда выкатывает новый релиз микросервисного приложения. Контейнеры собраны, тесты пройдены, пайплайн зелёный. Но при попытке запуска сервисы не могут подключиться к базе данных. Начинается отладка, которая спустя три часа приводит к тривиальной причине: два месяца назад системный администратор вручную изменил правила файрвола в облачной консоли для проведения тестов и забыл вернуть их обратно. Конфигурация тестовой среды разъехалась с production-средой. Этот сценарий — классический симптом управления инфраструктурой вручную, проблемы, которую решает подход Infrastructure as Code (IaC).

До массового внедрения IaC инфраструктура создавалась двумя способами: через графический интерфейс (ClickOps) или с помощью императивных скриптов на Bash/Python.

ClickOps отлично подходит для изучения облачной платформы. Создать первую виртуальную машину, прокликав мастера настройки в консоли Yandex Cloud, — это быстро и наглядно. Но когда требуется развернуть тридцать одинаковых машин, настроить балансировщик, распределить сети по зонам доступности и повторить всё это для сред dev, stage и prod, ручной труд становится источником критических ошибок. Человеческий фактор неизбежно приводит к «дрейфу конфигурации» (configuration drift) — состоянию, при котором реальная инфраструктура перестаёт соответствовать тому, что описано в документации.

Попытка автоматизировать ClickOps исторически привела к созданию императивных скриптов. Инженер пишет Bash-скрипт, который вызывает CLI-утилиты облачного провайдера: создать сеть, подождать, создать подсеть, подождать, запустить виртуальную машину.

Проблема императивного подхода кроется в обработке ошибок и управлении состоянием. Если скрипт упадет на середине (например, из-за сетевого таймаута при создании виртуальной машины), его нельзя просто запустить заново. При повторном запуске скрипт попытается снова создать сеть и подсеть, которые уже существуют, что приведет к конфликту имен и новой ошибке. Инженеру приходится писать сложную логику проверок: «если сеть X не существует, создай её, иначе пропусти». С ростом инфраструктуры такой код превращается в нечитаемый монолит, поддерживать который может только его автор.

Декларативная парадигма и идемпотентность

Terraform кардинально меняет подход, переходя от вопроса «Как сделать?» к вопросу «Что должно получиться?». Это суть декларативной парадигмы.

Вместо написания последовательности команд, инженер описывает желаемое конечное состояние системы. Terraform сам вычисляет разницу между тем, что есть сейчас, и тем, что должно быть, а затем генерирует минимально необходимый набор команд (API-вызовов) для приведения реальности к желаемому виду.

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

| Характеристика | Императивный подход (Bash, Python) | Декларативный подход (Terraform) | | :--- | :--- | :--- | | Фокус | Шаги и алгоритм действий | Желаемое конечное состояние | | Сложность кода | Растёт экспоненциально из-за проверок состояний | Растёт линейно, пропорционально количеству ресурсов | | Обработка ошибок | Требует ручной реализации try/catch и откатов | Встроена в ядро инструмента (остановка или откат графа) | | Обновление | Нужно писать отдельный скрипт для каждого изменения | Достаточно изменить один параметр в описании состояния |

Фундаментальное свойство декларативного подхода — идемпотентность. В математике и информатике это свойство операции, при котором многократное применение дает тот же результат, что и однократное.

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

Архитектура Terraform: Ядро, Провайдеры и Состояние

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

!Архитектура Terraform: взаимодействие Core, Providers и State

Terraform Core (Ядро)

Ядро — это скомпилированный бинарный файл, написанный на языке Go. Core ничего не знает о том, как создавать виртуальные машины в Yandex Cloud или контейнеры в Docker. Его задачи строго ограничены:
  • Чтение и парсинг конфигурационных файлов, написанных на языке HCL (HashiCorp Configuration Language).
  • Чтение текущего состояния из файла State.
  • Построение графа зависимостей между ресурсами.
  • Вычисление разницы (diff) между желаемым и текущим состоянием.
  • Передача команд соответствующим плагинам.
  • Providers (Провайдеры)

    Провайдеры — это плагины, которые переводят абстрактные команды от Core в конкретные API-вызовы целевой платформы. Существуют официальные провайдеры для AWS, Google Cloud, Yandex Cloud, Kubernetes, GitHub и сотен других систем.

    Когда вы описываете ресурс yandex_compute_instance, ядро Terraform понимает лишь то, что нужно обратиться к провайдеру Yandex, передать ему параметры (количество ядер, объем памяти) и попросить выполнить операцию создания. Вся логика аутентификации, формирования HTTP-запросов и обработки ответов от Yandex Cloud API зашита внутри провайдера. Благодаря такой модульности, с помощью одного инструмента можно управлять инфраструктурой в разных облаках, настраивать мониторинг в Datadog и управлять репозиториями в GitLab.

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

    Файл состояния (terraform.tfstate) — это самый важный и потенциально опасный компонент в архитектуре. Это JSON-файл, в котором Terraform хранит карту соответствия между ресурсами, описанными в коде, и реальными объектами в облаке.

    Зачем нужен State, если можно просто запросить текущее состояние напрямую из облака? Во-первых, производительность. В крупных инфраструктурах могут быть десятки тысяч ресурсов. Опрос API облака для каждого из них занял бы часы. State работает как кэш. Во-вторых, метаданные. Terraform должен знать, какие именно ресурсы были созданы им, а какие были созданы вручную другими пользователями. Если в облаке есть сеть default, но её нет в State-файле, Terraform не будет пытаться её удалить или изменить, так как не считает её зоной своей ответственности. В-третьих, разрешение зависимостей. В State хранятся внутренние идентификаторы объектов (например, subnet_id), которые облако присваивает ресурсам после создания. Эти ID необходимы для связывания ресурсов между собой при последующих запусках.

    Граф зависимостей (Directed Acyclic Graph)

    Одним из главных архитектурных преимуществ Terraform является то, как он определяет порядок создания ресурсов. В императивном скрипте ресурсы создаются строго сверху вниз, строка за строкой. Terraform работает иначе: он строит направленный ациклический граф (DAG).

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

    !Построение и обход графа зависимостей

    Построение графа решает две задачи:

  • Топологическая сортировка: Terraform автоматически выстраивает правильную последовательность действий. Инженеру не нужно думать о том, в каком порядке располагать блоки кода в файле. Можно описать виртуальную машину в первой строке, а подсеть — в сотой. Terraform сам разберется, что первично.
  • Максимальная параллелизация: Если граф показывает, что три виртуальные машины зависят от одной подсети, но не зависят друг от друга, Terraform после создания подсети запустит создание всех трех машин одновременно, в параллельных потоках. Это радикально сокращает время развертывания инфраструктуры.
  • Если в конфигурации допущена логическая ошибка, приводящая к циклической зависимости (ресурс А зависит от Б, Б зависит от В, а В зависит от А), ядро Terraform обнаружит это на этапе построения графа и выдаст ошибку еще до выполнения каких-либо действий в облаке.

    Жизненный цикл изменений: Workflow

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

    Инициализация (terraform init)

    Первый шаг в любом новом проекте. При запуске этой команды Terraform анализирует конфигурационные файлы, определяет, какие провайдеры требуются (например, Yandex Cloud), скачивает их бинарные файлы из глобального реестра (Terraform Registry) и инициализирует рабочий каталог. Также на этом этапе настраивается подключение к файлу состояния, если он хранится удаленно. Без инициализации ядро не сможет взаимодействовать с облаком.

    Планирование (terraform plan)

    Это киллер-фича Terraform. Команда plan выполняет «сухой прогон» (dry run). Ядро обновляет информацию о текущем состоянии из облака, сравнивает её с локальным кодом и выводит на экран подробный отчет о том, что именно будет сделано.

    В отчете используются цветовые маркеры и символы:

  • Зеленый + означает, что ресурс будет создан.
  • Желтый ~ означает, что существующий ресурс будет изменен (in-place update).
  • Красный - означает, что ресурс будет удален.
  • Красно-зеленый -/+ означает деструктивное изменение: ресурс невозможно просто обновить, он будет удален и создан заново (например, при изменении зоны доступности диска).
  • На этом этапе не происходит никаких реальных изменений. Инженер может (и должен) внимательно изучить план, чтобы убедиться, что опечатка в коде не приведет к удалению production-базы данных.

    Применение (terraform apply)

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

    HCL: Язык конфигурации

    Инструмент использует собственный язык HCL (HashiCorp Configuration Language). В отличие от JSON, который строг и перегружен кавычками, или YAML, который чувствителен к отступам и часто приводит к ошибкам парсинга, HCL спроектирован специально для чтения и написания человеком.

    Он поддерживает комментарии, переменные, встроенные функции (например, для кодирования в Base64 или работы с IP-адресами) и базовую логику. Синтаксис строится на блоках. Каждый блок имеет тип (например, resource), подтип (например, yandex_vpc_network) и локальное имя, которое используется только внутри кода для ссылок.

    В этом примере resource указывает Terraform, что мы хотим управлять компонентом инфраструктуры. yandex_vpc_network — это конкретный тип ресурса, понятный провайдеру Yandex Cloud. main_network — это идентификатор, по которому мы сможем сослаться на эту сеть из других блоков кода. Внутри фигурных скобок задаются аргументы, определяющие свойства ресурса в облаке.

    Переход к Infrastructure as Code — это не просто смена инструмента, это смена культуры. Инфраструктура перестает быть набором серверов, настроенных вручную неизвестно кем и когда. Она становится версионируемым, тестируемым и рецензируемым программным продуктом. Изменения вносятся через Pull Requests, проверяются автоматическими линтерами и применяются через CI/CD пайплайны. Terraform, благодаря своей модульной архитектуре и строгому декларативному подходу, выступает фундаментом этого процесса, гарантируя, что реальность в облаке всегда соответствует коду в репозитории.

    2. Провайдеры и ресурсы: инициализация проекта и аутентификация в Yandex Cloud

    Провайдеры и ресурсы: инициализация проекта и аутентификация в Yandex Cloud

    В 2020 году один из разработчиков крупного финтех-стартапа случайно закоммитил в публичный репозиторий на GitHub конфигурационный файл с жестко зашитым OAuth-токеном от облачного провайдера. Спустя 14 минут автоматизированные боты-парсеры нашли этот токен, авторизовались в облаке с правами администратора и развернули кластер из сорока самых мощных виртуальных машин с GPU для майнинга криптовалюты. К утру, когда служба безопасности заметила аномалию, счет за облачные услуги превысил 60 000 долларов. Этот инцидент стал классическим примером того, почему управление инфраструктурой как кодом начинается не с написания ресурсов, а с грамотного выстраивания границ доступа, понимания облачной иерархии и безопасной передачи учетных данных провайдеру.

    Иерархия ресурсов Yandex Cloud: где живут ваши данные

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

    Верхний уровень — Организация (Organization). Это сущность, объединяющая всех пользователей, платежные аккаунты и глобальные настройки безопасности компании. В рамках небольших личных проектов организация может создаваться автоматически, но в корпоративной среде это корневой узел управления.

    Второй уровень — Облако (Cloud). Внутри одной организации может быть несколько облаков (например, production-cloud, development-cloud). Облако выступает границей биллинга: к нему привязывается платежный аккаунт. Все ресурсы внутри одного облака оплачиваются с единого счета. Каждое облако имеет уникальный строковый идентификатор, который потребуется Terraform.

    Третий, самый гранулярный уровень — Каталог (Folder). Это логическая папка внутри облака, в которой непосредственно создаются сети, диски, виртуальные машины и кластеры баз данных. Каталоги служат для изоляции проектов или сред внутри одного облака. Например, в development-cloud могут быть каталоги frontend-dev, backend-dev и analytics-testing. Роли и права доступа (IAM-политики) чаще всего назначаются именно на уровне каталога.

    !Иерархия ресурсов Yandex Cloud и привязка сервисного аккаунта

    При работе с Terraform минимально необходимыми координатами являются идентификатор облака (cloud_id) и идентификатор каталога (folder_id). Без них провайдер не поймет, в каком именно логическом сегменте нужно разворачивать инфраструктуру.

    Блок terraform и настройка провайдера

    Связь между ядром Terraform и API Yandex Cloud обеспечивает специализированный плагин — провайдер. Поскольку Terraform поддерживает сотни различных облаков и сервисов, ядро по умолчанию не содержит в себе логики работы ни с одним из них. Провайдер необходимо явно запросить и сконфигурировать.

    Конфигурация начинается с корневого блока terraform, в котором описываются мета-требования к самому проекту:

    Параметр required_version защищает команду от случайного запуска кода старой версией бинарного файла Terraform, что могло бы привести к повреждению State-файла из-за несовместимости форматов.

    Блок required_providers указывает, откуда скачивать плагин. Строка source = "yandex-cloud/yandex" — это путь в публичном реестре (Terraform Registry). Особое внимание стоит уделить версии: ~> 0.100.0. Оператор ~> (пессимистичный оператор ограничения) разрешает скачивать обновления только на уровне патчей. То есть версия 0.100.1 или 0.100.9 будет установлена, а 0.101.0 (где могут быть обратно несовместимые изменения API) — отвергнута. Жесткая фиксация минорной версии провайдера — критическое правило для production-инфраструктуры, защищающее от внезапных поломок при выходе новых релизов плагина.

    После объявления требований необходимо настроить сам провайдер. Блок provider принимает параметры аутентификации и координаты развертывания:

    Параметр zone задает зону доступности по умолчанию. Yandex Cloud физически распределен по нескольким дата-центрам (зонам), таким как ru-central1-a, ru-central1-b и ru-central1-d. Если при создании подсети или виртуальной машины зона не будет указана явно, провайдер использует значение из этого блока.

    Стратегии аутентификации: от локальной разработки до CI/CD

    Самая большая ошибка при написании IaC — хранение секретов в текстовых файлах конфигурации. Блок provider, приведенный выше, синтаксически корректен, но архитектурно уязвим. Terraform и провайдер Yandex Cloud поддерживают несколько механизмов аутентификации, каждый из которых подходит для своей фазы жизненного цикла проекта.

    OAuth-токен (пользовательский доступ)

    OAuth-токен выдается конкретному физическому пользователю (Яндекс ID). Это длинная строка, которая предоставляет скриптам те же права, что есть у человека в веб-консоли.

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

    Для локальной разработки OAuth-токен допустим, но передавать его следует исключительно через переменные окружения, чтобы избежать попадания в систему контроля версий (Git). Провайдер Yandex Cloud автоматически считывает переменную YC_TOKEN:

    При таком подходе блок провайдера в файле main.tf становится абсолютно безопасным и лаконичным:

    IAM-токен и утилита yc

    IAM-токен (Identity and Access Management) — это короткоживущий криптографический ключ, время жизни которого не превышает 12 часов. По истечении этого времени токен превращается в бесполезный набор символов.

    !Динамика времени жизни токенов аутентификации

    Работать с IAM-токенами вручную неудобно из-за необходимости их постоянного обновления. Однако официальная консольная утилита Yandex Cloud (yc CLI) берет эту работу на себя. Если на рабочей станции разработчика установлен и авторизован yc, Terraform может использовать его профиль для прозрачной генерации свежих IAM-токенов при каждом запуске terraform plan или apply.

    Для этого в конфигурации провайдера не нужно указывать token, а переменная окружения YC_TOKEN должна быть пустой. Terraform обратится к локальному профилю yc, получит актуальный IAM-токен и выполнит операции. Это самый безопасный и удобный метод для локальной работы инженера.

    Сервисные аккаунты и Authorized Keys (стандарт для CI/CD)

    Когда Terraform запускается не с ноутбука инженера, а в изолированном контейнере GitLab CI или GitHub Actions, интерактивная авторизация через yc невозможна. Использовать личный OAuth-токен разработчика в пайплайне — грубое нарушение безопасности.

    Решением является Сервисный аккаунт (Service Account, SA). Это не физический пользователь, а специальная сущность внутри каталога Yandex Cloud, предназначенная исключительно для программного доступа. Сервисному аккаунту назначаются строгие роли (например, editor на конкретный каталог).

    Для аутентификации сервисного аккаунта в CI/CD используется авторизованный ключ (Authorized Key) — JSON-файл, содержащий публичную и приватную части RSA-ключа.

    Процесс настройки выглядит так:

  • В облаке создается сервисный аккаунт.
  • Для него генерируется Authorized Key (файл key.json).
  • Содержимое этого файла сохраняется в защищенное хранилище секретов CI/CD системы (например, GitLab CI/CD Variables).
  • В пайплайне секрет записывается во временный файл или передается через переменную окружения YC_SERVICE_ACCOUNT_KEY_FILE.
  • Провайдер Yandex Cloud автоматически распознает этот метод:

    При этом Terraform будет динамически обменивать этот RSA-ключ на короткоживущие IAM-токены для выполнения API-запросов, обеспечивая максимальный уровень безопасности без участия человека.

    Механика terraform init и изоляция зависимостей

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

    Во-первых, Terraform обращается к публичному реестру (registry.terraform.io) и скачивает бинарный файл провайдера Yandex Cloud, соответствующий операционной системе и архитектуре процессора (например, linux_amd64 или darwin_arm64). Скачанный плагин помещается в скрытую директорию .terraform/providers/ в корне проекта. Это означает, что зависимости локализованы: два разных проекта на одном компьютере могут использовать разные версии одного и того же провайдера, не конфликтуя друг с другом.

    Во-вторых, генерируется файл .terraform.lock.hcl. Это механизм фиксации зависимостей (Dependency Lock). В нем записывается точная версия скачанного провайдера и криптографические хеши его бинарных файлов (алгоритмы h1 и zh1).

    Файл .terraform.lock.hcl обязательно должен быть добавлен в Git. Это гарантирует, что если другой инженер склонирует репозиторий или код запустится в CI/CD, Terraform скачает абсолютно идентичную версию провайдера. Если злоумышленник подменит бинарный файл в публичном реестре, хеши не совпадут, и terraform init завершится с ошибкой, предотвратив выполнение скомпрометированного кода.

    Работа в закрытых контурах (Provider Mirroring)

    В корпоративных средах с жесткими правилами безопасности серверы CI/CD часто не имеют прямого доступа в интернет и не могут достучаться до registry.terraform.io. В таких случаях применяется механизм сетевых зеркал (Network Mirrors) или файловых зеркал (Filesystem Mirrors).

    Инженер заранее скачивает нужные провайдеры и помещает их во внутреннее хранилище артефактов (например, Nexus или Artifactory) или просто в директорию на сервере. Затем в домашней директории пользователя создается конфигурационный файл CLI ~/.terraformrc, который переопределяет логику поиска:

    При такой настройке terraform init даже не попытается выйти в интернет за провайдером Yandex Cloud, а возьмет его из локальной директории, что обеспечивает автономность и скорость развертывания.

    Синтаксис ресурсов: анатомия декларативного блока

    После успешной инициализации и настройки аутентификации можно описывать инфраструктуру. Основной строительный блок Terraform — это resource.

    Синтаксис блока ресурса состоит из ключевого слова, типа ресурса, локального имени и тела с аргументами:

    Разберем эту конструкцию детально:

  • yandex_iam_service_accountтип ресурса, строго определенный в документации провайдера Yandex Cloud. Префикс yandex_ указывает на принадлежность к провайдеру.
  • builderлокальное имя (идентификатор). Оно используется только внутри кода Terraform для ссылок на этот ресурс из других частей конфигурации. Облако ничего не знает об имени builder.
  • name = "ci-builder-sa"аргумент ресурса, который будет передан в API облака. Именно под именем ci-builder-sa этот аккаунт появится в веб-консоли Yandex Cloud.
  • Важно различать аргументы (Arguments) и атрибуты (Attributes). Аргументы — это то, что мы задаем в коде (например, name и description). Атрибуты — это данные, которые генерируются самим облаком после создания ресурса. Например, после применения конфигурации облако присвоит сервисному аккаунту уникальный id и дату создания created_at.

    Мы не можем задать id вручную, но можем обратиться к этому атрибуту в другом месте кода, используя синтаксис обращения к ресурсу: <тип>.<локальное_имя>.<атрибут>.

    Например, если мы захотим выдать созданному аккаунту права, мы сошлемся на его сгенерированный идентификатор: yandex_iam_service_account.builder.id. Именно эта механика ссылок позволяет Terraform выстраивать граф зависимостей: он понимает, что прежде чем назначать права, необходимо сначала создать сам аккаунт и дождаться получения его id от API.

    Понимание того, как провайдер транслирует HCL-код в API-вызовы, как безопасно передавать учетные данные через переменные окружения и как фиксировать версии через .terraform.lock.hcl, создает надежный фундамент. Без этих настроек любая попытка автоматизировать инфраструктуру превращается в риск утечки данных или поломки из-за несовместимости версий. Настроенный и аутентифицированный провайдер — это открытый, но защищенный канал связи с облаком, готовый к приему команд на развертывание виртуальных сетей и вычислительных мощностей.

    3. Переменные и вывод данных: параметризация конфигураций для гибкого развертывания

    Копирование 500 строк файла main.tf для создания production-среды на основе тестовой, с последующим поиском и заменой слова «dev» на «prod» — прямой путь к катастрофе. Одно пропущенное значение, и production-сервер баз данных разворачивается на дешевом прерываемом диске, а веб-сервер случайно подключается к тестовому кластеру. Жесткое кодирование (hardcoding) значений в конфигурационных файлах уничтожает главную ценность инфраструктуры как кода — переиспользуемость. Код должен быть универсальным шаблоном, поведение которого меняется исключительно через передачу внешних параметров.

    В экосистеме Terraform управление данными строится на четырех китах: входные переменные (variable), локальные значения (locals), источники данных (data) и выходные значения (output). Вместе они формируют гибкий конвейер: получение внешних вводных, их внутренняя трансформация, запрос состояния реального облака и отдача результата.

    Входные переменные: API вашей инфраструктуры

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

    Строгая типизация в HCL

    Terraform поддерживает строгую типизацию. Если не указать аргумент type, переменная сможет принять любое значение (тип any), что сводит на нет предсказуемость кода. Типы делятся на три категории:

  • Примитивные: string (строка), number (число), bool (логическое значение).
  • Коллекции:
  • - list(type) — упорядоченный массив элементов одного типа. Индексируется с нуля. - set(type) — неупорядоченная коллекция уникальных элементов. Полезна для списков тегов или зон доступности, где порядок не имеет значения, а дубликаты недопустимы. - map(type) — ассоциативный массив пар «ключ-значение», где все значения имеют один тип.
  • Структурные: object и tuple.
  • Особую ценность для описания облачных ресурсов представляет тип object. Он позволяет передавать сложные, вложенные структуры данных, гарантируя наличие нужных ключей. Например, конфигурация виртуальной машины в Yandex Cloud требует указания ядер и памяти:

    При попытке передать в эту переменную объект без ключа core_fraction или со строковым значением вместо числа, Terraform выдаст ошибку еще на этапе парсинга, до обращения к API облака.

    Валидация значений (Custom Validation Rules)

    Типизация защищает от синтаксических ошибок, но не от логических. Например, тип number пропустит значение -5 для количества ядер, а тип string — опечатку «stgae» вместо «stage». Для бизнес-логики используется вложенный блок validation.

    В блоке condition должно быть выражение, возвращающее true или false. Для сложных строковых проверок часто применяют регулярные выражения через функцию regex в связке с can (которая перехватывает ошибку, если регулярное выражение не находит совпадений).

    Защита чувствительных данных

    Пароли от баз данных, приватные ключи SSH и токены API нельзя выводить в консоль при выполнении команд. Аргумент sensitive = true указывает Terraform маскировать значение переменной в логах и выводе команд plan и apply.

    В консоли вместо реального пароля отобразится (sensitive value). Однако важно понимать границу этой защиты: флаг sensitive скрывает данные только от глаз оператора в терминале. В файле terraform.tfstate значение все равно будет сохранено в открытом виде (plain text).

    !Поток данных в Terraform

    Иерархия подстановки: как Terraform ищет значения

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

  • Значение по умолчанию (default). Если переменная не передана ни одним из способов ниже, используется default из блока variable. Если default нет — Terraform остановит выполнение и интерактивно попросит ввести значение в консоли.
  • Переменные окружения (TF_VAR_name). Удобно для передачи секретов в CI/CD пайплайнах. Если переменная называется db_password, Terraform будет искать переменную окружения TF_VAR_db_password.
  • Файл terraform.tfvars. Стандартный файл, в котором значения задаются в формате имя = "значение". Автоматически загружается, если лежит в корне директории.
  • Файлы .auto.tfvars или .auto.tfvars.json. Загружаются автоматически после terraform.tfvars. Полезны для разделения переменных по смыслу (например, network.auto.tfvars, compute.auto.tfvars).
  • Флаги командной строки (-var и -var-file). Имеют наивысший приоритет. Переопределяют любые значения из файлов и переменных окружения.
  • !Иерархия приоритетов переменных в Terraform

    Архитектурно правильный подход заключается в том, чтобы держать базовые настройки в terraform.tfvars, специфичные для окружения переопределения — в *.auto.tfvars (или передавать через -var-file="prod.tfvars"), а секретные ключи инжектировать исключительно через переменные окружения TF_VAR_ в защищенном контуре CI/CD.

    Локальные значения: внутренняя логика (Locals)

    Входные переменные (variable) нельзя вычислять динамически. В них нельзя использовать функции, конкатенацию или ссылаться на другие переменные. Это просто контейнеры для внешних данных.

    Для внутренней логики, вычислений и избавления от дублирования кода (принцип DRY) используется блок locals. Локальные значения вычисляются во время выполнения и могут ссылаться на что угодно: переменные, атрибуты ресурсов, другие локальные значения.

    Обращение к локальному значению происходит через префикс local. (в единственном числе, в отличие от названия блока locals).

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

    Источники данных: чтение состояния облака (Data Sources)

    Инфраструктура не существует в вакууме. Часто для создания нового ресурса нужно знать параметры уже существующего. Например, чтобы развернуть виртуальную машину, нужен идентификатор загрузочного образа (Image ID).

    В Yandex Cloud образы операционных систем регулярно обновляются (патчи безопасности, новые версии ядер). Если жестко прописать ID образа Ubuntu 22.04 в коде (например, fd8...), через несколько месяцев этот образ может быть удален из публичного реестра провайдером, и код сломается.

    Здесь на помощь приходят источники данных — блок data. В отличие от resource, который создает и управляет объектами, data только читает информацию из облака (выполняет GET-запрос к API).

    Синтаксис обращения к источнику данных: data.<тип_источника>.<имя>.<атрибут>.

    Источники данных позволяют сделать конфигурацию динамической. Вы можете запрашивать существующие сети (если сеть создана сетевым отделом в другом State-файле), получать информацию о зонах доступности или искать конкретные SSL-сертификаты по их имени. Это стирает границу между тем, что управляется вашим кодом, и тем, что уже существует в инфраструктуре.

    Выходные значения: передача результатов (Outputs)

    После выполнения terraform apply облако генерирует множество динамических данных: внутренние ID ресурсов, сгенерированные MAC-адреса, публичные IP-адреса. По умолчанию эта информация оседает внутри файла состояния (terraform.tfstate), который не предназначен для ручного парсинга.

    Чтобы извлечь конкретные данные на поверхность, используется блок output.

    При успешном развертывании Terraform выведет это значение в консоль: Outputs: web_server_public_ip = "158.160.45.12"

    Сценарии использования Outputs

  • Информативность. Быстрый доступ к эндпоинтам баз данных, IP-адресам балансировщиков или ссылкам на созданные бакеты без необходимости заходить в веб-консоль Yandex Cloud.
  • Интеграция с другими инструментами. Команда terraform output -json выдает результаты в машиночитаемом формате. Это критически важно для передачи эстафеты системам управления конфигурациями. Например, Ansible может прочитать этот JSON, извлечь IP-адрес виртуальной машины и автоматически запустить на ней установку Nginx.
  • Межмодульное взаимодействие. При разбиении монолитного кода на модули (независимые блоки), output служит возвращаемым значением модуля. Модуль сети может возвращать ID созданной подсети, чтобы модуль виртуальных машин мог использовать его в качестве входной переменной.
  • Как и входные переменные, output поддерживает аргумент sensitive = true. Если выходное значение содержит пароль или приватный ключ, Terraform скроет его при стандартном выводе в терминал, защищая от случайной компрометации через логи CI-системы.

    Параметризация конфигурации через переменные, локальные вычисления и источники данных превращает жесткий скрипт в гибкую функцию. , где — входные переменные (variable) и состояние облака (data), — логика ресурсов и локальных значений (locals), а — инфраструктура и выходные данные (output). Именно этот математически предсказуемый поток данных позволяет использовать один и тот же код для развертывания десятков идентичных, но изолированных окружений.

    4. Сетевой фундамент в облаке: создание VPC, подсетей и правил доступа

    Любой облачный проект начинается с сети. При ручном создании виртуальной машины через веб-консоль облачный провайдер часто предлагает использовать сеть по умолчанию (например, default-ru-central1-a). Для тестового стенда это удобно, но в production-среде использование дефолтных сетей — прямой путь к конфликтам IP-адресов, невозможности настроить VPN-туннели между дата-центрами и критическим уязвимостям безопасности. Инфраструктура как код требует явного, декларативного описания каждого элемента сетевого фундамента: логических границ, адресных пространств, маршрутов и правил межсетевого взаимодействия.

    Анатомия Virtual Private Cloud (VPC)

    В Yandex Cloud базовым контейнером для всей сетевой инфраструктуры выступает Virtual Private Cloud (VPC). Логически VPC — это изолированное виртуальное сетевое пространство внутри конкретного каталога (Folder).

    Важное архитектурное отличие Yandex Cloud от некоторых других провайдеров (например, AWS) заключается в том, что сама по себе сущность VPC не имеет жестко заданного диапазона IP-адресов (CIDR-блока). VPC служит лишь глобальной логической оболочкой, объединяющей подсети. Ресурсы, находящиеся в разных VPC, по умолчанию полностью изолированы друг от друга на сетевом уровне и не могут обмениваться трафиком даже внутри одного облака, если между ними не настроен специальный пиринг или VPN.

    В Terraform создание этого базового слоя описывается минималистично:

    Этот блок кода регистрирует новую сеть в Yandex Cloud. Возвращаемый атрибут id этого ресурса (в данном случае yandex_vpc_network.production_vpc.id) станет обязательным аргументом для всех остальных сетевых компонентов.

    Подсети и зоны доступности: физическая привязка логики

    Если VPC — это логическая абстракция, то подсети (Subnets) имеют жесткую привязку к физической инфраструктуре — зонам доступности (Availability Zones). Зона доступности представляет собой независимый дата-центр или изолированный сегмент дата-центра со своим питанием и охлаждением (в Yandex Cloud это ru-central1-a, ru-central1-b и ru-central1-d).

    Подсеть всегда создается внутри конкретной зоны доступности и внутри конкретной VPC. Именно на уровне подсети задается диапазон внутренних IP-адресов в формате бесклассовой адресации (CIDR), например 10.10.1.0/24.

    Количество доступных IP-адресов в подсети подчиняется строгим математическим правилам. Базовая формула для расчета емкости сети выглядит как , где — префикс маски подсети. Однако для размещения виртуальных машин доступны не все адреса. В любой подсети облачный провайдер резервирует несколько адресов для служебных нужд: адрес самой сети, широковещательный адрес (broadcast), а также адреса для шлюза по умолчанию и внутреннего DNS-сервера Yandex Cloud. Поэтому реальное количество доступных хостов вычисляется как . Для маски /24 это свободных адреса.

    !Архитектура VPC и подсетей в Yandex Cloud

    При декларативном описании инфраструктуры важно заранее спланировать адресное пространство так, чтобы подсети не пересекались.

    В этом примере мы создали две независимые подсети в одной зоне доступности. Несмотря на то, что они разделены логически (для фронтенда и бэкенда), по умолчанию маршрутизатор Yandex Cloud обеспечивает связность между всеми подсетями внутри одной VPC. Ограничивать этот трафик мы будем позже с помощью Security Groups.

    Выход во внешний мир: NAT-шлюзы и таблицы маршрутизации

    По умолчанию виртуальные машины, размещенные в созданных подсетях, имеют только внутренние IP-адреса (например, 10.10.2.15). Они могут общаться друг с другом, но не имеют доступа к интернету.

    Назначение публичного IP-адреса каждой виртуальной машине — грубое нарушение принципов безопасности. Базы данных, внутренние API и кэширующие серверы не должны торчать в публичный интернет. Однако этим серверам необходимо скачивать обновления ОС, пакеты из репозиториев или обращаться к внешним API (например, к платежным шлюзам).

    Решением этой задачи является NAT (Network Address Translation). В Yandex Cloud для этого используется управляемый ресурс — NAT-шлюз. Он позволяет инстансам из приватных подсетей инициировать исходящие соединения в интернет, подменяя их внутренний IP-адрес на свой публичный. При этом инициировать входящее соединение из интернета к приватной ВМ через NAT-шлюз невозможно.

    Чтобы направить трафик от подсети к NAT-шлюзу, требуется создать таблицу маршрутизации (Route Table) и привязать её к подсети.

    Процесс настройки состоит из трех шагов в Terraform:

  • Создание самого NAT-шлюза.
  • Создание таблицы маршрутизации с правилом (route), направляющим весь внешний трафик (CIDR 0.0.0.0/0) на этот шлюз.
  • Обновление ресурса подсети — добавление ссылки на таблицу маршрутизации.
  • Теперь любая виртуальная машина в подсети backend_subnet_a, пытаясь отправить пакет на адрес 8.8.8.8, отправит его на виртуальный маршрутизатор VPC. Маршрутизатор сверится с таблицей private_route_table, увидит совпадение с 0.0.0.0/0 и перенаправит пакет на nat_gateway, который обеспечит выход в интернет.

    Security Groups: микросегментация и сетевой экран

    Наличие маршрута определяет, куда может пойти трафик. Но оно не определяет, разрешено ли ему туда идти. За безопасность и фильтрацию трафика отвечают группы безопасности (Security Groups).

    В классической IT-инфраструктуре межсетевой экран (firewall) обычно стоит на периметре сети или между крупными сегментами (VLAN). В облачной архитектуре применяется концепция микросегментации: Security Group действует как виртуальный firewall, привязанный непосредственно к виртуальному сетевому интерфейсу (vNIC) конкретной машины.

    Stateful-фильтрация

    Фундаментальное свойство Security Groups — они являются stateful (сохраняющими состояние). Это означает, что сетевой экран отслеживает состояние установленных соединений. Если правило Ingress (входящий трафик) разрешает запрос извне на порт 443 виртуальной машины, то Security Group автоматически разрешит ответный трафик (Egress) для этого конкретного соединения, даже если в правилах Egress нет явного разрешения.

    !Stateful-фильтрация трафика через Security Group

    По умолчанию, если к виртуальной машине привязана пустая Security Group, весь входящий трафик запрещен (Default Deny), а весь исходящий — разрешен.

    Декларативное описание правил

    Правила в Security Group делятся на ingress (входящие) и egress (исходящие). Каждое правило должно содержать протокол, диапазон портов и источник/назначение трафика.

    Ссылки между группами: отказ от IP-адресов

    Самая мощная возможность Security Groups в парадигме IaC — это способность использовать в качестве источника или назначения не статические IP-адреса, а идентификаторы других Security Groups.

    Представим классическую трехзвенную архитектуру: балансировщик нагрузки (Load Balancer), пул веб-серверов (Web) и кластер базы данных PostgreSQL (DB).

    В императивном мире системному администратору пришлось бы выяснять IP-адреса всех веб-серверов и прописывать их в firewall базы данных. При добавлении нового веб-сервера (масштабировании) пришлось бы вручную обновлять правила.

    В Terraform мы оперируем логическими связями. Мы можем создать правило для базы данных, которое звучит так: «Разрешить входящий трафик на порт 5432 только от тех виртуальных машин, которым назначена группа безопасности web_sg».

    Теперь, сколько бы веб-серверов ни было создано динамически, если им при создании назначена группа web_sg, база данных автоматически примет от них соединения. Это эталонный подход к проектированию облачной безопасности.

    Предотвращение циклических зависимостей в правилах доступа

    При проектировании сложных архитектур часто возникает ситуация взаимных или рекурсивных зависимостей.

    Например, для настройки кластера базы данных (где узлы реплицируют данные друг с другом) требуется разрешить трафик на порт 5432 от узлов этой же самой группы безопасности. То есть db_sg должна ссылаться сама на себя. Другой пример: микросервис А делает запросы к микросервису Б, а микросервис Б отправляет асинхронные коллбеки микросервису А. Их Security Groups должны ссылаться друг на друга.

    Если попытаться описать это через вложенные блоки ingress внутри ресурса yandex_vpc_security_group, Terraform столкнется с проблемой построения графа зависимостей (DAG). Чтобы создать группу А, нужно знать ID группы Б. Но чтобы создать группу Б, нужно знать ID группы А. Возникает циклический тупик (Cycle Error).

    Для решения этой проблемы архитектуры IaC Terraform провайдер Yandex Cloud предлагает использовать отдельный ресурс — yandex_vpc_security_group_rule. Этот подход разделяет создание логической оболочки группы (которая получает ID) и наполнение её правилами.

    В этом сценарии Terraform сначала успешно создает обе пустые группы, так как они ни от чего не зависят. Они получают свои идентификаторы в облаке. Затем Terraform создает правила, используя уже известные ID. Граф зависимостей разрешается линейно, без циклов. Использование обособленных ресурсов для правил — это best practice при написании модульных и масштабируемых конфигураций сети.

    Сетевой фундамент — это каркас, определяющий топологию и границы безопасности проекта. Декларативное описание VPC, подсетей, таблиц маршрутизации и групп безопасности гарантирует, что сеть разворачивается идентично в средах разработки, тестирования и продакшена. Ошибки конфигурации отлавливаются на этапе планирования (terraform plan), а не в виде инцидентов информационной безопасности в рабочей среде. После того как логические границы и правила доступа определены, инфраструктура готова к размещению вычислительных ресурсов.

    5. Вычислительные ресурсы: развертывание виртуальных машин Compute Cloud и управление метаданными

    Вычислительные ресурсы: развертывание виртуальных машин Compute Cloud и управление метаданными

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

    Анатомия ресурса yandex_compute_instance

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

    !Архитектура виртуальной машины в Yandex Cloud

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

    Вычислительные мощности и нюансы core_fraction

    Блок resources определяет, сколько аппаратных ресурсов гипервизора будет выделено инстансу. В отличие от классической виртуализации, где вы просто указываете количество ядер и объем памяти, Yandex Cloud вводит концепцию базовой производительности ядра — core_fraction.

    Параметр core_fraction указывает гарантированную долю процессорного времени, доступную виртуальной машине. Значение 20 означает, что физическое ядро гипервизора будет отдавать вашей ВМ минимум 20% своего времени. Если соседи по физическому серверу простаивают, ваша машина может использовать до 100% ядра, но как только возникает конкуренция за ресурсы, гипервизор жестко ограничит (затроттлит) потребление до гарантированных 20%.

    Для production-баз данных, таких как PostgreSQL, или высоконагруженных Nginx-балансировщиков всегда используется core_fraction = 100. Сниженные значения (5%, 20%, 50%) идеально подходят для тестовых сред, CI/CD-раннеров или микросервисов с неравномерной, пульсирующей нагрузкой, позволяя кратно снизить стоимость инфраструктуры.

    Также необходимо учитывать параметр platform_id. Yandex Cloud предлагает разные поколения процессоров (например, Intel Broadwell, Cascade Lake, Ice Lake). Платформа standard-v1 (Broadwell) поддерживает core_fraction 5% и 20%, тогда как более новые платформы standard-v2 и standard-v3 поддерживают только 20%, 50% и 100%. Попытка применить несовместимую комбинацию приведет к ошибке на этапе terraform apply.

    Дисковая подсистема

    Каждая виртуальная машина должна иметь загрузочный диск, описываемый в блоке boot_disk. Здесь мы связываем вычислительные ресурсы с образом операционной системы.

    Внутри initialize_params мы указываем image_id. Использование жестко закодированных ID образов — плохая практика, так как облачный провайдер регулярно обновляет их, закрывая уязвимости. Вместо этого используется источник данных data "yandex_compute_image", который динамически находит актуальный ID нужного семейства (например, ubuntu-2204-lts).

    Параметр type определяет физический носитель.

  • network-hdd — стандартные магнитные диски, подходят для хранения холодных данных и бэкапов.
  • network-ssd — твердотельные накопители, стандарт для большинства веб-сервисов и операционных систем.
  • network-ssd-nonreplicated — локальные SSD без сетевой репликации. Обеспечивают максимальную производительность, но при выходе из строя физического хоста данные будут безвозвратно утеряны. Подходят для кэшей или кластеров баз данных, где отказоустойчивость настроена на уровне самого приложения.
  • Производительность сетевых дисков (IOPS и пропускная способность) в Yandex Cloud масштабируется линейно в зависимости от их объема по формуле . Диск network-ssd объемом 10 ГБ будет работать физически медленнее, чем диск того же типа объемом 100 ГБ. Если приложению не хватает скорости записи, иногда дешевле увеличить объем диска, чем переходить на более дорогие типы хранилищ.

    Сетевой интерфейс и адресация

    Блок network_interface подключает виртуальную машину к ранее созданному сетевому фундаменту (VPC и подсетям).

    Параметр subnet_id определяет, в какой зоне доступности и с каким внутренним IP-адресом запустится машина. Установка nat = true приказывает облаку выдать инстансу публичный (белый) IP-адрес для доступа из интернета.

    Важный нюанс: по умолчанию nat = true выдает динамический публичный IP-адрес. При каждой остановке и запуске виртуальной машины (не путать с перезагрузкой ОС) этот адрес будет меняться. Если для сервиса требуется постоянный IP (например, для привязки DNS-записи), необходимо предварительно создать ресурс yandex_vpc_address и передать его значение в аргумент nat_ip_address внутри сетевого интерфейса.

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

    Метаданные и Cloud-Init: мост между инфраструктурой и ОС

    Создание виртуальной машины в облаке дает вам работающее ядро Linux, но на этом этапе система абсолютно "голая". В ней нет пользователей (кроме заблокированного root), нет ключей SSH, не установлены нужные пакеты. Настройка этих параметров вручную нарушает принципы IaC. Решение — передача метаданных.

    Метаданные — это словарь пар "ключ-значение", который хранится на стороне облачного провайдера. Виртуальная машина в процессе загрузки обращается к специальному локальному IP-адресу (обычно 169.254.169.254), считывает этот словарь и применяет настройки.

    Самый важный ключ в словаре метаданных называется user-data. В него передается конфигурация в формате Cloud-Init — индустриального стандарта для инициализации облачных инстансов.

    !Процесс инициализации виртуальной машины через Cloud-Init

    Cloud-Init конфигурация пишется в формате YAML и начинается с обязательной директивы #cloud-config.

    Этот компактный файл выполняет колоссальную работу:

  • Создает пользователя devops.
  • Добавляет его в группу sudo с правом выполнения команд без пароля (стандарт для автоматизации через Ansible).
  • Инжектирует публичный SSH-ключ, делая возможным безопасное подключение.
  • Обновляет индекс пакетов (apt-get update).
  • Устанавливает Docker и утилиты для мониторинга.
  • В блоке runcmd выполняет произвольные shell-команды от имени суперпользователя (в данном случае — включает автозапуск демона Docker).
  • Интеграция Cloud-Init в Terraform

    Передавать многострочный YAML напрямую в HCL-код ресурса неудобно и трудно читаемо. Лучшая практика — выносить Cloud-Init в отдельный файл (например, meta.yaml) и считывать его с помощью встроенной функции Terraform file().

    hcl scheduling_policy { preemptible = true } hcl resource "yandex_compute_instance" "db_server" { # ... базовая конфигурация ...

    lifecycle { prevent_destroy = true } } hcl lifecycle { ignore_changes = [metadata] } ```

    Развертывание вычислительных ресурсов через код фундаментально меняет отношение к серверам. Виртуальная машина перестает быть уникальным объектом ручной сборки. Благодаря декларативному описанию параметров в HCL и детерминированной инициализации через Cloud-Init, любой инстанс можно уничтожить и пересоздать заново в точно таком же виде за несколько минут. Это открывает путь к построению отказоустойчивых систем, где выход из строя отдельного узла является штатным событием, а не аварией.