Размышления о декларативной конфигурации

от автора

Не кажется ли вам, что декларативная конфигурация и программирование инфраструктуры не так уж хороши, как их расхваливают?

Я достаточно долго занимался декларативной конфигурацией в Kubernetesразмышлял о ней, работал с kubectl applyKRMkustomizeGoogle Cloud Config Synckptporch, … В то же время параллельно развивалась декларативная автоматизация — эта работа велась в Google, где на протяжении многих лет широко использовалась декларативная конфигурация. При этом вне Google появился Terraform, и на этом лоскутном одеяле также возникло множество других инструментов.

Что же такое декларативная конфигурация, в каких случаях она хороша, и как к ней подступиться?

❯ Сравним декларативную конфигурацию и программирование инфраструктуры

Первым делом давайте отметим, что я не говорю исключительно о программировании инфраструктуры (IaC). В понятии «Infrastructure as Code» обычно подразумеваются сразу две вещи, смешиваемые воедино:

  1. Конфигурация инфраструктуры должна быть представлена в виде кода или псевдокода. Для этогоможет применяться синтаксис разметки шаблонов, конфигурационные языки (напр., HCL, CUE), обычные языки программирования (напр., Typescript), иногда — скрипты. В таких форматах обычно поддерживается подстановка встроенных параметров, также поддерживаются ссылки на поля, выражения, условные операторы, иногда — циклы и функции.

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

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

Также обратите внимание: я говорю в основном про конфигурацию ресурсов в Kubernetes и ресурсов в облачноподобных инфраструктурных системах, хотя, всё сказанное в основном применимо и к конфигурации приложений. Речь, например, о структурах данных с множеством атрибутов и подструктур (списках, словарях, объединениях).

Вот пример из Terraform:

resource "google_compute_global_forwarding_rule" "http" {  project = var.project  count = local.create_http_forward ? 1 : 0  name = var.name  target = google_compute_target_http_proxy.default[0].self_link  ip_address = local.address  port_range = var.http_port  labels = var.labels  load_balancing_scheme = var.load_balancing_scheme  network = local.internal_network }

При работе с ресурсами Kubernetes поддерживает стандартные операции (Create (Создать), Read (Прочитать), Update (Обновить), Delete (Удалить), List (Перечислить)) и метаданные (именаметкианнотации). Этого (обычно) хватает, чтобы в общем виде реализовать операции чтения, изменения или записи в одиночном или групповом виде — эта функция давно активно используется в kubectl. Типы ресурсов и их схемы можно определять декларативно. В Terraform операции и схемы определяются прямо в коде на стороне клиента при помощи фреймворка провайдеров. Этот фреймворк позволяет толстым клиентам обёртывать причудливые API в собственные методынежелательные поведения, несогласованные структуры и т.д. Правда, может потребоваться немало времени, чтобы написать такой клиент — подробнее об этом ниже.

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

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

Часто бывает так, что в своей категории выделяются два наиболее популярных инструмента, например, Chef и Puppet или Helm и Kustomize, а вслед за ними тянется длинный-длинный хвост прочих, которые так и не вырываются из зоны притяжения первых. На мой взгляд, такой выбор формата зачастую обусловлен конкретными экосистемами (напр., Terraform, Helm), степенью знакомства с инструментом и личными предпочтениями.

У новых конфигурационных языков (DSL) обычно крутые кривые обучения, а также сравнительно скудное содержимое, небольшие инструментарии и библиотечные экосистемы. Кроме того, я вижу, что многие люди, отвечающие за подготовку конфигурации, не хотят писать, тестировать, поддерживать, собирать, эксплуатировать, обновлять, т.д., программы для такой работы, которые следовало бы создавать на обычных языках программирования. Я хотел бы почитать о том, меняется ли данная ситуация с появлением Pulumi и CDK (напр., cdk8s.io). Полагаю, отчасти всё зависит от того, насколько удобно переиспользовать генерирующий код, и как часто требуется вносить в него изменения. Кажется, такие решения уместны в тех случаях, где их можно встраивать в инструмент или фреймворк. В качестве примера я пробовал sst.dev, но представляется, что он постепенно отходит от AWS CDK, и его перестраивают на базе провайдеров Terraform, которые (наряду с Terraform) также используются в Pulumi и Crossplane. Согласен, что нацеливаться в данном случае на уровень ресурсов стратегически правильно.

❯ Что такое декларативная конфигурация?

В рамках декларативной конфигурации мы выражаем искомое состояние, а не описываем шаги, которые нужно выполнить, чтобы к нему прийти. То есть, мы описываем цель. Декларативная конфигурация помогает сосредоточиться на аспекте ЧТО, а не КАК. Фундаментально можно представить желаемое состояние в виде сериализованных данных. Поскольку такие данные могут быть обширными, зачастую они генерируются — например, при помощи инструментов программирования инфраструктуры.

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

Декларативная конфигурация не обязательно подразумевает работу с высокоуровневыми абстракциями. Другая крайность – описать при декларативной конфигурации все желаемые атрибуты, которые должны быть у управляемых нами ресурсов. Иногда такая конфигурация называется WET (мокрой) по выражению «Write Every Time» (писать каждый раз), в противовес DRY (сухой) или «Don’t Repeat Yourself» (не повторяйся). Второй подход подразумевает, что общие атрибуты требуется как-то вычленять. WET-конфигурацию можно представить как ассемблерный вывод, получаемый из инструментов программирования инфраструктуры или Kubernetes YAML.

❯Какие возможности даёт нам декларативная конфигурация?

Массовые операции

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

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

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

❯ Сериализованное представление желаемого состояния

При сериализованном представлении желаемого состояния, даже если речь идёт о простой низкоуровневой разновидности WET, уже открывается ряд полезных возможностей, например:

  • Автоматическое оживление работы на основе желаемого состояния;

  • Экспорт/импорт состояния;

  • Проверка перед развёртыванием и обеспечение следования политикам;

  • Изменение процедуры ревью и одобрения;

  • Версионирование и отмена/откат действий.

❯ Разделяемость и совместное использование

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

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

  • Пакеты обычно являются человеко-читаемыми, и человек может их писать;

  • Исходники пакетов обычно управляются как текстовые файлы в системе контроля версий;

  • Зависимости зачастую статически связываются;

  • Пакеты могут заноситься в репозиторий артефактов или хранилище объектов для последующего развёртывания, либо считываться непосредственно из системы контроля версий;

  • В пакетах часто содержатся описания илидругие метаданные, чтобы и было прощеобнаруживать.

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

❯ В чём польза декларативной конфигурации?

Декларативная конфигурация может быть особенно полезна в организациях, которые используют Kubernetes и/или облачные ресурсы. Именно на предоставление инфраструктуры обычно уходит больше всего времени у команд, занимающихся обслуживанием платформ. При декларативной конфигурации можно обеспечить желаемые возможности, которые не встроены в большинство инфраструктурных систем, в том числе, в системы контроля версий. Таким образом, можно выстроить общий уровень оснащения для множества разнородных систем. Он был бы похож на унифицированные API, но без создания общих абстракций, которые охватывали бы множество провайдеров. При массовых операциях можно управлять скоплениями ресурсов – и это, вероятно, покажется привлекательным для тех пользователей, которым требуется управлять большим количеством ресурсов. Разделяемость ресурсов и их переиспользование легче внедрять в похожих сценариях, а при конструировании вариантов проще повторять одни и те же операции в разных окружениях, регионах или командах.

Декларативная конфигурация также может послужить базисом для других систем и инструментов, например, для сервисов предоставления ресурсов.

❯ Подводные камни программирования инфраструктуры

На практике при программировании инфраструктуры обычно возникает ряд подводных камней.

В большинстве инструментов для программирования инфраструктуры реализуется однонаправленный подход, при котором предполагается, что все изменения вносятся только через эти инструменты (эта практика называется «исключительная активация»), а все другие изменения в текущем состоянии трактуются как нежелательный дрейф конфигурации. На практике дрейф сложно предотвращать. Иногда он вызван человеческим фактором, но может возникать и просто при работе систем. GitOps позволяет быстро обнаруживать такие отклонения, но автоматически устранять дрейф обычно можно только в одном направлении, и для этого требуется менять текущее состояние.

Сочетая переиспользование готовых решений, генерирование вариантов, пытаясь при этом вычленить общие атрибуты, а также создавать высокоуровневые абстракции, мы постоянно усложняем генератор конфигураций. Из-за этого писать и поддерживать генераторы конфигураций оказывается всё сложнее, переиспользуются они менее эффективно, чем хотелось бы, а также становятся всё менее понятными. В таких случаях говорят о «кустарной автоматизации». Кроме того, при таком подходе упускается из виду большинство автоматических изменений.

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

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

Почему же приходится управлять API, расположенными в плоскости управления, и как они используются? Их попеременно вызывают различные взаимодействующие друг с другом системы и инструменты. Это хорошо видно на примере внешнего слоя Kubernetes API, где поддерживается многосистемное взаимодействие через watch. Когда мы полностью переносим управление в некий неуказанный источник истины (напр., какие-то файлы в какой-то каталог в каком-то git-репозитории) в формате, не поддающемся интерпретации, причём, для этого применяется необратимый непрозрачный процесс, мы фактически переводим управляющий API в режим «только для чтения», а не «для чтения и записи».

❯ Можно ли доработать декларативную конфигурацию?

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

Во-вторых, чрезвычайно важно найти способы повысить удобство работы с учётом экономических интересов пользователей, сервис-провайдеров, разработчиков инструментов, авторов плагинов и пакетов. До сих пор большинство инструментов для программирования инфраструктуры устроено достаточно бесхитростно, а концепциям, на которых они основаны — десятки лет. Это движки-шаблонизаторы (напр., jinja, envsubst) или простой языковой транспилятор, менеджер пакетов, механизм плагинов, граф задач, система сборки и развёртывания (или уже имеющаяся система непрерывной интеграции и доставки), а также каталог сервисов. Вышеперечисленные элементы работают с любыми типами ресурсов. Поверхность схемы ресурсов у различных провайдеров обычно очень велика, и поэтому оказывается дорого писать код, специфичный для конкретных ресурсов (скажем, плагины для провайдера) или конфигураций (например, модули Terraform для многократного использования). Как правило, такие разработки в той или иной степени зависят от краудсорсинга и напрямую не монетизируются.

В-третьих, необходимо уделить внимание, прежде всего, тем занятиям, на которые пользователи тратят максимум времени — полагаю, это написание конфигурации и её изменение. Мне нравится, что Firefly сосредоточилась именно на этом участке работы и начала с ресурсов, которыми пользователи уже обзавелись. Также недавно появился целый ряд инструментов для  выстраивания инфраструктуры на основе кода, инструментов для графического проектирования и т.д. Кроме того, я бы хотел взглянуть на такие варианты декларативной конфигурации, которые не ограничиваются вызовом шаблонов, написанных вручную.

До сих пор иногда приходится писать на ассемблере или HTML, но обычно мы этого не делаем. Если бы декларативную конфигурацию редко писали вручную, то как бы можно было это изменить?

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud в нашем Telegram-канале

Перейти ↩

📚 Читайте также:


ссылка на оригинал статьи https://habr.com/ru/articles/860844/