Привет, Хабр! Это Андрей Аксенов, Tech Lead of DBA Core RnD компании Wildberries. Занимаюсь базами данных, а точнее — автоматизирую их раскатку, настройку и весь жизненный цикл. Расскажу, как мы организовали эффективное управление более чем 15 тыс. инстансов БД для 100+ инженеров проектов. Поговорим, как автоматизировать Ansible, и рассмотрим полный флоу по работе с БД, которые повышают надёжность и снижают когнитивную нагрузку на инженеров.

Статья основана на моём выступлении на DevOpsConf 2025, которое можно посмотреть на YouTube, Rutube и в VK Видео.

Особенности инфраструктуры

Инфраструктура компании внушительна — около 10 тысяч железных серверов, расположенных как в коммерческих ЦОДах, так и в собственном. Плюс компания строит ещё несколько ЦОДов.

Вообразите себе обычную серверную стойку, но только высотой с Останкинскую телебашню. Туда поместится хорошо, если половина наших серверов.

Типовой продуктовый сервис в компании представляет из себя Stateless-приложение, которое крутится в Kubernetes-кластерах (K8s), хранит данные в S3-хранилищах и различных СУБД. Так это выглядит в разрезе ЦОДов:

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

Базы данных работают либо на виртуальных машинах, либо на железках. В первую очередь — на нашем частном облаке WB-Cloud. Часть нагрузки забирают на себя инстансы Proxmox.

Во время работы с виртуальными машинами мы держим в голове один факт: у нас нет централизованных СХД, которые позволят быстро поднять виртуалку в случае потери гипервизора. Поэтому надёжность СУБД обеспечивается средствами кластеризации.

Из-за этого утрату виртуальных машин мы рассматриваем как вполне вероятное событие и имеем инструменты для ее быстрого переразвертывания и включения в кластер СУБД или брокера.

На размер виртуальных машин у нас установлен разумный предел по объему диска — 1 Тб. Это баланс емкости и скорости — миграция такой ВМ при наличии сети 25 Гбит/с составляет 5 минут.

Как все начиналось

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

Благодаря сарафанному радио по компании разошлась слава о наших надёжных кластерах БД, и мы трансформировались в команду DBA Core. Сейчас у нас 39 инженеров DBA Core, 25 инженеров L2 технической поддержки, более 160 инфраструктурных инженеров проектов и — на всех нас в сумме — 980 продуктов.

Когда команда начала расти, мы столкнулись с тем, что «старички» и новые коллеги воспринимают ansible-роли по-разному. Для нас роли были инструментом, который мы изучили вдоль и поперёк и без труда подпиливали под себя. А коллеги из других проектов, не готовые тратить время на изучение документации и кода, воспринимали роли как готовую коробку, которая просто должна работать. Так появилась необходимость снижать порог входа инженеров в наши продукты. 

Фактически мы породили конвейер по производству БД в масштабах огромной инфраструктуры. Единственный способ выжить при таких объёмах — автоматизация и стандартизация.

Ansible

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

Изначальная схема

Для хранения переменных СУБД используем подход Infrastructure as Code - inventory хранится в git репозиториях.

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

/product
  /{env}
    /some-pgsql-cl1
      /group_vars
        /all
        /etcd_cluster
        /postgres_cluster
      hosts.ini
      playbook_postgres.yml
      playbook_haproxy.yml
      playbook_monitoring.yml
      requirements.txt

Плейбуки, переменные, указатели версий ansible-ролей хранились в одном месте. На первый взгляд удобно: при работе с кластером ставим нужные версии ролей, запускаем плейбуки и имеем прогнозируемый результат.

Но когда возникают задачи, требующие изменения большого числа кластеров, приходит боль. Точкой кипения стала задача переезда haproxy на новый комплект k8s кластеров — решение этой задачи начиналось с клонирования нескольких сотен репозиториев. Реализуемо, но приятного мало.

Закрыв эту задачу, мы засели за анализ и обновили подходы к хранению и применению изменений.

Ansible-роли

Изначально мы писали мега-роли, которые, например, целиком разворачивают кластер высокой доступности postgres (внутри etcd кластер, установка и настройка postgres, patroni, pgbouncer, pgbackrest и комплект экспортеров). Такой подход был неудобен, так как некоторые элементы нужны самостоятельно (например etcd кластер), да и тестирование усложняется. Сейчас пишем отдельные роли, которые имеют собственные тесты + интеграционный тест, который собирает из этого комплекта кластер высокой доступности.

Схема типового кластера Postgres
Схема типового кластера Postgres

Для облегчения работы и себе, и тем, кто пойдёт по нашему пути, мы сделали Role Guideline. Документация — это, конечно, хорошо. Но когда перед глазами есть пример правильного кода с пояснениями в стиле «Это правильно, потому что…», людям гораздо проще принять правоту и поэтапно следовать выбранному пути.

В нашем гайдлайне есть примеры тасок и тестов Molecule и даже объяснение, как затащить это в CI. Также задекларированы основные принципы, которые пересекаются с Ansible Best Practices:

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

  • Контроль ошибок. Роль должна завершить работу с кодом возврата failed при возникновении ошибки.

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

  • Идемпотентность. Роль не должна изменять сервис, вызывать перезагрузку или иные деструктивные действия в случае повторной прокатки.

  • Самодостаточность. Роль должна выполняться не только в совокупности с другими ролями, но быть полностью независима.

  • Тегирование. Каждый блок или задача должны иметь чёткий и понятный тег для вызова.

Это не прямое цитирование Ansible Best Practices: мы подкрепляем принципы кодом.

Все переменные обязательно имеют префикс в виде имени роли:

{{ rolename }}_ # обычные переменные ansible-роли
_{{ rolename }}_ # внутренние переменные ansible-роли
__{{ rolename }}_ # локальные переменные блока/цикла

{{ rolename }}_enable: false # переменная-активатор роли

С помощью префиксов мы изолируем переменные одной роли от другой, если они запускаются в рамках одного плейбука. Для переменной-активатора значение по умолчанию false: пока она не будет переопределена, все таски этой роли просто не пройдут по хостам. Это сделано, чтобы никто не пытался, например, развернуть PostgreSQL на нодах, предназначенных, например, для etcd-кластера. Без нее у меня пару раз такой фокус получался, но результат был невеселый.

Таски протегированы. Есть общие теги и теги с префиксом роли. Список общих тегов формализован. Благодаря этому человек, переходя от одного типа СУБД к другому, понимает, что нужно запустить, чтобы переконфигурировать весь кластер. Если инженер знает, что именно нужно поправить, он может переконфигурировать даже один компонент.

tags:
  - install
  - {{ rolename }}_install
  - config
  - {{ rolename }}_config

Также в гайдлайне есть пример настройки линтера ansible-lint, задача которого поддерживать стабильный уровень стресса разработчиков стиль написания кода.

Тесты

Роли обязательно покрыты тестами, для этого используется фреймворк Molecule — популярное решение, которое легко использовать и локально, и в пайплайнах.

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

Для проверки совместимости ролей с новыми версиями Ansible и Python мы используем библиотеку Tox. Она позволяет параметризировать версии зависимостей при запуске тестов.

В качестве драйверов используется Docker — самое простое решение для локальной разработки. Дополнительно мы написали драйвер для WB-Cloud, поэтому тесты проходят в условиях, максимально близких к боевым.

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

Изменения в ролях дополнительно тестируются в пайплайнах GitLab. Как раз там активно используется библиотека Tox. В базовом тесте мы перебираем версии Ansible — то есть мы уверены, что можем поднять основную версию ansible, не сломав роли:

# Фрагмент .gitlab_ci.yaml
molecule-test:
  stage: primary_test
  script:
    - ansible-lint -c .ansible-lint
    - molecule test --all

tox-tests:
  stage: additional_tests
  script:
    - ansible-lint -c .ansible-lint
    - tox -e ${ANSIBLE}

# Строим вот такие матрицы тестов:
parallel:
  matrix:
    - ANSIBLE: ['2.13', '2.14', '2.15', '2.16', '2.17']
      DEBIAN_CODENAME: [buster, bullseye, bookworm]
      PG_VERSION: [10, 11, 12, 13, 14, 15, 16]
    # PG 17 нет для Debian 10
    - ANSIBLE: ['2.13', '2.14', '2.15', '2.16', '2.17']
      DEBIAN_CODENAME: [bullseye, bookworm]
      PG_VERSION: [17]

Это тест Postgres exporter, который должен работать на разных версиях PostgreSQL, на разных версиях Debian и, для порядка, — ещё и на разных версиях Ansible. В данном случае получается 27 различных комбинаций, которые замечательно параллелятся по ранерам, если их много. Тест не занимает слишком много времени, но даёт спокойствие и уверенность в том, что забытая комбинация не всплывёт в неподходящий момент.

Плейбуки

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

# Плейбук разворачивания Postgres Ha Cluster
- name: Deploy ETCD
  hosts: etcd_cluster
  roles:
    - common
    - node_exporter
    - etcd
- name: Cluster PATRONI
  hosts: postgres_cluster
  roles:
    - common
    - swap
    - node_exporter
    - postgresql
    - pgbackres
    - pgbackrest_exporter
    - patroni
    - postgresql_rbac
    - postgresql_exporter
    - pgbouncer
    - pgbouncer_exporter

Процесс создания кластера БД или брокера

В самом начале был логичный подход — почитай роль, переопредели дефолты, собери переменные — и кластер БД готов. Это хорошее задание для onboarding, но для обычной работы такой подход не подходит — долго.

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

Мы взяли второй подход за основу и сделали единый шаблон с переменными БД, которые можно применять по принципу «Переименуй хосты и названия пользователей». Шаблон правильно сшивает переменные разных ролей, и его дефолтное поведение соответствует утверждённым политикам. Например, политике резервного копирования.

Ещё один неоспоримый плюс шаблона — он значительно снижает порог входа инженеров в работу. Это было нашей первой задачей.

Допустим, у вас есть инженер, который может читать ansible-код, но слабо представляет, что такое кластер ClickHouse. По подсказкам в переменных он может спокойно собрать шардированный кластер ClickHouse утвержденной конфигурации

Виды шаблонов БД и брокеров, которые поддерживает наша команда:

  • PostgreSQL

  • Clickhouse

  • MongoDB

  • Redis

  • etcd

  • Kafka

  • RabbitMQ

Шаблоны БД

Внутри шаблона ничего сверхъестественного. Есть переменные для создания окружения, то есть для создания виртуалок, генерации секретов и переменные сервиса. Внутренности шаблона Patroni выглядят так:

Путь

Назначение

./group_vars/all

Групповые переменные кластера

./group_vars/all/createvm.yml

ТТХ ВМ для создания

./group_vars/all/vault-secrets.yml

Секреты для генерации

./group_vars/etcd_cluster

Переменные etcd

./group_vars/postgres_cluster

Переменные postgres

./hosts

Список хостов в группах

./dependencies.dpn

Файл зависимостей. Подключает inventory k8s-кластеров, на которые будет залит haproxy

Параметры БД указываются интуитивно понятно. Тюнинг БД в общем приближении тоже осуществляется достаточно просто. В роли реализован функциональный аналог знаменитого PgTune. Мы получаем от разработчиков информацию, как они собираются пользоваться БД, и выбираем наиболее близкий вариант:

# Версия PostgreSQL
postgresql_version: 17

# Параметры тюнинга PostgreSQL
# dw, web, oltp, mixed, desktop
postgresql_tune_db_type: oltp

# Макс. число одновременных подключений к серверу БД
postgresql_params_connection:
  max_connections: 200

Чтобы определить параметры PostgreSQL, используются ansible-факты — получаем актуальную информацию с хостов и применяем коэффициенты в соответствии с выбранным типом нагрузки на БД.

Подход к написанию переменных

Скажем, нам нужно включить бэкап на PostgreSQL. Это три действия: ставим pgBackRest, меняем параметр archive_command и добавляем в планировщик задачу на снятие полных резервных копий. В первом варианте переменных есть целых три места, которые требуют активного действия и про которые можно забыть:

pgbackrest_install: true
pgbackrest_backup_cron_enable: true

# Раскомментировать нужный вариант
postgresql_parameters:
  # archive_command: "{{ pgbackrest_archive_push_command }}"
  # archive_command: "/bin/true"

Упрощаем, связываем переменные между собой:

pgbackrest_install: true

# Не трогать, само сообразит
pgbackrest_backup_cron_enable: "{{ pgbackrest_install }}"
postgresql_parameters:
  archive_command: | 
    {{ (pgbackrest_install | bool) | 
    ternary(
    pgbackrest_archive_push_command, 
    '/bin/true') 
    }}

Вспоминаем что по-умолчанию продакшн окружение обязательно бэкапится, тестовые по отдельному запросу:

# переопределить если нужен бэкап на dev/stage окружениях
pgbackrest_install: "{{ (env == 'prd') | ternary(true, false) }}"

# Не трогать, само сообразит
pgbackrest_backup_cron_enable: "{{ pgbackrest_install }}"
postgresql_parameters:
  archive_command: | 
    {{ (pgbackrest_install | bool) | 
    ternary(
    pgbackrest_archive_push_command, 
    '/bin/true') 
    }}

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

Монолитный инвентарь VS монолитный репозиторий

Самую большую работу мы проделали с хранением всех переменных. В Ansible интуитивно хочется сделать один большой инвентарь на все БД, чтобы одно изменение расходилось по всем базам.

Мы нашли сразу три аргумента против:

  1. Инфраструктура Wildberries активно растёт и будет расти дальше.

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

  3. Цена ошибки при едином инвентаре — все БД компании, что очень больно и дорого.

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

Монолитный репозиторий легко масштабируется, изменения вносятся на уровне одного сервиса и в случае ошибки тоже страдает только один сервис. Сказка! Как её реализовать?

Структура директорий:

Путь

Назначение

/_all/

Переменные всего инвентори

/envs/{env}/

Переменные конкретного окружения

/{project}/all/

Переменные проекта

/{project}/{product}/all/

Переменные продукта

dependencies.dpn

Инклуд дополнительных inventory

/{project}/{product}/{env}/{service}

Переменные сервиса
(в данном случае БД)

Запуск ansible для сбора переменных в правильном порядке:

ansible-playbook
	-i all
	-i envs/{env}
	-i {project}/all
	-i {project}/{product}/all
	-i {project}/{product}/{env}/{service} 
	путь_до_плейбука

Изоляция переменных на практике

Скрытый текст

Допустим, у нас в проекте два инженера, и они получают задание: инженер А должен настроить кластер Redis, инженер Б — кластер PostgreSQL. Для наглядности раскрасим их рабочие зоны:

Они открывают структуру своего проекта и начинают работать в переменных БД. У инженера А аnsible работает как в переменных кластеров, так и в глобальных переменных. Зона работы первого инженера выделена фиолетовым:

У инженера Б задача чуть сложнее. Кроме всего прочего, для кластера PostgreSQL ему нужно залить HAProxy в K8s. Инженер пишет файл dependencies.dpn, который наша автоматика обрабатывает и к инвентарю которого подключает ещё инвентарь K8s. Команда запуска будет выглядеть следующим образом

Файл dependencies.dpn:

dependsOn:
  - inventory: ../k8s-namespace
    useInPlaybook: true
    affectsOnChanges: true

Рабочая область инженера Б:

Итоговая команда запуска плейбука:

ansible-playbook
	-i all 
	-i envs/{env} 
	-i {project}/{product}/all
	-i {project}/{product}/{env}/k8s-namespace
	-i {project}/{product}/{env}/{service} 
	путь_до_плейбука

Далее А и Б идут задавать результаты своей работы, делают мердж-реквесты, и все изменения находятся ровно в двух директориях — конфликтов слияния нет, работа идёт параллельно.

Что мы ещё учли? Что инженеры могут выполнять задачи на ПК с разными ОС или в Gitlab CI, а нам нужно получать абсолютно одинаковые результаты при передаче работы над кластером от инженера к инженеру. Очевидное решение — упаковать все зависимости в docker-контейнер, где есть нужные версии ролей, и окружающие ПО.

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

Автоматизированная работа с ansible переменными

Когда мы хотим применить что-либо на всём инвентаре, нужно чётко понимать, какие действенные значения переменных будут применяться к хостам. Первый вариант, который приходит в голову, — парсить YAML. Вполне реализуемо, но до первых грабель.

Варианты написания переменных

Ansible позволяет писать inventory и в ini формате и в yaml. И все это можно сочетать. Плюс поддерживаются диапазоны — первые грабли на пути прямого парсинга yaml и ini файлов.

# ini формат

[custom_group]
host1 host1_var=host1_val
# диапазоны хостов цифровые
host_range_[01:50]
# диапазоны хостов символьные
host_range_[a:z]
[custom_group:vars]
test_var=A
# YAML формат
---
custom_group:
  hosts:
    host1:
      host_var: host_val
    # диапазоны хостов цифровые
    host_range_[01:50]:
    # диапазоны хостов символьные
    host_range_[a:z]:
  vars:
    test_var: A

Как в ansible работает наследование

Если нужна продвинутая логика, определяем, какое действующее 

значение у переменной применится к определённому хосту. У Ansible хорошо описан порядок наследования. Посмотрим, как он работает:

[parent_group:vars]
test_var=P
[parent_group:children]
middle_b_group
middle_a_group

[middle_a_group:vars]
test_var=A
[middle_a_group:children]
child_group

[middle_b_group:vars]
test_var=B
[middle_b_group:children]
child_group

[child_group]
host

Есть группа верхнего уровня, две группы среднего уровня и группа нижнего уровня. На группах среднего уровня определена переменная test_var. Как думаете, какое значение будет в нижней группе? «B»! А почему?

Всё банально — «B» стоит дальше по алфавиту. Что дальше, то и применяется. Но вы же помните, что беда и счастье Ansible в его гибкости? Переопределяем переменную ansible_group_priority, где по умолчанию стоит единица, и теперь при разрешении конфликтов применяется переменная именно из этой группы:

[parent_group:vars]
test_var=P
[parent_group:children]
middle_b_group
middle_a_group

[middle_a_group:vars]
test_var=A
ansible_group_priority=2
[middle_a_group:children]
child_group

[middle_b_group:vars]
test_var=B
[middle_b_group:children]
child_group

[child_group]
host

Считывание переменных при помощи ansible-inventory

Хочешь автоматизировать работу с переменными ansible — думай как ansible. Ещё лучше — заставь ansible думать за себя. Наша команда так и поступила.

У ansible есть замечательный инструмент ansible-inventory. Он даёт отрендерить всю портянку инвентарей и показать их такими, какими их видит ansible. Вот все группы:

# ansible-inventory -i hosts --export --list    
{ "_meta": {
  "hostvars": {
    "hostname": { словарь переменных хоста } }}
"all": {"children": [ список дочерних групп ],
  "hosts": [ список хостов ],
    "vars": { словарь переменных }},
"custom_group": {
  "children": [ список дочерних групп ],
    "hosts": [ список хостов ],
      "vars": { словарь переменных }}}

С помощью ansible-inventory мы рендерим понятную структуру, которая растёт в прогнозируемых направлениях. Если добавляется группа, она просто становится ключом первого уровня. Все ссылки на дочерние группы и хосты — это обычные листы с переменными и словарями, которые легко поддаются программированию и не зависят от стиля написания переменных. На базе этой идеи у нас родился новый проект.

Наше детище — Inventory Housekeeper

Под капотом Python с использованием компонента ansible-runner — библиотеки, которая разрабатывается для Ansible AWX. Она позволяет нормально взаимодействовать с ansible, асинхронно запускать плейбуки и таски, получать от них коды возвратов и собирать ansible-inventory.

Наш Inventory Housekeeper решает стандартные задачи по считыванию и работе с переменными. Дополнительно мы реализовали дамп переменных обратно на диск и научили его лазить в HashiCorp Vault, так как появилась практическая задача. Инструмент идеально подходит для миграций переменных. Например, решали задачу перевода всех БД PostgreSQL на новый postgres-exporter. Также инструмент хорошо применим для выполнения задач от нашей уважаемой службы Информационной Безопасности, откуда часто приходят задачи, требующие сложной логики в реализации.

Мы также подумали: если мы умеем менять переменные, почему бы не создавать новые БД таким же образом? На старте у нас имеется немного исходных данных от проекта, которые вписываются в простую структуру и превращаются в веб-форму. Что мы с ними делаем? Берём шаблон БД и данные из заявки, обогащаем их системными данными (name conventions, свободная зона доступности и пр.) и скармливаем Inventory Housekeeper. На выходе получаем заполненные переменные новой БД.

Изменение кластера БД происходит по той же схеме, но с одним отличием: вместо шаблона мы считываем переменные уже существующего кластера. Просто и красиво.

Дальше мы поняли, что возможности Housekeeper куда шире работы с БД. Если новый сервис (не база данных) автоматизируется через ansible, подходит под наши методы и имеет шаблон, Housekeeper также может генерировать нужные переменные для него.

Inventory CI

Когда мы сделали переменные кластера, они попадают в GitLab CI. Происходит проверка по принципу «Доверяй своим инженерам, но проверяй». Потом мы добавляем таргеты в мониторинг и деплоим HAProxy.

При изменения в репозитории запускается сканнер, который определяет в каких БД произошли изменения и запускает пайплайны только по этим БД:

Тут вы видите, как некий Андрей Аксенов делал тестовые БД:

Сканер изменений также считывает ветки в Hashicorp Vault, которые необходимы для выполнения этой операции. Пользователь GitLab сопоставляется с пользователем HashiCorp Vault, и запускается проверка, действительно ли пользователь имеет доступ в эти ветки. Результатом будет либо прокатка, либо красный свет.

Дальнейшее развитие системы

Мы уже придумали, как использовать весь потенциал максимально простых инструментов (Ansible, GitLab CI) для большой инфраструктуры. Снижение порога входа позволило подключить большое количество инженеров к работе с БД для выполнения типовых операций. Инвентарь может расти вширь практически неограниченно. Благодаря тому, что у нас изолированы переменные БД, мы в любой момент можем их оперативно собрать, обработать и перенести в другой инвентарь — вдруг старый надоест :)

В ближайших планах — полная автоматизация всех типовых операций за счет генерации ansible-переменных. На горизонте — полный отказ от ручного редактирования переменных и интеграция в разрабатываемую инфраструктурную платформу.

Если у вас остались вопросы или вы хотите поделиться мнением — добро пожаловать в комментарии!


Разборы, новости, экспертиза наших разработчиков и вакансии — в телеграм-канале, подписывайтесь!

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

Комментарии (4)


  1. lockedpid
    03.09.2025 11:45

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


  1. barazbay
    03.09.2025 11:45

    Каким образом осуществляется поддержка старых проектов? Например, сделали роль, запилили переменных, inventory housekeeper теперь для новых выкаток безупречно генерирует новые переменные, а для старых проектов как? Нужно их сгенерировать и внедрить в проект, например, захотели pgbackrest поменять на wal-g, взлетело и теперь нужно раскатить везде и переменные по-хорошему должны быть там, где готовится вскоре раскатка, но не должны быть там, где это изменение еще нужно запланировать заранее, согласовать и внедрить, например, критичный проект.


    1. maniak26 Автор
      03.09.2025 11:45

      inventory housekeeper может считать и как шаблон и как переменные реального кластера.
      По факту он с подобной трансформации и начался - надо было кастомный postgres_exporter поменять на другую реализацию postgresql_exporter. Плюс, на хосты где включены бэкапы, поставить pgbakrest_exporter.
      Получилась такая схема работы
      1. читаем переменные кластера
      2. Если кластер подходит под изменение, прогоняем подготовочный плейбук (гасит и сносит старый экспортер).
      3. Патчим переменные (уже на уровне python dict) - удаляем переменные старой роли postgres_exporter, добавляем необходимые переменные postgresql_exporter и pgbackrest_exporter.
      4. Дампим измененные переменные
      5. Прогоняем новые роли postgresql_exporter и pgbackrest_exporter.

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


      1. barazbay
        03.09.2025 11:45

        занятно)

        я пришел к тому, что у меня все переменные хранятся в host_vars в файликах с названиями ролей, но там есть еще префиксы - отдельно для хранения зашифрованных переменных, отдельно для переменных, которые нежелательно перезаписывать (postgresql.conf, pg_hba.conf, patroni.yml) и сделал сначала генерировалку таких конфигов из ansible template в jinja2, но во второй итерации сделал на bash через наивный cat eof + немного логики для hba. Каждый кластер это отдельный гит репо, инвентори в нем единственный и простой - hosts.ini с условно 2-3 пг, 3 етсд и еще дблаб, то есть даже две разные среды одного логического проекта - это два репозитория. Ну вот, генерировалка генерирует проект со всеми переменными, создает гит репо и в проекты, которые уже существуют тоже может сгенерировать переменные для новых проектов или поправить те файлы, которые можно редактировать. Просто делюсь "как я решал похожую проблему". Тож столкнулся с проблемой, что мои роли воспринимают как коробку, а теперь и эти вспомогательные баш скрипты тоже так же воспринимают(