
Представьте: ночной алерт, приложение отдаёт пятисотки, но само оно живо. Понятно, что дальше начнётся знакомое — вкладки с графиками, поиски в логах и попытки понять, кто и что изменил. Сколько на это обычно уходит времени? А что, если вместе с вами в инциденте будет разбираться ИИ-агент?
Привет! Я Антон Воронцов, CRE в Yandex Cloud. В конце июля я в очередной раз листал свежие репозитории на GitHub, чтобы посмотреть, что происходит в моих смежных дисциплинах: SRE, автоматизациях и, понятное дело, ИИ. Вдруг я наткнулся на один интересный репозиторий, который активно рос: количество звёзд, коммиты и заинтересованные люди из разных стран — всё это про OpenSRE.
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убивали незнакомое имя, даже когда оно уже было в списке;резолвер отбрасывал неизвестные ключи до валидации, и настроенная интеграция исчезала без ошибки;
исключение из предиката плагина роняло старт CLI вместо того, чтобы свестись к «не настроено»;
спецификация могла занять ключ встроенной интеграции через алиас и перекрыть, например, aws.
Каждый случай закреплён отдельным тестом с говорящим именем — чтобы следующий, кто тронет реестр, узнал о проблеме от CI, а не от пользователя.
Швы сделали интеграцию регистрируемой, но не сделали плагин загружаемым: register_* функции есть, но вызывать их было некому. В моей реализации плагина была обёртка opensre-yc. Это точка входа пакета для кастомного визарда: opensre-yc configure, opensre-yc run <аргументы opensre> — обёртка для регистрации интеграции, тулов и алертов. Костыль? Определённо, но для PoC плагина подходит идеально.
Самый неприятный блокер плагина — в ядре не было механизма обнаружения интеграций. Значит, opensre-yc — не временное неудобство, а навсегда. В середине августа я спросил мейнтейнеров в Discord про механизм обнаружения плагинов и показал текущий статус opensre-yc-plugin. Ребята ответили, что пофиксят обнаружение плагинов в течение недели и сказали: делай не 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, но тут важны уточнения. Так как мой агент не имеет нативного доступа к документации (в хорошем смысле, спасибо Антироботу) и не знает, как у нас внутри устроена работа с некоторыми сервисами — всю логику нужно писать самостоятельно. Чтобы пользоваться инструментом, я сначала учил его тому, что знаю сам. Плюс я достаточно неплохо знаю Python, что тоже здорово помогло в начале.
Всё, что пошло в код интеграции, я перепроверил вручную — и оно работало. Когда пришло время заливать в main, то код остался практически неизменным, за исключением нескольких фишек по структуре интеграций и правил написания, о которых говорит только CI в момент создания PR. При этом сами мейнтейнеры активно помогали «допинать» PR до слияния. Они пару раз фиксили за мной и спрашивали после мержа: «Можешь перепроверить, не сломали ли наши доработки вообще в целом твой модуль?»
Самое сложное оказалось не в написании интеграции
Нашёлся один неожиданный блокер. В проекте не было и не планировалось поддержки Yandex AI Studio как LLM-провайдера — оно и понятно, ребятам это просто не нужно. Блокером я назвал это потому, что по моей задумке весь контур инструментов должен был остаться внутри Yandex Cloud.
Зато у одного разработчика из Индии висел PR на поддержку кастомных OpenAI-совместимых эндпоинтов, через которые может работать Yandex AI Studio. Было непросто, но я его допинал, и этот мини-блокер был снят. В опенсорсе задача может упираться в чужой незакрытый PR и быть сильно связана с коммуникацией.
К тому же апстрим не стоял на месте, пока я писал: в OpenSRE шло по 350 коммитов в неделю — дважды это задело мой код.
Сначала переехали импорты фреймворка инструментов: мелочь на две строки, из-за которой перестал применяться подготовленный набор патчей.
Потом мейнтейнер при мерже перестроил уже принятый код: четыре пакета схлопнулись в один, пять точек регистрации стали одной. Перестройка была правильной — разделение имело смысл в плагине и мешало в апстриме — но следующую пачку PR пришлось планировать заново.
Заметка для тех, кто соберётся писать свою интеграцию: сверка с апстримом — важный отдельный этап планирования. Перед началом переноса посмотрите дифф за последние недели: не переехали ли импорты, не изменилась ли структура, не сделал ли кто-то часть вашей работы.
Итоги создания интеграции

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 в работе
Мы делали воркшоп по надёжности на HighLoad++, в котором было три кейса: участники чинили то, что мы сломали в изолированной инфраструктуре. Я подумал скормить в OpenSRE с поддержкой Yandex Cloud один из кейсов и сравнить качество рассуждений и время, за которое получится найти правильное решение.
Кейс тривиальный: кластер Managed Service for Kubernetes, в котором крутится приложение todo (заметки) и база данных Managed Service for PostgreSQL, с которой общается бэкенд приложения. Мы сделали принудительный фейловер мастера.
Приложение, согласно 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. Всего было девять замеров (по три с каждой моделью). Каждый раз я очищал контекст сессии и выключал память флагом 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-провайдера. Решение было правильным, как я и задумывал в самом кейсе.
Статистика всех прогонов:
Модель |
Время (среднее) |
Результат |
|
4 минуты 15 секунд |
два из трёх решений были верны. Только один прогон дал верный ответ про RW/RO sFQDN. |
|
3 минуты 52 секунды |
все три прогона оказались успешными, решения про sFQDN не было. |
|
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 из трёх прогонов правильная рекомендация была в одном, но узнал я это только потому, что прогнал несколько раз.
Что по итогу
Контрибьютить оказалось не так страшно и тяжело, как могло казаться. Мой опыт до этого ограничивался проектами в нашем репозитории с примерами для Yandex Cloud. OpenSRE — это первый публичный проект, в который я зашёл всерьёз.
Этим я фактически подписался на поддержку интеграции: чтение issues, ответы на вопросы и починку того, что отвалится после следующего рефакторинга (в том числе со стороны провайдера Yandex Cloud). Скажу честно: слепо полагаться на интеграцию я бы пока не стал. Но увидеть, что она даёт, уже можно — и этого достаточно, чтобы решить, стоит ли идти дальше. Я решил, что стоит.
А если вы хотите увидеть, что умеем мы в Yandex Cloud — загляните в наш канал. Там мы рассказываем о том, как устроены наши системы под капотом и какие ещё эксперименты мы проводим.
P. S.
Ситуация с момента подготовки статьи изменилась: приём внешних PR в OpenSRE сейчас фактически приостановлен. С интеграцией мне повезло попасть в окно, когда он был открыт, но самый актуальный код живёт у меня в форке. То, что показано на видео, я проверял на upstream-релизе v0.1.2026.9.6 — там оно работает; на более новых сборках не проверял.