Привет, Хабр! Я Михаил Фучко, технический продакт-менеджер SDN и Terraform в команде zVirt. Я продолжаю серию статей о пути, который мы проделали в процессе разработки собственного провайдера инфраструктуры для zVirt. В первой части мы определились с терминологией, обсудили основные концептуальные подходы автоматизации и сформировали образ «светлого будущего» — программно-определяемая инфраструктура серверной виртуализации на основе Terraform. Теперь пришло время обсудить проектирование самого решения. 

Начнем с определения — что, собственно, нужно делать?  Поговорим подробнее, что из себя представляет Terraform и где проходит граница ответственности между HashiCorp как изначальным разработчиком ПО и вендором, который пишет собственный провайдер.

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

Что такое Terraform

Разберемся подробнее в архитектуре. Terraform — это программный комплекс, разработанный компанией HashiCorp. Он реализует подход автоматизации инфраструктуры типа «манифест», о котором я подробно рассказывал в предыдущей статье. Обычно применяется для работы с виртуальными машинами, дисками, сетями и прочим в облачных провайдерах и серверной виртуализации. Высокоуровневые сущности (поставить Nginx, запустить контейнер, поправить права на конфиг) чаще управляются Ansible. 

Ключевые компоненты комплекса:

  • HCL — язык написания манифеста. Вводит «понятийный аппарат» (ресурсы, источники данных и т.п.), определяет синтаксис, базовую семантику объектов, поддерживаемые типы, алгоритмические и логические конструкции и т.п.;

  • Terraform Core — движок «компиляции» и исполнения манифеста. Формирует граф зависимостей описанных объектов, узнает (с помощью провайдеров) их текущее состояние и формирует команды на CRUD — создание, чтение, обновление и удаление объектов;

  • Provider(s) — семейство программных провайдеров подключения к инфраструктуре конкретных вендоров. Они реализуют интерфейсы, заданные Terraform, и обеспечивают слой совместимости между ядром программного комплекса и конкретным поставщиком инфраструктуры. Провайдеры комбинируют информацию о поддерживаемых платформой ресурсах («виртуальная машина», «диск» и т.п.) и инструмент для обращения к API платформы — фактически реализуя REST-клиент. 

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

  • Врапперы, которые сами генерируют манифесты, — terragrunt, terraflow, logan;

  • Инструменты оркестрации — Digger, Terramate;

  • Анализаторы манифестов — Checkov, Infracost.

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

Как получить Terraform

Для систем на базе Linux компания HashiCorp поддерживает репозитории, содержащие актуальную сборку под конкретное семейство — это Ubuntu / Debian, Fedora, CentOS / RHEL и некоторые другие. В данный момент доступ к ресурсам Terraform закрыт из нашей страны, но никого из заказчиков это пока не волновало. Вопросы альтернативных реализаций затронем позже в этом цикле статей. 

Для Мac OS X доступен репозиторий на базе Homebrew, для Windows — Chocolatey (но без официальной поддержки HashiCorp). На любую из поддерживаемых систем Terraform также можно установить вручную: распаковать zip-архив и прописать путь в PATH либо собрать из исходников на GitHub. 

Скажу пару слов о лицензии. Terraform — это коммерческое ПО, чьи открытые исходники распространяются по лицензии Business Source License 1.1. Это значит, что код можно смотреть, изменять и создавать его деривативы, но только для некоммерческого использования. Исправить баг можно, продать как свой сервис нельзя. Сам продукт доступен для использования в том числе и в коммерческих целях внутри компании, но запрещено создание аналогов платных сервисов компании HashiCorp на основе Terraform. 

Забегая вперед, отмечу, что на разработчика провайдера не наложено никаких ограничений. Можно делать открытые и закрытые, распространять по своему желанию и на коммерческой основе. Главное не прикасаться к самим компонентам Terraform. Идеальный сценарий: вы поставляете провайдер (бинарный файл), потребитель обязан найти и установить Terraform самостоятельно. 

Отдельно стоит отметить, что HashiCorp имеет репозиторий провайдеров Terraform Registry, куда вендоры (и сами HashiCorp) могут складывать свои провайдеры для автоматического подтягивания Terraform во время исполнения. По очевидным причинам этот путь для многих отечественных вендоров закрыт, поэтому мы решили использовать собственный репозиторий, который развернули на своих мощностях. Пользователям решения нужно будет добавить пару строчек для регистрации нашего репозитория в своих системах, и далее все пойдет привычным образом. 

Как работает Terraform

Итак, Terraform установлен и готов к работе — пришло время что-нибудь администрировать. Подготовительная стадия обычно заключается в создании каталога для проекта Terraform. В скрытых подкаталогах будут располагаться необходимые для работы модули. Для простоты считаем, что манифест мы уже написали и сосредоточимся на действиях самого TF. 

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

Далее идет опциональный шаг terraform plan. На этом этапе Terraform оценивает инфраструктуру, описанную в манифесте, и формирует план изменений. Если это первый запуск, сравнивать еще не с чем. При последующих запусках TF сопоставляет изменения в манифесте с содержимым файла terraform.tfstate. Там отражено актуальное состояние инфраструктуры на момент прошлого выполнения с точки зрения terraform. 

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

В ходе этого шага также учитываются зависимости объектов: какие из них должны быть созданы/отредактированы раньше, какие — позже. Для несвязанных объектов допускается параллельное обращение. Этот шаг не является обязательным и ничего не меняет — только демонстрирует план работ.

Работы операции планирования. Будет создана виртуальная машина, львиная доля характеристик которой на данный момент пользователем не заявлена, а следовательно, будет известна после создания. Данные унаследуются от шаблона.
Работы операции планирования. Будет создана виртуальная машина, львиная доля характеристик которой на данный момент пользователем не заявлена, а следовательно, будет известна после создания. Данные унаследуются от шаблона.

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

  • Объекты, не имеющие зависимостей, обрабатываются первыми;

  • Поощряется параллельная обработка;

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

Созданная инфраструктура сохраняется в файл terraform.tfstate, который будет использоваться для дальнейшего отслеживания состояний. 

Вывод Terraform во время применения изменений. Одна сущность будет создана, требуется подтверждение пользователя.
Вывод Terraform во время применения изменений. Одна сущность будет создана, требуется подтверждение пользователя.

Ликвидация развернутой инфраструктуры производится в ходе операции terraform destroy. Это последовательный проход по ресурсам, описанным в terraform.tfstate, с вызовом операции DELETE на каждый из них. Построение графа зависимостей выполняется по инвертированной логике: первыми уничтожаются наиболее комплексные объекты, а составные части — по мере утраты потребностей в них. Параллелизм поддерживается.  

Работа операции уничтожения. Все поля сущности станут null, сама сущность перестанет существовать. Ожидается команда от пользователя.
Работа операции уничтожения. Все поля сущности станут null, сама сущность перестанет существовать. Ожидается команда от пользователя.

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

Работа операции импортирования. Описанная ВМ будет зарегистрирована в файлах состояния Terraform и дальнейший ее менеджмент будет выполняться средствами IaC.
Работа операции импортирования. Описанная ВМ будет зарегистрирована в файлах состояния Terraform и дальнейший ее менеджмент будет выполняться средствами IaC.

Где заканчивается HashiCorp и начинается свой Terraform-провайдер

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

Их разработка лежит уже за пределами ответственности HashiCorp (хотя некоторые провайдеры компания все-таки разрабатывает и поддерживает самостоятельно), это задача вендора. Именно вендор хочет, чтобы его продукт встраивался в современную автоматизацию и был «IaC capable». Что же конкретно подразумевается под провайдером инфраструктуры? 

Это бинарный файл, который мы получаем при компиляции кода на языке Go (да, провайдеры пишутся только на нем). Фактически его роль — gRPC-сервер, к которому Terraform Core (основное рабочее ядро TF) обращается за данными и для внесения изменений в инфраструктуру. Вся логика взаимодействия именно с продуктом вендора находится внутри этого файла.

Провайдер реализует несколько основных функций:

  • Метаданные (Metadata). Провайдер сообщает свое имя и некоторую базовую информацию о себе;

  • Схема (Schema). Описываются глобальные параметры, необходимые для корректной работы файла. Например, для провайдера серверной виртуализции очевидно нужен адрес эндпоинта управления, логин и пароль — это как минимум;

  • Конфигурация (Configure). Фактически с этой функции начинается работа провайдера. Terraform передает ему данные, проверяет их корректность и готовит API-клиента для обращения к продукту. Все последующие операции с инфраструктурой будут выполняться уже с помощью этого настроенного механизма;

  • Ресурсы (Resources). Основные «рабочие лошадки», объекты-аватары различных сущностей инфраструктуры. Виртуальные машины, диски, сети — все имеет свой «ресурс» с описанной семантикой применения, валидациями и т.п.;

  • Источники данных (Data Sources). Объекты получения информации об инфраструктуре. Например, вам надо выбрать, на каком хосте запускать ВМ. Вот для такой задачи будет существовать объект-источник данных. При его исполнении вы получите список хостов, из которого можно выбирать и передавать потом в виртуальную машину;

  • Клиент API для продукта. Непосредственный исполнитель запросов Terraform. Основные задачи: обеспечивать аутентификацию при обращениях к API, формировать и отправлять HTTP-запросы и обрабатывать ответы и ошибки. 

В основе работы Terraform с ресурсами лежат операции CRUD. TF сверяет желаемое состояние (манифест) с фактическим и при необходимости вызывает Create, Read, Update или Delete:

  • Create — создание объекта. На основе данных из манифеста Terraform отправляет запрос на создание. Некоторые атрибуты (например, ID) становятся известны только после этого и сразу сохраняются в state-файл;

  • Read — получение данных о текущем состоянии объекта. Позволяет обнаружить расхождения, вызванные внешними изменениями, и принять решение об «актуализации». Для объектов типа «источник данных» (data sources) реализуется только эта операция;

  • Update — обновление состояния объекта. Запускается как реакция на изменение внешними силами (кто-то зашел в UI виртуализации и вручную переименовал виртуальную машину) или из-за смены параметров в манифесте;

  • Delete — удаление объекта. Выполняется, когда указанный ресурс исчез из манифеста.

Благодаря такому «переходнику» Terraform получает возможность полноценно управлять инфраструктурой.

Резюме

Теперь задача «обеспечение поддержки Terraform в zVirt» приобрела вполне конкретные очертания. Какие выводы мы сделали на этапе анализа: 

  • Terraform — это продукт HashiCorp, вмешиваться в его работу не рекомендуется. Клиенты сами получают инфраструктуру автоматизации, проблем с этим нет;

  • Обеспечение поддержки Terraform заключается в разработке провайдера — плагина, который соединяет ядро Terraform с API инфраструктурного решения;

  • Провайдер — это бинарный файл, скомпилированный из Go. Он реализует нужные интерфейсы и описывает ресурсы, которыми Terraform сможет управлять;

  • Необходимо решить вопрос доставки провайдера клиентам. Для этого потребуется развернуть собственный Terraform Registry на мощностях Orion soft — обеспечить поддержку версионирования, защищенного доступа и т.п. 

Теперь можно говорить и о самой разработке. Внезапно вспомним, что zVirt базируется на oVirt и задумаемся: быть может, кто-то уже разработал провайдер для oVirt и можно здорово сократить время, доведя до ума существующее решение и добавив поддержку возможностей zVirt? Забегая вперед — желание «сократить время» привело к трате примерно человеко-года разработки на попытки «отдебажить» и «выпрямить». Но об этом — в следующей статье.