О дивный мир, Infrastructure as a Code! С каждым днём появляется всё больше инструментов для работы с гетерогенными инфраструктурами любой сложности. На каждом уровне абстракции — свои утилиты и комбайны, которые если и не решают всех задач, то активно к этому стремятся. Примеров много. Самый популярный — Terraform. Чуть менее востребованы не такие функциональные Pulumi и Crossplane. Все давно привыкли, что управлять контейнерами и кластерами Kubernetes помогает целый набор решений совершенно разного калибра и функциональности: Rancher, Deckhouse, OpenShift, Kubespray, Helm, werf, K9s, Komodor. Привет! На связи Василий Мармер, DevOps-тимлид компании «Флант». Сегодня мы поговорим об Atlas — еще одной утилите, которая делает работу DevOps-инженера более комфортной. Atlas увидел мир в ноябре 2021 года, а сейчас у него более 3,3 тысячи звёзд на GitHub. Язык программирования — Go. Рассматриваемая утилита служит для управления схемами миграций баз данных и позволяет задействовать современные паттерны DevOps. Разработчики Atlas воспринимают свой инструмент как универсальный способ визуализировать, планировать и выстраивать миграции схем баз данных в соответствии с CI. Atlas и его оператор справляются не только с обычными MySQL и PostgreSQL, но и с MariaDB, TiDB и CockroachDB, неплохо поддерживает SQLite, что может стать подспорьем для разработчиков, которые предпочитают эту простую и легковесную базу данных. К сожалению, в рамках одной статьи протестировать их все невозможно. Из важных особенностей Atlas стоит отметить, в частности, возможность настраивать политику сравнения схем: можно выбрать skip destructive (чтобы обезопасить всю базу целиком или конкретные таблицы) или concurrent_index (чтобы удалять или создавать индексы конкурентно, без блокирования таблиц, — это опция, кстати, по умолчанию выключена). Удобно и логично настраиваются типы колонок в HCL. В Kubernetes можно использовать Atlas не только как оператор, но и в качестве init-контейнера конкретной базы данных. Ещё можно использовать Argo CD. Этот вариант выглядит наиболее интересным из всех, однако требует отдельной и довольно объемной статьи. Можно, конечно, ограничиться рекомендуемыми на официальном сайте инструментами миграции и импорта. Кроме того, есть неплохо описанная интеграция с Terraform — эта статья может стать неплохим подспорьем для тех, кто с помощью Terraform «крутит базы в облаках». Одним словом, вариантов использования Atlas очень много: на хостах — как инструмент или контейнер, в Kubernetes — от sidecar-, init-контейнера, управления схемами баз данных (независимо от количества кластеров, в том числе созданных с помощью Custom Resources) до использования подхода GitOps с помощью Argo CD и подобных инструментов. В любом случае теперь DDL гораздо проще сделать частью детально описанных инфраструктур.  Atlas позволяет работать двумя способами: использование декларативного описания схемы базы данных (как в Terraform) — сравниваем текущее состояние базы со схемой, описанной в HCL-, SQL-форматах или используем интеграцию с ORM (в результате получаем diff для дальнейшей работы); версионирование — планируем изменения, проверяем их и, наконец, применяем с помощью Atlas. Нас, DevOps-инженеров, для воплощения мечты World as a Code интересует прежде всего именно декларативный вариант использования Atlas. Что ж, проведём полевые испытания. Прежде всего установим на хост сам инструмент: Не забываем про completion: Для экспериментов нам понадобится плагин Compose для Docker. Убедимся, что он установлен: Начнем изучение Atlas с использования Docker Compose. Не будем затрагивать самые основы, такие как установка и принципы работы, — они достаточно хорошо описаны в официальной документации. А более сложные варианты использования утилиты, такие как совместная работа с K8s, обсудим далее. Первые опыты начнём над MySQL. Для этого поднимем в Docker небольшой пример: Создадим для наглядности простую базу данных:  Зальем эту базу в наш MySQL-контейнер: Появилось шесть табличек с простой структурой: Теперь можно испытать Atlas в деле:  Сделаем несколько изменений. Предположим, что в нашем приложении могут быть достаточно длинными названия отделов, имена, фамилии. Сравним файлы в формате .HCL со схемами — новой и старой. Важный момент: нам нужен ключ --dev-url, который указывает на соединение с пустой dev-базой, с помощью которой Atlas симулирует, подсчитывает и проигрывает изменения схем. Укажем docker://mysql/8/schema  — и Atlas сам поднимет пустую вре́менную базу в Docker. Получаем готовый diff: Такой же результат будет, если сравнить схему базы данных и новый файл. Продолжим эксперименты. Добавим ещё одну (уже вторую) пустую базу данных, просто скопировав предыдущий контейнер и изменив порт, который пробрасывается на localhost: Приведем первую базу данных к состоянию первой миграции с помощью файла схемы: Вторую базу данных приведем к состоянию первой напрямую: Ключ --auto-approve позволяет выполнять операции без вмешательства пользователя. Дальше посмотрим, как Atlas работает с PostgreSQL. Для этого понадобится короткий compose: Запустим его: Теперь приготовим тестовый DDL: Зальём его в первую базу: Подготовим слегка изменённый DDL: Зальем наш DDL во вторую базу: Применим изменения во второй базе данных к первой. Всё происходит так, как и ожидалось: Если подумать — плоховато нам будет без индексов. Для разнообразия накинем их вручную на вторую базу:  Теперь, чтобы накинуть индексы на первую базу, испытаем миграции: сначала создадим с помощью create_tables миграцию с базы данных без индексов: Посмотрим, что содержит директория: Теперь с помощью create_indexes создаем вторую миграцию, уже с индексами: Внутри видим следующее: Что ж… Выглядит прекрасно! А вот и вишенка на торте. Если попросить Atlas проверить последние две миграции из директории migrations на тестовой базе в Docker, то Atlas сообщит: «При создании индексов будут заблокированы таблицы в базе данных». Мы же не готовы с этим согласиться, не так ли? Поправим миграцию руками, добавив волшебное слово CONCURRENTLY: Запускаем линтер снова: Всё как у взрослых. Пытаемся исправить проблему: Странно… Линтер игнорирует CONCURRENTLY! Чтобы разобраться, почему так происходит, мы создали issue. Ответ последовал в тот же день — авторы исправили найденный нами недочет. Кстати, при создании таких индексов следует помнить, что CONCURRENTLY — это не свойство индекса, а свойство инструкции по созданию индекса. Не стоит забывать и о том, что длинная транзакция, затрагивающая таблицу, и такой индекс для этой же таблицы — это не самые лучшие друзья. Мы уверены в безупречности наших миграций. Применим их на первой базе, наконец-то указав первую миграцию как базовую (--baseline 20230712165510): Возникшая ошибка вполне логична. Исправление напрашивается само: С этого момента внутри базы существуют схема и табличка atlas_schema_revisions.atlas_schema_revisions, где будут сохраняться миграции: Более подробно описывать функционал миграций в Atlas не будем. Отметим лишь, что основную часть потребностей разработчика (если вообще не все) инструмент закрывает — вплоть до импорта всех известных форматов утилит для миграции, а также позволяет создавать кастомные форматы. Свобода никак не ограничивается. Можно использовать Gorm, Beego как провайдеров схем для Atlas. Можно написать свой провайдер с помощью Atlas SDK. Что ж, настало время перейти на другой уровень. Давайте соберём в K8s стенд из нескольких баз данных и накатим на них миграции с помощью Atlas Operator: Запустим кучку баз с помощью CloudNativePG и Bitpoke MySQL из конфигурационного файла db.yaml:  Создадим ns и применим файл db.yaml: Восемьдесят секунд  — и всё работает! Приготовим две большие портянки, одна из них — для наших «мусь»:  Внутри у нас есть секреты с URL, которые укажут Atlas’у, куда идти и как (обязательно с указанием namespace, поскольку оператор живёт в другом пространстве имен), общий configmap с нашей схемой и её изменениями в виде миграций, а также custom resource AtlasMigration, который сведёт всё воедино и попросит оператор провести миграции наших баз данных. Посмотрим, что получится. Применим наш YAML-файл: Вывод команды get нельзя назвать очень информативным: Посмотрим описание базы данных. Хм… Видим, что Reason: Applied, однако для полной уверенности информации всё-таки не хватает:  Если же заглянуть в базу данных, то все сомнения развеиваются: С PostgreSQL аналогично:  На этом пути нас ждал один курьёзный Pull Request — в ссылке на issue tracker всего проекта мы обнаружили опечатку, пришлось поправить и её. Это, конечно же, никак не помешало создать новый issue о нашей неудавшейся миграции, когда свойство CONCURRENTLY приводит к ошибке при создании индекса: С помощью разработчиков мы поправили наши миграции до исправного состояния. Из документации было не совсем очевидно, что именно нужно сделать. Но мы разобрались и стало понятнее: A file directive should be separated by two newlines from the first statement's comment. Все индексы добавились, миграции доехали — и на этом, пожалуй, стоит остановиться. Мы попробовали Atlas в нескольких простых комбинациях в двух контейнеризованных окружениях. Кажется, что результат вполне неплох, а инструмент пригоден для применения во всех контурах. Важно понимать: что именно находится в миграциях DDL; какой результат необходим в состояниях баз данных; каковы ожидания от инструмента миграции и оператора, который им управляет.  Контейнеризованная инфраструктура, которую используют разные группы разработчиков с несколькими (и даже вполне многочисленными) контурами-окружениями, — очень интересный и живой кейс. Научившись совместно использовать Atlas, можно с максимальной воспроизводимостью и прозрачностью для всех участников процесса — а, главное, безопасно! — тиражировать изменения не только кода, но и DDL: от локального окружения разработчика до production. Безопасность могут обеспечить как сам инструмент (например, с помощью вычисления контрольных сумм миграций), так и проверка миграций. Нет сомнений, что функционал будет развиваться и дальше. Читайте также в нашем блоге: Больше разработчиков должны знать это о базах данных Как настроить миграцию etcd между облачными кластерами Kubernetes и избежать простоев Беспростойная миграция MongoDB в Kubernetes Базы данных и Kubernetes (обзор и видео доклада)