Привет! Это Николай Гриценко, ведущий технический менеджер в Yandex Infrastructure — команде, которая создаёт и развивает внутреннюю инфраструктуру Яндекса, от сетей и дата‑центров до инфраструктуры разработки. Я занимаюсь направлением Internal Developer Platform (IDP). Вместе с коллегами мы много разрабатывали наши собственные инструменты по оркестрации выкладки кода.
В этой статье разберёмся в терминах, попытаемся понять, что же вообще такое инфраструктура как код или IaC, какие бывают инструменты, какие виды и что у нас с этим в Яндексе.
Зачем нужно управлять инфраструктурой
Если вы работаете с инфраструктурой, то, скорее всего, видели, как со временем её структура может развиваться внутри компании от элементарной до очень сложной.

-
Просто
Сначала у вас есть какой‑то код и какой‑то сервер. Разработчики берут этот код, компилируют, а вы как SRE кладёте его на сервер, он работает. Всё хорошо.
-
Сложнее
Приходит менеджер и говорит: ребята, надо добавить новых фич. Разработчики добавляют фичи, и получается, что уже нужен не один сервер, а два, а чуть позже — и три. Вскоре у вас образуется целая серверная стойка, а может быть, и полноценный дата‑центр, обслуживание которого ложится на ваши плечи.
-
Чуть сложнее
Для того чтобы эффективно управлять всем этим хозяйством, вы внедряете различные абстракции и запускаете на серверах виртуализацию.
-
…и ещё сложнее
Виртуализация работает, но хочется ещё большей эффективности в плане экономии серверных ресурсов и метрик времени по релизам. Вы внедряете контейнеры и Kubernetes. В итоге у вас получается множество слоёв инфраструктуры с кучей абстракций и непонятными взаимодействиями между ними. На нашей схеме это хорошо иллюстрирует последний рисунок.
А чего мы хотим?
Посмотрим, как можно избежать подобной картины и какие требования нужно предъявлять к подходам и инструментам для того, чтобы можно было эффективно управлять инфраструктурой.
-
Оперативные изменения
Нам хочется вносить изменения в нашу инфраструктуру достаточно быстро. Конечно, мы хотим, чтобы инфраструктура была аудируема, и не хотим тратить много времени на поддержку тех конфигов, которые пишем для неё.
-
Единый конфиг
Мы хотим сократить конфигурационный дрифт. Когда в продакшн‑среде живёт код и у сервиса есть своя история, то окружение постепенно начинает разъезжаться. Естественно, мы хотим это минимизировать, и поэтому нужен один конфиг, чтобы раскатывать его одинаково по всей инфраструктуре.
-
Писать меньше кода
Мы хотим написать конфиг один раз. Я думаю, никто не любит писать много односложного кода. Например, для работы в мультиоблаках. В идеале следовать принципу Write Once Run Anywhere: написать один конфиг и раскатать его и в Amazon, и в Azure, и в Google, и в Yandex Cloud.
-
Гибкая конфигурация
У DevOps‑инженеров и разработчиков много времени уходит на то, чтобы найти, где в случае инцидента что‑то чинить в инфраструктуре. Это достаточно серьёзная проблема: уметь быстро диагностировать проблемы инфраструктуры, быстро их решать и быстро применять эти фиксы.
Инструменты управления инфраструктурой
Вспомним самые популярные и старые инструменты для управления инфраструктурой:
Подавляющее большинство коллег из инфраструктуры работали с ними, многие продолжают это делать и сейчас. Нельзя сказать, что это очень удобные решения, но раньше на рынке такого софта выбор был небольшой. Практически не было инструментов, которые помогали бы организовать и оркестрировать какие‑то выкладки. Например, Chef — решение от выходцев из Amazon, которое было одним из немногих на рынке, когда я только начинал заниматься этой темой в далёком 2012 году.
Сейчас, конечно, появились и более современные инструменты, в первую очередь небезызвестный Terraform, но кроме него, есть ещё CDK8s, Crossplane и Pulumi. У них нет такой богатой истории, как у предыдущего списка, но в целом они дают больший контроль над инфраструктурой, а также возможность писать различные инфраструктурные конфигурации.
В Crossplane можно строить композитные ресурсы и транслировать различные варианты конфигураций в конечные объекты. Pulumi — это отдельная платформа для оркестрации со своими плагинами для разных языков программирования, а CDK8s — это оболочка над Kubernetes. В последних двух решениях можно писать целые программы на удобном вам языке, которые могут управлять инфраструктурой.
У Terraform достаточно богатый язык выражений на HCL. Можно написать свои ресурсы и модули, используя Terraform, и уметь разворачивать свою инфраструктуру.
Давайте посмотрим немного под другим углом — это всё про опенсорс, но как живут крупные компании? Есть инструменты, которые использует Google: Annealing и prodspec. Про них довольно мало пишут в сети. Иногда что‑то рассказывают на конференциях. Но с уверенностью можно сказать, что в Google очень строго следят за тем, чтобы конфигурационные файлы для инфраструктуры создавались по определённым требованиям. Каждая команда (даже небольшая) разрабатывает трансляторы, которые преобразуют их собственные форматы конфигов в формат, нужный для работы основной системы. Это делается для того, чтобы все части инфраструктуры работали согласованно и без ошибок.
В Яндексе также разработан подобный инструмент — Infractl. Он родился, когда мы поняли, что при работе с инфраструктурой могут получаться сложные конфигурации, которые потом непросто собрать воедино вместе со всеми балансировщиками, контейнерами. О нём поговорим чуть позже, а пока вернёмся к концепции IaC с точки зрения того, что мы уже обсудили.
Инфраструктура как код — что это
Что мы обычно понимаем под IaC:
Пишем код. Создаём императивные конфиги для инфраструктуры на любимом языке (HCL, starlark), которые можно где‑то хранить.
Используем API. С помощью перечисленных инструментов можно напрямую взаимодействовать с облачными примитивами через API. Например, можно сходить в Amazon и сказать: «Создай мне EC2-контейнер. Подними какую‑то сеть или security‑группу».
Приводим «полудекларативно» нашу конфигурацию к нужному состоянию. Многие языки поддерживают различные вкрапления императивного кода. То есть можно написать на Terraform и в декларативном стиле, однако там же можно, например, добавлять for each к своим конфигурациям. Это часто порождает огромные сложности в понимании того, что получится на выходе.
Сравним с Infrastructure as Data
Есть другой подход, который эволюционировал из инфраструктуры как кода. Он называется Infrastructure as Data. Впервые о нём заговорили некоторое время назад, и он чуть‑чуть отличается от привычного IaC. Посмотрим по пунктам:
Пишем конфиги. Примерно как и в IaC, но наши конфиги строго декларативные, это наше конечное состояние системы.
Следим за состоянием декларативной конфигурации и автоматически приводим её к нужному состоянию. Мы начинаем отслеживать состояние нашей конфигурации и пытаемся через цикл reconcile автоматически привести инфраструктуру к нужному состоянию относительно того, как написаны конфиги.
Обогащаем выходные окружения дополнительными параметрами. Это обогащение какими‑то дополнительными политиками, правилами и дополнительным контекстом тех конфигураций, которые мы пишем.
Два примера инструментов. Первый — классический Infrastructure as Code — это Terraform. Infrastructure as Data, как правило, рассматривается в разрезе Kubernetes. В принципе, можно использовать любой IAC‑tool, в том числе и Terraform поверх того же Kubernetes.
Нарисую простую схему, и попробуем посмотреть в сравнении на эти подходы.

В случае Infrastructure as Code мы пишем код для описания всей инфраструктуры. Перед развёртыванием проверяем, подходит ли наш план: всё ли идёт по намеченному пути. Если план нас устраивает, двигаемся дальше и получаем нужную инфраструктуру. Если что‑то не так, то откатываем изменения, исправляем ошибки в коде и пробуем снова. Это повторяющийся процесс до тех пор, пока всё не заработает как нужно.
В случае с Infrastructure as Data всё устроено немного иначе. Мы записываем желаемое состояние системы в конфигурационный файл, затем загружаем эти описания в специальное хранилище. После этого система постоянно отслеживает изменения (это и называется циклом reconcile) и обновляет состояние, добавляя информацию о доступах, правах и других настройках. В итоге мы получаем актуальное состояние всей инфраструктуры.
Если хочется узнать об этом подробнее
Замечательный эксперт из компании Softworks Геральд Шмидт проводил вебинар Gerald Schmidt. Thoughtworks. Очень рекомендую его посмотреть. Там он подробно рассказывает историю, как это всё происходило, для чего, кто придумал и так далее. И советую обратить внимание на техрадар этой компании, где можно узнать подробнее о различных инфраструктурных инструментах: на какой стадии развития находятся, рекомендованы ли к применению или нет.
Imperative vs Declarative
Все инструменты, которые мы рассматривали до этого, могут использоваться либо в императивном, либо в декларативном стиле. Тут достаточно просто. Как пример — CDK8s. Берём наш любимый TypeScript или любой другой язык, который поддерживается, пишем MyChart с конструктором, с переменными, с вызовами. При запуске он будет конвертировать и выдавать нужный конфиг. Это императивный подход.

Декларативный подход: возьмём кусок из Ansible, где мы просто говорим, что хотим запустить какие‑то конкретные задачи, при этом нам не важно, как они будут запущены. В данном случае, например, это сделано на YAML.
- name: manage state of Windows/RHEL Web Services
hosts: rhel_web_servers:windows_web_servers
connection: local
gather_facts: True
tasks:
- name: Print all available facts
ansible.builtin.debug:
var: ansible_facts
- name: Print os type
ansible.builtin.debug:
var: ansible_distribution
Mutable vs Immutable
Инфраструктура может быть как мутабельная, так и иммутабельная. У обоих подходов есть свои плюсы и минусы. Если ваша инфраструктура иммутабельная, это означает, что вы должны полностью переделывать каждый раз всю инфраструктуру, как только вы коммитите какую‑то спеку. Плюсы в том, что у вас сокращается конфигурационный дрифт. Вы всегда точно знаете, какая инфраструктура получится на выходе, то есть никаких флуктуаций там быть не может.
Если вы будете делать это в мутабельном варианте, поменяете какой‑то параметр, то может быть достаточно сложно предугадать, как поведёт себя инфраструктура.
Плюс мутабельности — это чёткое фиксированное время выкатки изменения. При накатке правок и конфигураций они все имеют какой‑то понятный цикл и, соответственно, предсказуемы по времени.
Stateful vs Stateless?
Как и в коде, инфраструктура может хранить состояния, например, для секретов или содержимого переменных. На самом деле эти два подхода точно так же зависят от тех задач, которые вы решаете, от того приложения, которое крутится на ваших контейнерах. Если приложение stateful, наверняка вам захочется иметь и stateful‑инфраструктуру. Если у вас приложение stateless, скорее всего, инфраструктуру тоже захочется сделать stateless.
Как работают решения в Яндексе: Infractl и Internal Developer Platform
В Яндексе инфраструктурой управляют с помощью специальных решений: Internal Developer Platform и Infractl, которые я уже упоминал. Нам была нужна платформа, которая сможет увязать всю сложную инфраструктуру, а именно:
-
Описать инфраструктуру декларативно
Решения Яндекса для управления инфраструктурой — это не какой‑то отдельный инструмент, а большая Dev/SRE‑платформа, которая может корректно описывать всю инфраструктуру для разработчика.
Мы решили, что возьмём за основу именно декларативный подход, то есть будем делать конфиги и описывать их исключительно в декларативной форме.
-
Связать инфраструктуру и код приложения
Также мы пытаемся связать инфраструктуру с кодом приложения, потому что часто бывает, что развёртывание инфраструктуры сильно зависит от тех фич, которые добавляют в приложение. Их нужно как‑то катить совместно. Не всегда одновременно, но, по крайней мере, как‑то связанно друг с другом. И нельзя разделять эти события — если упустить, на каком‑то из этапов релиза может случиться серьёзный инцидент или задуманная фича будет работать по‑другому.
-
Упрощать создание новых сервисов в несколько кликов
Инфраструктура Яндекса достаточно масштабная и сложная. Тут становится критичным такой фактор, как время, которое разработчик тратит на документацию, изучение флагов и CLI. Поэтому наработки, с помощью которых можно упростить создание новых сервисов, становятся очень востребованными. Мы хотим сделать настройку инфраструктуры достаточно простой, чтобы можно было это сделать в несколько кликов.
-
Уметь по‑разному раскатывать свои релизы
Также мы хотим уметь по‑разному раскатывать релизы, то есть иметь различные политики. Например, политики, которые позволят делать это полокационно: сначала в один дата‑центр, потом во второй. При этом важно давать возможность видеть, как этот трафик распределяется между ними. Мы должны уметь каким‑то образом контролировать нашу выкладку, и это должно быть тоже декларативно и просто описано.
-
Упрощать диагностику проблем
С помощью хранения и упрощения спецификаций хотим легко проводить диагностику проблем. Здесь нам помогает то, что мы имеем единую модель приложений в нашей платформе. За счёт этого мы можем правильно связывать друг с другом компоненты, которые создаются, и показывать их узкие места, правильно связывать это с Observability.
-
Стать единым источником знаний для разработчика
Если человек хочет развернуть сервис в Яндексе, он в первую очередь должен прийти на нашу платформу. Помимо автоматизированного создания, мы являемся и источником правильной документации для стандартных и не очень стандартных решений.
Что представляет собой конфигурация инфраструктуры Яндекса
Наследование Kustomize вместо модулей (а‑ля Terraform). Мы используем Kustomize в качестве движка для наследования. В отличие от модулей, которые применяются классически в Terraform, мы пытаемся через наследование покрыть наибольшее количество кейсов переиспользования кода и снизить дублирование.

На этой схеме папок и файлов можно увидеть, что у нас есть папка my‑product и в ней лежит файл i.yaml с метаинформацией о сервисах и микросервисах, которые находятся в этом каталоге. Там же можно будет найти политики.

Дальше можно посмотреть, какие у нас есть базовые настройки i.runtime.base.yaml. Там собраны все те настройки, которые будут распространяться на все рантаймы нижележащего уровня. У нас может дополняться какая‑то метаинформация уже в конкретном вызове API. И, соответственно, через наследование, через ключевое слово from можно унаследоваться от базового слоя и доопределять настройки.

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

Вот как выглядит декларативное описание реального сервиса в Яндексе.
По сути, это два файла. Первый файл — это YAML с базовым описанием. Здесь мы определяем compute, настраиваем порты в файрволе, даём ссылку на тот docker‑image, который хотим собрать, и определяем параметры наших контейнеров.
Во втором файле через ключевое слово from делаем наследование и внутри доопределяем или переопределяем те ключевые параметры спецификации, которые мы хотим выкатить. В данном случае мы переопределили блок compute, добавили несколько переменных в ENV и сказали, что нам нужно для этого окружения в четырёх дата‑центрах по три реплики.
Давайте посмотрим, как мы работаем с такими спецификациями.
Монорепозиторий в качестве источника правды. Наш монорепозиторий используется в качестве хранилища конфигов. Разработчик работает с ними по привычной схеме взаимодействия с VCS (коммиты, пул‑реквесты и т. д.).
На этапе пул‑реквеста можно делать различные хуки, валидации, дополнительные автоматизированные проверки. Ну и, помимо прочего, это важный источник знаний для коллег, чтобы можно было прийти и аудируемо внести правки или комментарии на изменения в инфраструктуры.
После того как мы прошли пул‑реквесты и все проверки успешные, мы передаём эти спеки в CI‑пайплайн, который занимается выкладками, и запускаем две команды: bild и make. Bild собирает нужный образ docker, и он становится доступным для контейнерного окружения инфраструктуры.
И infractl make, который собирает эти спецификации в конечный формат и отправляет их дальше.

Затем выполняется команда infractl put, и спецификация отправляется на применение при помощи хуков Kubernetes.

Здесь видно, что мы работаем с Kubernetes в качестве центрального звена. Мы его используем исключительно как хранилище и как дополнительный сахар — он нужен для того, чтобы можно было удобнее делать контроллеры, хуки, кастомные CRD. Мы не используем стандартные K8s‑примитивы в виде деплоймента, replica set и так далее. Мы написали свои собственные примитивы и используем etcd как хранилище.
После команды put спека попадает в веб‑хуки, там происходят валидации, тесты, обогащение политиками, правами и всем остальным. После этого контроллер, который видит, что нужно применить какие‑то изменения, начинает свою работу, и мы эти изменения применяем. Они раскатываются по тем провайдерам, которые указаны в спеках (балансировщики, контейнеры и т. п.).
На самом деле есть ещё один этап отслеживания изменений в монорепозитории. Если кто‑то закоммитил какие‑то изменения, мы их автоматически подхватываем и приводим через reconcile‑цикл к нужному состоянию нашей инфраструктуры. Этот процесс получается в таком виде полностью контролируемым.

Terraform vs Infractl. Давайте подведём небольшие итоги и попробуем сравнить популярный подход Terraform и нашу разработку. В Infractl мы работаем с более высокими абстракциями, нежели в Terraform. Мы пытаемся соблюдать баланс между гибкостью и простотой, чтобы можно было простые вещи легко описать, а для сложных у нас всегда есть возможность что‑то доопределять.

Также у нас есть полноценный UI, где мы можем редактировать эти спеки. В Terraform это в принципе невозможно, так как это клиентская утилита и не получится каким‑либо образом программно достучаться до «API Terraform», потому что его как такового нет.
Мы добавляем достаточно много яндексового сахара в спецификации для того, чтобы можно было правильно провязывать взаимодействие сущностей, например, балансировщиков, observability‑мониторингов и непосредственно runtime‑контейнеров.
Terraform при этом остаётся достаточно востребованным инструментом. Чаще всего он отлично подходит для простых окружений. У нас есть примеры, когда наши коллеги на Terraform полно описали инфраструктуру, которую они хотят видеть. Получалось очень громоздко: 250 файлов, куча модулей и множество взаимодействий. В этом было очень сложно ориентироваться и можно очень легко всё сломать. Мы сделали для себя вывод, что Terraform хорош, когда нужно что‑то просто описать и работать напрямую с облачными примитивами, без дополнительных абстракций.
Спасибо, что дочитали до конца, буду рад ответить на вопросы. Это выступление было чуть больше года назад, и с тех пор наша молодая платформа обрела осязаемую оболочку. Если хочется узнать больше о том, что изменилось и какие уроки мы извлекли, буду рад видеть вас 5 июня на infra.conf»25, где мы с коллегой расскажем подробности про устройство Internal Developer Platform (IDP) в Яндексе.