- BrainTools - https://www.braintools.ru -

350 коммитов в неделю: как контрибьютить в проект, который меняется быстрее, чем ты пишешь

350 коммитов в неделю: как контрибьютить в проект, который меняется быстрее, чем ты пишешь - 1

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

Привет! Я Антон Воронцов, OpenSRE [1].

OpenSRE — это инструмент для построения ИИ SRE-агентов, которые расследуют и разрешают production-инциденты, работая внутри вашей инфраструктуры. По сути это хелпер-автоматизатор для SRE. 

Эта статья о том, как я впервые контрибьютил во внешний проект, что стало самым сложным и как вообще работает этот инструмент. 

Дисклеймер. OpenSRE находится в стадии public-alpha. Мейнтейнеры прямо пишут, что не гарантируют безопасность работы. Мы тоже не можем гарантировать, что у вас ничего не сломается. Эта статья — рассказ об эксперименте, а если решите попробовать, то внимательно посмотрите, чем именно вы пользуетесь и с какими правами вы это запускаете. У аккаунта, от имени которого вы запускаете OpenSRE, не должно быть модифицирующих ролей.

Как я начал контрибьютить

Вообще мне самому хотелось создать такой инструмент, но, когда я нашёл уже существующий, появилась идея подружить его с Yandex Cloud. 

Всё потому, что у OpenSRE есть три ценных режима:

  • Помощник по инфраструктуре. Агент помогает разобраться, что где развёрнуто, как связано и куда смотреть. 

  • Второй пилот на инциденте. Вы — лид расследования, а агент подсовывает гипотезы по обработанным данным и список того, что стоит проверить.

  • Автономный триаж. Агент запускается по алерту сам и к моменту, когда вы откроете ноутбук, уже отвечает на первые вопросы: что деградировало первым, наша это проблема или провайдера, какой радиус поражения. Эта информация сокращает время поиска причины. 

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

Естественно, первым делом идею подружить OpenSRE и Yandex Cloud я закинул коллегам. Мы плотно работаем с различными информационными системами клиентов, поэтому стараемся находить новые и эффективные инструменты. В общем, коллеги дали добро, и мы решили посмотреть, что вообще из этого получится.

Мейнтейнеры общаются в Discord, куда я и пришёл. Сначала на моё предложение они отреагировали достаточно сухо и предложили делать 3rd-party интеграцию в виде отдельного плагина. 

Окей, делаем плагин.

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

Например, регистрация, которую никто не видит. Первая версия при регистрации пересобирала таблицы сервисов:

SUPPORTED_VERIFY_SERVICES = [s.service for s in INTEGRATION_SPECS if s.has_verifier]

Казалось бы, работает: opensre integrations list показывал новый сервис. Но читатели берут список так:

from integrations.registry import SUPPORTED_VERIFY_SERVICES

то есть привязываются к объекту, а не к имени. А модуль integrations.verify импортируется задолго до того, как плагин успевает зарегистрироваться. Присваивание создаёт новый список, старый остаётся у всех, кто уже его импортировал. Интеграция настраивается, а integrations verify утверждает, что её не существует.

Лечение оказалось довольно простым:

SUPPORTED_VERIFY_SERVICES[:] = [s.service for s in INTEGRATION_SPECS if s.has_verifier]

Следом тот же баг вскрылся во втором подходе: integrations.app берёт список не из реестра, а переимпортирует его из integrations.verify.

Ещё один баг заключался в окне с пустой таблицей. Производные словари обновлялись очевидным способом:

SERVICE_FAMILY_MAP.clear()
SERVICE_FAMILY_MAP.update(families)

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

def refillmapping(target, source):

  target.update(source)

  for key in [key for key in target if key not in source]:

    del target[key]

Остальные находки были того же рода: 

  • перечень сервисов для мастера настройки собирался кортежем на импорте и не видел поздних плагинов; 

  • отдельные ворота в cmd_setup убивали незнакомое имя, даже когда оно уже было в списке; 

  • резолвер отбрасывал неизвестные ключи до валидации, и настроенная интеграция исчезала без ошибки [2]

  • исключение из предиката плагина роняло старт CLI вместо того, чтобы свестись к «не настроено»; 

  • спецификация могла занять ключ встроенной интеграции через алиас и перекрыть, например, aws.

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

Швы сделали интеграцию регистрируемой, но не сделали плагин загружаемым: register_* функции есть, но вызывать их было некому. В моей реализации плагина была обёртка opensre-yc. Это точка входа пакета для кастомного визарда: opensre-yc configure, opensre-yc run <аргументы opensre> — обёртка для регистрации интеграции, тулов и алертов. Костыль? Определённо, но для PoC плагина подходит идеально.

Самый неприятный блокер плагина — в ядре не было механизма обнаружения интеграций. Значит, opensre-yc — не временное неудобство, а навсегда. В середине августа я спросил мейнтейнеров в Discord про механизм обнаружения плагинов и показал текущий статус opensre-yc-plugin [3]. Ребята ответили, что пофиксят обнаружение плагинов в течение недели и сказали: делай не 3rd-party интеграцией, а сразу в integrations/ OpenSRE.

Разработка модуля

Создание интеграции в main сняло сразу четыре проблемы: обёртка не нужна, механизм обнаружения теперь тоже не блокер, удобнее следить за изменениями апстрима и есть бесплатный настроенный CI, который вас будет проверять. Нативная поддержка развязала руки, большая часть ручек и тулов уже была написана в плагине. Осталось самое трудное — приземлить это всё в main и не сломать ничего чужого.

За первый час я не написал ни строчки — читал карту репозитория:

  • AGENTS.md — структура директорий и раздел «Adding an Integration»;

  • docs/adding-tools-and-integrations.md — полный список того, что придётся затронуть;

  • docs/ARCHITECTURE.md — правила «слоёв»;

  • docs/tool-placement-policy.md — где должен жить инструмент: рядом с вендором, в системных или в кросс-вендорных каталогах.

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

Дальше я решил копировать: выбрал интеграцию, похожую по форме на то, что мне нужно и прочитал её целиком, от конфигурации до тестов. У этой интеграции довольно понятная структура: нормализация конфигурации и строгая модель полей, функция classify, которая превращает сохранённые креды в рабочий конфиг, верификатор для integrations verify, клиент к API, мастер настройки, регистрация в каталоге и env-загрузчике — и только потом сами инструменты.

Инструменты я считал самой простой частью работы. Сначала так и было. Регистрировать ничего не нужно: реестр сам подхватывает модули из tools/. Декоратору хватает четырёх вещей: имя, описание, схема входа и поверхность, на которой инструмент виден агенту. Инструмент, который только читает данные, я написал за вечер.

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

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

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

Дольше всего я разбирался с невидимыми договорённостями: их много, они разбросаны и почти все всплывают только на ревью или в CI. Описание инструмента должно удовлетворять контракту качества, а каждый новый инструмент нужно классифицировать в тесте телеметрии. Имена модулей и классов проверяются отдельным сторожем на «зонтичные» слова. Env-переменные обязаны лежать в config/constants/, а не рядом с кодом, который их читает, иначе получается циклический импорт. 

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

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

Большую часть находок дали прогоны против настоящей инфраструктуры. У Managed Greenplum нет коллекции hosts. Запрос отдаёт 404, и кластер возвращается без единого адреса — снаружи не отличить от кластера, у которого хостов действительно нет. Остановленные кластеры инструмент записывал в сломанные. Их выключили намеренно, но в ответе это никак не отличалось от отказа. А метрику я построил по подсказке нашего же инструмента, и она не совпала ни с чем: список меток он отдавал по всей папке, а не по сервису, о котором спрашивали.

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

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

Саму интеграцию я писал с Claude Code, но тут важны уточнения. Так как мой агент не имеет нативного доступа к документации (в хорошем смысле, спасибо Антироботу) и не знает, как у нас внутри устроена работа с некоторыми сервисами — всю логику [4] нужно писать самостоятельно. Чтобы пользоваться инструментом, я сначала учил его тому, что знаю сам. Плюс я достаточно неплохо знаю Python, что тоже здорово помогло в начале.

Всё, что пошло в код интеграции, я перепроверил вручную — и оно работало. Когда пришло время заливать в main, то код остался практически неизменным, за исключением нескольких фишек по структуре интеграций и правил написания, о которых говорит только CI в момент создания PR. При этом сами мейнтейнеры активно помогали «допинать» PR до слияния. Они пару раз фиксили за мной и спрашивали после мержа: «Можешь перепроверить, не сломали ли наши доработки вообще в целом твой модуль?»

Самое сложное оказалось не в написании интеграции

Нашёлся один неожиданный блокер. В проекте не было и не планировалось поддержки Yandex AI Studio как LLM-провайдера — оно и понятно, ребятам это просто не нужно. Блокером я назвал это потому, что по моей задумке весь контур инструментов должен был остаться внутри Yandex Cloud.

Зато у одного разработчика из Индии висел PR на поддержку кастомных OpenAI-совместимых эндпоинтов, через которые может работать Yandex AI Studio. Было непросто, но я его допинал, и этот мини-блокер был снят. В опенсорсе задача может упираться в чужой незакрытый PR и быть сильно связана с коммуникацией.

К тому же апстрим не стоял на месте, пока я писал: в OpenSRE шло по 350 коммитов в неделю — дважды это задело мой код. 

  1. Сначала переехали импорты фреймворка инструментов: мелочь на две строки, из-за которой перестал применяться подготовленный набор патчей. 

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

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

Итоги создания интеграции

350 коммитов в неделю: как контрибьютить в проект, который меняется быстрее, чем ты пишешь - 2

18 августа мейнтейнеры влили в main PR с инструментами интеграции, и Yandex Cloud появился в release notes — v0.1.2026.8.19, а следом вv0.1.2026.8.27. Для меня это уже победа: интеграция перестала быть моим форком и стала частью продукта, который развивают другие люди.

Что работает сегодня

1 сентября в интеграции появились управляемые базы данных: здоровье кластеров, роли хостов, история операций и собственный лог кластера. 

Есть ещё девять инструментов в пяти направлениях: 

  • метрики — запрос и разведка; 

  • Cloud Logging — чтение и список групп; 

  • Compute Cloud — список и диагностика через серийную консоль; 

  • Healthchecks NLB/ALB;

  • generic-читатель — поиск по индексу и вызов любого из 951 read-эндпоинта на 69 сервисах. 

Только чтение: исключительно GET и белый список путей. Четыре режима аутентификации, включая метадата-сервис, — на виртуальной машине в облаке настройка сводится к одной команде. Модель подключается к Yandex AI Studio через штатный OpenAI-совместимый провайдер.

Планы по интеграции. Поддержка Managed Service for Kubernetes, serverless и аудит, а в конце — визард онбординга, чтобы подключение не требовало правки переменных вручную.

OpenSRE в работе

Мы делали воркшоп [5] по надёжности на HighLoad++, в котором было три кейса: участники чинили то, что мы сломали в изолированной инфраструктуре. Я подумал скормить в OpenSRE с поддержкой Yandex Cloud один из кейсов и сравнить качество рассуждений и время, за которое получится найти правильное решение. 

Кейс тривиальный: кластер Managed Service for Kubernetes, в котором крутится приложение todo (заметки) и база данных Managed Service for PostgreSQL, с которой общается бэкенд приложения [6]. Мы сделали принудительный фейловер мастера. 

Приложение, согласно ConfigMap, смотрит на конкретный узел для записи и чтения, используя FQDN конкретного хоста. Указанный хост перестал быть мастером и стал репликой. В итоге приложение продолжает корректно читать, но не может записать даже access logs. На поиск и применение реального решения у ребят на воркшопе ушло в среднем 14 минут. Из всех участников правильно решили кейс 30% участников, решили с оговоркой (вылечили симптом) 70%.

Посмотрим, как с этим справился OpenSRE. Настроена только интеграция с Yandex Cloud и Kubernetes. Для проверки использовал следующие модели, доступные в Yandex AI Studio:

  • qwen3-235b-a22b-fp8;

  • gpt-oss-120b;

  • deepseek-v4-flash.

В видео продемонстрирован прогон deepseek-v4-flash. Всего было девять замеров (по три с каждой моделью). Каждый раз я очищал контекст сессии и выключал память [7] флагом OPENSRE_MEMORY_DISABLED=1. Я ничего не говорил в сессии заранее о структуре проекта и его ресурсах. Использовал следующий промпт:

В k8s кластере main-stand-1 приложение todo читает, но не пишет: список задач открывается, а новая не сохраняется. Разберись, в чём причина, и скажи, какие действия нужно выполнить, чтобы исправить.

Для прозрачности прогонов записал демо:

Ход разбора

OpenSRE начал с осмотра: kubernetes list deployments, list pods, list services, затем list_yc_k8s_clusters — то есть сверил рабочую нагрузку с состоянием самого кластера MK8s со стороны облака. Зафиксировал: приложение состоит из фронтенда и бэкенда.

Дальше события и логи: kubernetes get events, потом kubernetes get pod logs дважды. Сам нашел, что бэкенд отвечает 200 и SELECT true проходит. Вывод из этого сделан правильный — связь с базой есть, значит, ломается не соединение, а запись.

Затем kubernetes get resource дважды — это Deployment и, судя по следующему шагу, ConfigMap. Потом переход в облако: list_yc_db_clusters находит кластер PostgreSQL, get_yc_db_cluster даёт хосты с ролями и недавние операции, kubernetes list configmaps — конфигурацию бэкенда.

Смычка сформулирована им дословно: DB_HOST жёстко прописан на rc1a-…, а по данным кластера этот хост сейчас REPLICA, мастер rc1b-….

Последний шаг — проверка вывода: read_yc_db_logs дважды, и в логах самой базы нашлось cannot execute INSERT in a read-only transaction

Инструменты. Одиннадцать видимых вызовов, около пятнадцати с учётом свёрнутых. Шесть кубовых и четыре наших: list_yc_k8s_clusters, list_yc_db_clusters, get_yc_db_cluster, read_yc_db_logs. Ни одного лишнего похода в файловую систему, ни одной попытки взять kubectl.

Рекомендация. DB_HOST на c-c9qb57nb3mjmetdo9g6p.rw.mdb.yandexcloud.net, RO_DB_HOST на .ro., перезапуск подов сделает Reloader сам, потом проверить записью новой задачи. Плюс отдельной строкой: «я только читал ресурсы, ничего не менял».

Про Reloader он нигде не спрашивал — вычитал аннотацию reloader.stakater.com/auto из Deployment на шаге get resource. Это единственное место, где он вышел за пределы прямого чтения и что-то вывел, причём сделал это верно.

На весь прогон ушло 2 минуты и 8 секунд. В это время включены: вызов, использование тулов, ожидание ответа от LLM-провайдера. Решение было правильным, как я и задумывал в самом кейсе.

Статистика всех прогонов:

Модель

Время (среднее)

Результат

qwen3-235b-a22b-fp8

4 минуты 15 секунд

два из трёх решений были верны. Только один прогон дал верный ответ про RW/RO sFQDN [8]

gpt-oss-120b

3 минуты 52 секунды

все три прогона оказались успешными, решения про sFQDN [8]не было.

deepseek-v4-flash

2 минуты 30 секунд

три прогона дали правильное решение с первого раза

Замеры в процессе написания кода и тестов показали, что половина результата зависит от самой модели. Причём дороже != лучше. 

Статистика использования токенов трёх прогонов deepseek-v4-flash (по тарифам Yandex AI Studio):

Тип

Стоимость в рублях 

Ставка за 1000 

Количество токенов

Входящие

29,99

0,3 ₽

~100 000

Кешированные

35,46

0,075 ₽

~473 000

Исходящие

2,82

0,5 ₽

~5 600

Итого

68,27

~578 000

Сравним результаты решений реальных людей и бездушной машины (суммарно):

Кто

Критерий успешности

Время (среднее)

Процент успешности

Группа инженеров

Кейс решён верно

12 минут 20 секунд

30% 

Группа инженеров

Кейс решён

14 минут 20 секунд

70% 

OpenSRE

Кейс решён верно

2 минуты 56 секунд

44% 

OpenSRE

Кейс решён

3 минуты 32 секунды

88% 

Выводы

Скорость — самое очевидное и наименее интересное. Агент не устаёт, не отвлекается и читает пятнадцатый источник подряд ровно с той же тщательностью, что и первый. 

Разброс между моделями оказался больше, чем между агентом и людьми. gpt-oss-120b находил причину в трёх прогонах из трёх и ни разу не предложил устойчивое лечение, каждый раз советуя прибить конфигурацию к новому мастеру. То есть повторял ровно ту ошибку, за которую мы не засчитывали кейс участникам воркшопа. deepseek-v4-flash на тех же инструментах, том же промпте и том же кластере дал правильную рекомендацию три раза из трёх, при этом оказавшись самым быстрым и самым дешёвым из трёх. 

Фраза «мы внедрили ИИ-агента» не означает почти ничего; значение имеет, какую модель вы под него подставили. Большинство ошибок было не связаны с работой модели. 

Пока я готовил прогоны, агент трижды выдал уверенно неверный вывод:

  • Он объявил, что подов бэкенда не существует, потому что придумал селектор app=todo-backend, тот ни с чем не совпал, а инструмент вернул пустой список, ничем не отличимый от «в неймспейсе пусто». Четыре пода в этот момент работали.

  • Он посоветовал вписать в конфигурацию имя конкретной машины, потому что поле называлось host, а долгоживущий адрес лежал рядом под менее очевидным именем.

  • Он диагностировал несуществующую поломку TLS, потому что рядом стояли port_is_tls: true и фраза «публичные хосты требуют TLS», а приложение было настроено на sslmode=disable. При этом оно в тот момент успешно читало из базы по тому самому соединению, которое агент назвал сломанным.

Во всех трёх случаях лечение было одинаковым и не имело отношения к выбору модели: инструмент должен называть свою неудачу.

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

Что агент сделал лучше людей: дойдя до гипотезы «хост стал репликой», он не остановился, а полез в логи базы и вытащил дословное cannot execute INSERT in a read-only transaction. Диагноз перестал быть рассуждением. Участники, которые «переключили мастер», причину тоже поняли — они не проверили следствие и вылечили симптом. 

Этот эксперимент не показывает некоторых моментов. Агент работает только на чтение: он выдаёт готовые команды, но применяет их человек. К тому же его результат не был стабильным — одинаковый вопрос на одинаковых данных давал ответы разного качества. Для продакшена смотреть надо не на лучший прогон, а на худший. У qwen3-235b-a22b-fp8 из трёх прогонов правильная рекомендация была в одном, но узнал я это только потому, что прогнал несколько раз.

Что по итогу

Контрибьютить оказалось не так страшно и тяжело, как могло казаться. Мой опыт [9] до этого ограничивался проектами в нашем репозитории с примерами для Yandex Cloud. OpenSRE — это первый публичный проект, в который я зашёл всерьёз. 

Этим я фактически подписался на поддержку интеграции: чтение issues, ответы на вопросы и починку того, что отвалится после следующего рефакторинга (в том числе со стороны провайдера Yandex Cloud). Скажу честно: слепо полагаться на интеграцию я бы пока не стал. Но увидеть, что она даёт, уже можно — и этого достаточно, чтобы решить, стоит ли идти дальше. Я решил, что стоит.

А если вы хотите увидеть, что умеем мы в Yandex Cloud — загляните в наш канал. Там мы рассказываем о том, как устроены наши системы под капотом и какие ещё эксперименты мы проводим. 

P. S.

Ситуация с момента подготовки статьи изменилась: приём внешних PR в OpenSRE сейчас фактически приостановлен. С интеграцией мне повезло попасть в окно, когда он был открыт, но самый актуальный код живёт у меня в форке. То, что показано на видео, я проверял на upstream-релизе v0.1.2026.9.6 — там оно работает; на более новых сборках не проверял.

Автор: nowhere_in_space

Источник [10]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/35300

URLs in this post:

[1] OpenSRE: https://github.com/tracer-cloud/opensre

[2] ошибки: http://www.braintools.ru/article/4192

[3] opensre-yc-plugin: https://github.com/nowhere-in-space/opensre-yc-plugin

[4] логику: http://www.braintools.ru/article/7640

[5] воркшоп: https://highload.ru/spb/2026/abstracts/17567

[6] бэкенд приложения: https://github.com/yandex-cloud-examples/yc-demo-todo-application

[7] память: http://www.braintools.ru/article/4140

[8] sFQDN: https://yandex.cloud/ru/docs/managed-postgresql/operations/connect/fqdn#fqdn-master

[9] опыт: http://www.braintools.ru/article/6952

[10] Источник: https://habr.com/ru/companies/yandex_cloud_and_infra/articles/1080524/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1080524

www.BrainTools.ru

Rambler's Top100