Наблюдаемость как платформа: единые метрики, логи и трейсы
02:41. Падает оформление заказа. Дежурный по checkout открывает Grafana — там метрики его сервиса, потому что его команда полтора года назад подняла себе Prometheus. Ошибки приходят из pricing. Дежурный идёт в pricing — а у них логи в облачном SaaS, доступа у него нет, надо будить их дежурного. Разбуженный дежурный pricing смотрит логи и говорит: «у нас всё хорошо, у нас таймауты в inventory». У inventory трейсы есть — они единственные, кто внедрил трейсинг, — но trace_id от checkout до них не доезжает, потому что между ними шина, а контекст через шину не прокидывается. В 03:58 выясняется, что виноват пул соединений к базе в pricing, исчерпанный из-за деплоя в 22:10. Инцидент длился 77 минут, из которых 61 минута — поиск людей и переключение между четырьмя интерфейсами.
Разбор инцидента напишет: «нужно улучшить мониторинг». Это неверный вывод. Мониторинг у каждой из трёх команд был; каждая по отдельности видела свой сервис лучше, чем среднее по рынку. Не было общего свойства системы: возможности пройти по одному следу через границы команд без переговоров. Это свойство нельзя купить и нельзя выдать распоряжением — оно возникает, только когда наблюдаемость становится платформенным сервисом с общим контрактом.
Про сами сигналы — что такое метрика, лог и трейс, как выбирать SLI, как не утонуть в алертах — в этом портале уже написано: мониторинг и алертинг в треке SRE, наблюдаемость распределённых систем с механикой трассировки, обзор инструментов в devops. Эта глава — про другое. Про то, как из набора инструментов сделать продукт, у которого есть пользователи, счёт, SLO, метрики принятия и очень реальный шанс провалиться.
Почему наблюдаемость — самый платформенный из платформенных сервисов
Есть простой критерий, который отделяет «сервис, который логично централизовать» от «сервиса, который лучше оставить командам»: ценность растёт от того, что все пользуются одним и тем же. У большинства платформенных сервисов такого свойства нет. Если половина команд собирает контейнеры вашим шаблоном, а половина — своими Dockerfile, обе половины работают. Неудобно, но работает. С наблюдаемостью не так. Ценность здесь сетевая: она равна не сумме подключённых сервисов, а числу пар, между которыми можно проследить запрос. Подключено 60 % сервисов — работает не 60 % трассировки, а примерно 36 % путей (0,6 × 0,6), и это ещё оптимистично, потому что рвутся обычно самые интересные пути — через шину, через сторонний прокси, через легаси. Отсюда первый вывод, определяющий всю стратегию:
Наблюдаемость — это сервис, у которого частичное принятие даёт сильно меньше пользы, чем следует из доли. Поэтому здесь принятие важнее возможностей радикальнее, чем где-либо ещё на платформе: пять сервисов с идеальными профилями и связкой логов с трейсами полезнее, чем сто сервисов, из которых половина шлёт данные в свой угол.
Это же делает наблюдаемость опасной темой. Сетевой эффект — соблазн запретить альтернативы: «с понедельника все шлют только к нам». Такой запрет даёт цифры в отчёте и почти нулевую пользу, потому что команда, которую заставили, пришлёт данные — и продолжит смотреть в свой Grafana. Про механику этого расхождения — глава о принятии; здесь важно, что наблюдаемость даёт особенно правдоподобную видимость успеха.
Второе свойство, из-за которого наблюдаемость сложнее большинства платформенных сервисов: вы платите за поведение чужих команд. Конвейер стоит примерно предсказуемо. Наблюдаемость — нет: один инженер в чужой команде добавляет в метрику метку user_id, и ваш месячный счёт вырастает вдвое, а вы узнаёте об этом из письма от финансистов. Это ставит платформенную команду в позицию, к которой она обычно не готова: она вынуждена управлять чужими решениями через квоты, лимиты и разговоры о деньгах — а не только через удобство.
Кто чем владеет: контракт двух сторон
Главная причина, по которой платформы наблюдаемости буксуют, — размытая ответственность. Платформа считает, что даёт трубу и хранилище, а качеством данных занимаются команды. Команды считают, что раз платформа собирает данные, она и отвечает за то, чтобы в них было можно что-то найти. В итоге за качество телеметрии не отвечает никто, и через год половина трейсов — это спаны с именем HTTP GET без атрибутов. Границу надо провести явно и записать; работающее разделение выглядит так:
order_id, tenant, вид платежа"] B["Спаны бизнес-операций
и уровни логов"] C["Свои SLI и пороги"] D["Дашборд своего домена"] end subgraph P["Зона платформы"] E["SDK и автоинструментирование
в шаблоне сервиса"] F["Прокидывание контекста
HTTP, gRPC, шина, задачи"] G["Приёмник, буфер, хранилище,
сроки хранения, доступы"] H["Базовый дашборд и алерты
из контракта сервиса"] I["Квоты, семплирование, счёт"] end E --> B F --> B B --> G A --> G G --> H G --> D C --> H I -.->|"обратная связь
по объёму и деньгам"| T
Правило, которое стоит повесить на стену платформенной команды: платформа отвечает за то, чтобы сигнал доехал, склеился и нашёлся; команда отвечает за то, чтобы в сигнале был смысл. Платформа не может знать, что order_id важнее retry_count, — это доменное знание. Команда не может (и не должна) знать, как устроен буфер приёмника. У этого разделения есть неприятное следствие, которое надо принять заранее: платформа обязана уметь показывать команде, что её телеметрия плохая. Не жаловаться в чате, а автоматически: «в 34 % ваших спанов нет атрибута service.namespace, поэтому они не попадают в дашборд домена». Иначе качество данных деградирует молча, а деградация обнаруживается ровно в момент инцидента — то есть тогда, когда исправлять поздно.
$ svc obs check payments
Наблюдаемость сервиса payments — 6 из 9 проверок пройдено
ok метрики RED поступают, лаг 8 с; логи структурные, 100 % с trace_id
ok ресурсные атрибуты: service.name, service.version, owner
ok базовый дашборд собран, алерты по SLO подключены, бюджет 71 %
ok расход за 30 дней: 610 USD (метрики 180, логи 350, трейсы 80)
! исходящие вызовы в RabbitMQ не прокидывают контекст
→ 41 % трейсов обрывается на границе шины
→ обновите platform-sdk до 4.2: https://internal.docs/obs/mq-context
! метрика payments_charge_attempts имеет метку card_bin (612 значений)
→ 44 000 рядов, 63 % вашего расхода на метрики
→ перенесите разрез в exemplars или в логи
! 14 % логов уровня ERROR не содержат текста ошибки, только код
Итог: сервис виден в платформе, но след рвётся на шине. Последствие:
при инциденте дежурный соседней команды не увидит вашу часть пути.
Такая проверка стоит платформенной команде примерно двух недель работы, а даёт две вещи сразу: разговор с командой ведётся про её пользу («ваш след рвётся»), а не про вашу отчётность («вы не соответствуете стандарту»); и деньги показываются рядом с качеством, что снимает половину будущих конфликтов о лимитах.
Инструментирование как контракт, а не как проект внедрения
Классическая ошибка — воспринимать инструментирование как разовую кампанию: «в этом квартале инструментируем 40 сервисов». Кампания заканчивается, приходят новые сервисы, старые переписываются, и через год вы снова на 60 %. Инструментирование — это свойство шаблона сервиса, а не проект. Сервис, созданный из платформенного шаблона, обязан быть виден в платформе в момент первого деплоя, без единой строчки от разработчика. Как это связано с каталогом и шаблонами — в главе про инфраструктуру как API; как это связано с золотым путём — во второй главе. Практически контракт состоит из трёх слоёв, и различать их полезно, потому что цена изменения у них разная.
| Слой | Что это | Кто меняет | Цена изменения |
|---|---|---|---|
| Транспорт | Протокол и адрес приёмника, формат | Платформа | Низкая, если через коллектор: команды не знают адресов |
| Соглашения об именах | service.name, deployment.environment, имена метрик, уровни логов |
Платформа, редко | Высокая: ломает дашборды и алерты у всех |
| Смысл | Доменные атрибуты, бизнес-спаны, ключи корреляции | Команда | Низкая, локальная |
Отсюда сразу два инженерных решения. Первое: между приложением и хранилищем всегда стоит коллектор, даже если сегодня хранилище одно и менять его вы не собираетесь. Коллектор — это точка, где платформа может переименовывать атрибуты, добавлять недостающие, резать шум, дублировать поток в новое хранилище во время миграции и включать квоту, не трогая ни одного приложения. Без него любое изменение в этом списке превращается в обход 120 репозиториев. Второе: соглашения об именах надо версионировать так же, как API. Их нельзя менять «просто потому что стало понятнее». Реальный пример из индустрии: в семантических соглашениях OpenTelemetry атрибут HTTP-статуса переехал из http.status_code в http.response.status_code, а http.method — в http.request.method (стабилизация HTTP semconv). Изменение правильное, но для платформы это значит: у всех сломались дашборды и правила алертов. Проходить такое надо с двойной записью и периодом совместимости, а не одним рывком.
# Конфиг коллектора: место, где платформа управляет чужой телеметрией,
# не заставляя команды ничего переписывать.
receivers:
otlp:
protocols:
grpc: { endpoint: 0.0.0.0:4317 }
http: { endpoint: 0.0.0.0:4318 }
processors:
# Защита самого коллектора: он не имеет права падать вместе с пиком.
memory_limiter: { check_interval: 1s, limit_percentage: 80, spike_limit_percentage: 20 }
# Обогащение: команда не обязана знать, кто владелец и в каком кластере она живёт.
resource:
attributes:
- { key: k8s.cluster.name, value: prod-eu-1, action: upsert }
- { key: owner.team, from_attribute: service.namespace, action: insert }
# Совместимость на время миграции соглашений: пишем оба имени.
attributes/semconv-bridge:
actions:
- { key: http.response.status_code, from_attribute: http.status_code, action: insert }
# Квота на кардинальность: метки-подрывники не доезжают до хранилища.
# Список ведётся платформой, каждое правило заведено по итогам разговора с командой.
attributes/cardinality-guard:
actions:
- { key: user_id, action: delete }
- { key: card_bin, action: delete }
- { key: session_id, action: delete }
# Хвостовое семплирование: решение принимается, когда трейс уже собран,
# поэтому все ошибки и все медленные запросы сохраняются целиком.
tail_sampling:
decision_wait: 12s
num_traces: 200000
policies:
- { name: errors-always, type: status_code, status_code: { status_codes: [ERROR] } }
- { name: slow-always, type: latency, latency: { threshold_ms: 800 } }
- { name: baseline, type: probabilistic, probabilistic: { sampling_percentage: 4 } }
batch: { timeout: 5s, send_batch_size: 8192 }
exporters:
otlp/traces: { endpoint: tempo-distributor:4317, tls: { insecure: true } }
prometheusremotewrite/metrics: { endpoint: http://mimir-distributor/api/v1/push }
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, resource, attributes/cardinality-guard, tail_sampling, batch]
exporters: [otlp/traces]
metrics:
receivers: [otlp]
processors: [memory_limiter, resource, attributes/semconv-bridge, attributes/cardinality-guard, batch]
exporters: [prometheusremotewrite/metrics]
Обратите внимание на attributes/cardinality-guard: это не техническая деталь, а продуктовое решение с ценой. Платформа молча удаляет данные, которые команда сознательно отправила. Если сделать это без предупреждения, вы получите инцидент доверия: инженер будет двадцать минут искать метку, которой нет, и решит, что платформа врёт. Правило простое: любое удаление данных должно быть видимым — в выводе svc obs check, в письме владельцу, в аннотации на дашборде. Тихая потеря данных дороже перерасхода.
Цена владения OpenTelemetry — назовём её
OpenTelemetry сегодня фактический стандарт, и выбирать его для новой платформы разумно: он снимает главный риск — привязку кода приложений к конкретному вендору хранилища. Но «стандарт» не значит «бесплатно». Честный счёт:
- Парк коллекторов — это ещё один распределённый сервис в проде, который надо мониторить, обновлять, масштабировать и который умеет терять данные при переполнении буфера. Для 120 сервисов это обычно агент рядом с подом плюс шлюзовой слой из нескольких реплик: примерно 0,3–0,5 FTE постоянной эксплуатации.
- Разная зрелость по языкам. Автоинструментирование Java и .NET покрывает почти всё; Go требует явной работы в коде (нет байткод-инъекции); Node и Python — посередине. Это значит, что «включили автоинструментирование» будет означать разное качество для разных команд, и планировать надо по худшему языку в компании.
- Подвижность спецификаций. Логи в OTel стабилизировались заметно позже метрик и трейсов; семантические соглашения продолжают меняться. Каждое обновление SDK потенциально трогает имена, по которым построены ваши дашборды. Практика: держать версию SDK в платформенном шаблоне и обновлять её централизованно, а не отдавать командам.
- Стоимость самого прокидывания контекста. Каждый исходящий вызов несёт заголовки трассировки; это копейки для HTTP и заметно для очень мелких сообщений в высокочастотной шине.
Документация: opentelemetry.io/docs, список процессоров коллектора — opentelemetry-collector-contrib. Никакой рекламы: инструмент хороший, но он добавляет вам сервис в эксплуатацию, а не убирает.
Золотой путь наблюдаемости и что делать с теми, кому он не подходит
Золотой путь здесь формулируется коротко: сервис, созданный по шаблону, в день первого деплоя получает всё нужное без единого решения со стороны разработчика. Конкретно — что именно «всё»:
- Метрики RED (запросы, ошибки, длительность) для всех входящих и исходящих вызовов, автоматически.
- Структурные логи, где
trace_idподставляется сам, а поиск по нему открывает и логи, и трейс. - Трейсы со сквозным контекстом через HTTP, gRPC, шину и фоновые задачи.
- Базовый дашборд, собранный из контракта сервиса, а не нарисованный руками.
- Алерты по объявленным SLO — с бюджетом ошибок и правилами выгорания (SLO, бюджет ошибок).
- Ссылки: из алерта — в дашборд, из дашборда — в трейсы за тот же интервал, из трейса — в логи того же спана. Именно эти переходы экономят минуты в 03:00, и именно их обычно забывают сделать.
- Строка расхода: сколько сервис стоит по каждому сигналу.
Теперь честная часть. Золотой путь — это медиана, а медианой описывается не всё. Всегда найдутся команды, которым он не подходит, и они не саботажники — у них другая задача:
- Пакетная обработка и ML-конвейеры. Трейс запроса тут бессмыслен: единица работы — не запрос, а прогон на четыре часа. Им нужны метрики по этапам, качество данных и профилировщик, а RED-метрики покажут ровную линию нулей.
- Мобильные и веб-клиенты. Реальный пользовательский опыт (RUM) — это другой конвейер: сбор с ненадёжной сети, приватность, версии приложений, которые живут годами. Пытаться завести это в тот же приёмник обычно ошибка.
- Команды с законной высокой кардинальностью. Антифрод, биллинг, поддержка: им реально нужен разрез по клиенту. Их проблема — не «они не умеют», а «наша метрическая модель для этого не годится».
- Сервисы с жёстким бюджетом задержки. Инструментирование в горячем пути стоит микросекунды, и иногда эти микросекунды имеют значение — см. измерение производительности.
Что с ними делать — это и есть проверка на то, путь у вас или забор. Плохой ответ: «шлите как все, мы потом придумаем». Хороший ответ состоит из трёх ходов.
Ход первый: дать другой профиль, а не выкинуть за борт. У платформы должно быть больше одного золотого пути. Например, три: web-service (RED, трейсы, стандартные алерты), batch-job (метрики этапов, длительность прогона, свежесть данных, отдельный класс алертов «прогон не стартовал»), client-app (RUM-конвейер). Это не «три платформы»: транспорт, хранилище, доступы, счёт и каталог у них общие. Различается только набор сигналов и дефолтных дашбордов. Три узких контракта над общей реализацией почти всегда лучше одной универсальной абстракции над тремя разными потребностями — подробно об этом в главе про абстракции.
Ход второй: люк с квотой, а не запрет. Команде антифрода нужен разрез по клиенту. Запрет означает, что они поднимут свой ClickHouse, и вы потеряете их из общего следа целиком. Вменяемое решение: широкие события с высокой кардинальностью пишутся в колоночное хранилище (аналитический профиль), а не в метрическую базу; у команды есть выделенная квота в байтах и она видит её расход; trace_id в этих событиях обязателен, чтобы связь с общим следом сохранилась. Команда получила своё, платформа не потеряла связность. Про выбор хранилищ под такие задачи — временные ряды и поиск и ClickHouse и OLAP.
Ход третий: считать исключения как продуктовый сигнал. Одно исключение — частный случай. Пятое исключение одного и того же типа — это ваш бэклог: значит, потребность массовая, и её надо втащить в золотой путь. Если исключений много, а бэклог не меняется, вы построили забор и охраняете его.
Отдельный случай, который стоит назвать прямо: аудит и комплаенс — это не наблюдаемость. Требования там другие: неизменяемость записей, срок хранения годами, доказуемая полнота, ограниченный доступ. Попытка положить аудит-логи в тот же конвейер, где живёт отладочная телеметрия с семплированием и удержанием в 14 дней, заканчивается либо провалом аудита, либо счётом, который убьёт платформу. Это два разных продукта с общим транспортом. См. безопасность по умолчанию и приватность и соответствие требованиям.
Метрики принятия: что мерить, чтобы не обмануть себя
Наблюдаемость даёт обильную статистику о себе: терабайты, ряды, число дашбордов, число сервисов, отправляющих данные. Почти всё это — метрики тщеславия. Они растут, даже когда платформа никому не помогает; более того, некоторые из них растут быстрее, когда платформа плохая (больше дашбордов обычно значит, что стандартных не хватает). Работающий набор строится вокруг одного вопроса: пользуются ли платформой в тот момент, когда это важнее всего — во время инцидента и при отладке.
| Метрика | Как измерять | Целевой смысл | Чем её подделают |
|---|---|---|---|
| Доля инцидентов, где первый полезный сигнал получен в платформе | Поле в шаблоне разбора инцидента, заполняется вручную | Растёт | Заполнение «по привычке»; лечится выборочной проверкой |
| Доля сервисов из каталога с валидной телеметрией | Автоматически, по самой телеметрии | Растёт к 100 % | Формально шлют, но никто не смотрит — сверяйте с активностью |
| Недельная активность: инженеров, сделавших хотя бы один нетривиальный запрос | Логи запросов интерфейса | Растёт вместе со штатом | Автообновление дашборда на телевизоре — исключайте фоновые запросы |
| Число живых альтернативных стеков | Ручная инвентаризация раз в квартал | Стремится к нулю | Ничем: это самая честная метрика провала |
| p95 времени ответа на типовой запрос (7 дней логов одного сервиса) | Замер синтетикой | < 5 с | Замер на пустом окне вместо реального |
| Время от создания сервиса до первого сигнала на дашборде | Из событий каталога | Минуты, не дни | — |
| Доля алертов, которые привели к действию | Разметка в системе дежурств | Растёт | Тихое закрытие шумных алертов без разбора |
Две метрики в этой таблице заслуживают отдельного слова. Число живых альтернативных стеков — самый неудобный и самый честный индикатор. Пока команда держит свой Prometheus «на всякий случай», она платит за него временем и не доверяет вашему. Считать надо не по декларациям, а по фактам: работающие инсталляции, оплаченные подписки на SaaS, дашборды, открытые за последний месяц. Полезно спрашивать не «почему вы не переехали», а «что должно случиться, чтобы вы выключили свой стек» — ответ обычно точный и короткий, и почти всегда это одна конкретная возможность, а не общее недоверие.
Доля инцидентов, где первый полезный сигнал получен в платформе — метрика, ради которой всё строится. Её нельзя собрать автоматически, и это нормально: в шаблон разбора добавляется одно поле с тремя вариантами («платформенные инструменты», «собственный стек команды», «догадка человека, знающего систему»). Третий вариант, кстати, самый информативный: если большинство инцидентов решается тем, что «Петя знает, где смотреть», у вас не платформа, а Петя. Про сами разборы — постмортемы.
Общий метод — как вообще честно измерять опыт разработчика, не скатываясь в подсчёт активности — в третьей главе; продуктовая база измерений — в метриках продукта и в статье «DevEx: What Actually Drives Productivity». А разница между «подключено» и «пользуются» видна в том, как проходит один и тот же инцидент.
упираются в pricing.charge D1->>P: перейти из спана в логи pricing
за тот же интервал P-->>D1: "connection pool exhausted",
первое появление в 22:11 D1->>P: наложить события деплоев P-->>D1: релиз pricing 1.44.0 в 22:10 D1->>D2: будим с готовым диагнозом:
откат 1.44.0 Note over D1,D2: 11 минут вместо 77.
Разбудили одного человека вместо трёх
Разница здесь не в объёме собранных данных — во втором случае их не больше. Разница в трёх переходах: трейс → логи, логи → деплои, диагноз → человек. Каждый из них стоит платформенной команде дней работы (единый идентификатор, общее временное окно, события деплоя как отдельный поток), и каждый экономит минуты в самый дорогой момент. Это и есть предметная работа платформы наблюдаемости — не «собрать данные», а сократить число переключений между инструментами до нуля.
Жизненный цикл телеметрии сервиса
Наблюдаемость сервиса — не бинарное состояние «есть/нет»: у неё есть жизненный цикл, и у платформы должна быть позиция по каждому переходу, включая последний, о котором забывают все.
первый деплой Виден --> Осмысленный: команда добавила
доменные атрибуты и SLI Осмысленный --> Дорогой: рост нагрузки
или неудачная метка Дорогой --> Осмысленный: семплирование,
квота, разговор Осмысленный --> Заброшенный: команда расформирована,
владелец не отвечает Виден --> Заброшенный: никто не смотрел
дашборд 90 дней Заброшенный --> Осмысленный: нашли владельца Заброшенный --> Отключён: телеметрия остановлена
после уведомления Отключён --> [*] note right of Дорогой Здесь платформа вмешивается первой: команда своего счёта не видит, пока ей не покажут end note
Состояние «Заброшенный» — это то, что отличает платформу с эксплуатацией от платформы, которую построили и оставили. Через два-три года в любой достаточно большой компании 10–20 % потока телеметрии — данные сервисов, которых уже нет, или сервисов без владельца. Они стоят реальных денег и мешают поиску. Процесс должен быть заранее описан: обнаружение (нет владельца в каталоге или дашборд не открывали 90 дней), уведомление, срок ожидания, отключение с возможностью включить обратно за час. Без последнего пункта отключение никто не решится провести.
Экономика: почему без счёта наблюдаемость становится налогом
Платформенная команда не пишет продукт. Значит, её существование оправдывается временем, сэкономленным другим, — общая логика разобрана в главе о платформенной команде и в первой главе. У наблюдаемости к этому добавляется прямой счёт за инфраструктуру, который умеет расти без всякого участия платформы. Начнём с механики, из-за которой он растёт скачками.
Ключевой момент, который стоит объяснять командам буквально этой картинкой: число метрик не влияет почти ни на что, а число значений у метки влияет на всё. Метрика с десятью метками низкой кардинальности дешевле, чем одна метрика с одной меткой на 50 000 значений. Инженер, который «просто добавил user_id, чтобы было удобнее искать», действовал разумно в рамках своего сервиса и не имел ни одного способа узнать, во что это обойдётся. Виновата не команда — виновата платформа, которая не показала цену в момент решения.
Отсюда практический принцип: лимит должен срабатывать как можно раньше и как можно ближе к автору. Порядок предпочтения: линтер в CI, который ловит метку с подозрительно высокой кардинальностью, → предупреждение при первом появлении новой метки в приёмнике → жёсткая квота на хранилище. Последнее — крайняя мера, потому что она бьёт по данным, когда решение уже принято и код в проде. Вторая половина экономики — распределение расхода, и оно почти всегда выглядит так:
Практический вывод неочевиден и очень важен для тона всей работы платформы: общие ограничения — это почти всегда неверный ход. Письмо «всем командам сократить логи на 30 %» бьёт по 84 командам, которые не влияют на счёт, тратит их квартал, портит вам отношения и экономит проценты. Три личных разговора с тремя командами дают 60 % экономии за неделю. Это ровно та же логика, что в локальной оптимизации и точках воздействия: вмешательство работает там, где рычаг, а не там, где удобно разослать письмо.
Теперь счёт окупаемости. Он должен быть у платформенной команды написан, пересчитан раз в квартал и показан руководству — не потому что кто-то требует, а потому что без него первый же секвестр бюджета срежет вас как «расход без выручки».
"""Окупаемость платформы наблюдаемости. Модель грубая, но честная:
все допущения названы, ни одно не спрятано."""
from dataclasses import dataclass
@dataclass
class Setup:
services: int = 120 # сервисов в каталоге
engineers: int = 260 # продуктовых инженеров
platform_fte: float = 4.0 # инженеров в команде наблюдаемости
fte_cost_month: float = 9_000 # полная стоимость инженера в месяц, USD
infra_month: float = 41_000 # хранилища, приёмники, трафик, USD
# Экономия. Числа берутся из ЗАМЕРОВ, а не из головы:
incidents_month: int = 22 # инцидентов в месяц (из системы дежурств)
mttr_before_min: int = 77 # медиана до платформы
mttr_after_min: int = 34 # медиана после (замер, не обещание)
responders_per_incident: float = 2.6
debug_hours_before: float = 3.1 # часов в неделю на инженера — отладка «вслепую»
debug_hours_after: float = 2.0
shadow_stacks_before: float = 2.5 # FTE, которые команды тратили на свои стеки
shadow_stacks_after: float = 0.4
def payback(s: Setup) -> dict:
cost = s.platform_fte * s.fte_cost_month + s.infra_month
hourly = s.fte_cost_month / 160 # 160 рабочих часов в месяц
# 1. Сокращение MTTR: экономятся часы всех, кого подняли.
mttr_hours = (s.mttr_before_min - s.mttr_after_min) / 60 \
* s.incidents_month * s.responders_per_incident
# 2. Ежедневная отладка — самая крупная и самая недооценённая статья.
debug_hours = (s.debug_hours_before - s.debug_hours_after) * 4.3 * s.engineers
# 3. Ликвидация параллельных стеков.
shadow_hours = (s.shadow_stacks_before - s.shadow_stacks_after) * 160
saved = (mttr_hours + debug_hours + shadow_hours) * hourly
return {
"затраты, USD/мес": round(cost),
"экономия, USD/мес": round(saved),
"из них отладка": round(debug_hours * hourly),
"из них инциденты": round(mttr_hours * hourly),
"из них свои стеки": round(shadow_hours * hourly),
"отношение": round(saved / cost, 2),
"стоимость сервиса, USD/мес": round(cost / s.services),
}
print(payback(Setup()))
# {'затраты, USD/мес': 77000, 'экономия, USD/мес': 90382, 'из них отладка': 69176,
# 'из них инциденты': 2306, 'из них свои стеки': 18900, 'отношение': 1.17,
# 'стоимость сервиса, USD/мес': 642}
Из этой модели вылезают три вывода, которые обычно противоречат интуиции.
Первое: инциденты — не главная статья экономии. Сокращение MTTR выглядит эффектно в презентации, но инцидентов немного. Основная экономия — в ежедневной отладке, которая происходит каждый день у каждого инженера и которую никто не считает. Час с небольшим в неделю на человека при 260 инженерах даёт в тридцать раз больше, чем весь инцидентный выигрыш.
Второе: экономия чувствительна к тому, реальны ли цифры. debug_hours_before нельзя выдумать. Его надо замерить: опрос из одного вопроса раз в квартал, дневник наблюдений, замер на выборке. Если этой цифры нет, у вас нет модели окупаемости — у вас есть аргумент в споре, который проиграет первому же скептику с калькулятором.
Третье: соотношение около единицы — это тревога, а не победа. В примере экономия чуть выше затрат, то есть платформа едва оправдывает себя. Это типичная картина для 120 сервисов и почти всегда означает одно из двух: либо инфраструктурный счёт раздут (посмотрите на длинный хвост), либо принятие недостаточное. При тех же затратах и принятии 95 % вместо 60 % отношение уходит за 1,6.
И зеркальное правило: платформа обязана показывать каждой команде её расход — не для наказания, а потому что решение о ресурсах невозможно принять, не видя цены. Показ расхода (showback) без перевыставления счёта (chargeback) в большинстве компаний даёт 80 % эффекта при 20 % политических издержек. Подробнее — в главе о стоимости и в разговоре о цене облака.
-- Ежемесячный showback: кто сколько прислал и во что это обошлось.
-- Отправляется владельцу сервиса из каталога, а не «всем инженерам».
SELECT
owner_team,
service_name,
round(sum(bytes) / 1e12, 2) AS tb_month,
round(sum(bytes) / 1e12 * 780, 0) AS usd_month, -- 780 USD за ТБ
round(100 * sum(bytes) / sum(sum(bytes)) OVER (), 1) AS pct_of_bill,
countIf(level = 'DEBUG') / count() AS debug_share
FROM logs_ingest_stats
WHERE day >= today() - 30
GROUP BY owner_team, service_name
ORDER BY tb_month DESC
LIMIT 20;
Колонка debug_share здесь не для отчёта, а для разговора: если 70 % объёма — это DEBUG, оставленный включённым после отладки полгода назад, экономия достигается одной строкой конфига и не требует ни от кого жертв. Такие случаи составляют заметную долю перерасхода, и находить их — работа платформы, а не команды.
Свои SLO и вопрос «кто сторожит сторожей»
У платформы наблюдаемости есть пользователи, значит, есть и обещания. Обещания надо записать как SLO — по той же механике, что описана в SRE, с одной поправкой: пользователи здесь не внешние клиенты, а инженеры, и SLI формулируются в терминах их работы.
| SLI | Определение | Разумная цель | Почему именно это |
|---|---|---|---|
| Лаг приёма | Время от события до его видимости в интерфейсе | p95 < 30 с | Дежурный не должен гадать, «уже видно или ещё нет» |
| Полнота | Доля принятых спанов от отправленных | > 99,9 % (после семплирования) | Потерянный спан = порванный след |
| Задержка запроса | p95 типового запроса за 7 дней логов | < 5 с | Дольше — инженер уходит в свой инструмент |
| Доступность интерфейса | Успешные запросы к API и UI | 99,9 % | Отдельный SLI: интерфейс падает не так, как хранилище |
| Доставка алертов | Доля алертов, дошедших до дежурного за 60 с | 99,95 % | Это уже не наблюдаемость, а безопасность работы |
| Свежесть контракта | Доля сервисов с валидной телеметрией | > 95 % | SLI качества данных, а не трубы |
Отдельная тема — лаг приёма нельзя выкидывать из головы, когда всё хорошо. Он растёт именно тогда, когда растёт нагрузка, то есть ровно во время крупного инцидента. Классический сценарий: началась авария, поток логов вырос впятеро, коллекторы упёрлись в лимит памяти, лаг вырос до восьми минут — и дежурные смотрят на данные восьмиминутной давности, не зная об этом. Отсюда практическое требование: текущий лаг приёма должен быть виден в интерфейсе всегда, полоской вверху экрана. Стоит день работы, спасает от целого класса неверных решений в аварии. И отсюда же главный вопрос, который ставится редко: что вы будете делать, когда платформа наблюдаемости упадёт сама?
наблюдаемости жива?"} B -->|"да"| C["Обычный разбор:
дашборд, трейсы, логи"] B -->|"нет"| D{"Есть независимый
минимальный путь?"} D -->|"нет"| E["Слепой режим:
дежурный лезет по SSH,
MTTR растёт кратно"] D -->|"да"| F["Резервный контур:
отдельный Prometheus,
внешняя синтетика,
прямые уведомления"] F --> G["Диагностика ограничена,
но возможна"] C --> H["Восстановление"] G --> H E --> H I["Watchdog: алерт,
который шлётся ВСЕГДА"] -.->|"пропал = платформа мертва"| D
Минимум, который обязан быть у любой платформы наблюдаемости:
- Watchdog-алерт — правило, которое срабатывает постоянно и шлёт наружу сигнал «я жив». Пропал сигнал — значит, умерла система алертинга, и об этом надо узнать от внешнего сторожа, а не от пользователей. Такой сторож должен работать вне вашей инфраструктуры: Dead Man’s Snitch, healthchecks-сервис или просто крон в другом облаке.
- Независимый путь доставки алертов. Если алерты идут через тот же кластер, что и хранилище, авария кластера означает тишину.
- Минимальный резервный контур. Не копия платформы, а несколько десятков жизненно важных метрик, собираемых отдельным простым Prometheus в другом домене отказа, плюс внешняя синтетическая проверка ключевых пользовательских сценариев. Внешняя проверка ценна ещё и тем, что отвечает на вопрос «а вообще работает?» независимо от всей вашей телеметрии.
- Написанный сценарий работы вслепую. Как откатить релиз, если дашбордов нет. Кто принимает решение. Где лежат прямые доступы. Проверяется на учениях — см. хаос-инженерию и учения.
Платформа наблюдаемости — по определению зависимость всех остальных сервисов. Значит, к ней применимы требования выше среднего по надёжности, и её собственные дежурства не могут опираться на неё саму (дежурства).
Типовые провалы
Пять сценариев, которые повторяются из компании в компанию. Каждый начинается с разумного намерения.
1. Обёртка над облачным провайдером, которая только мешает. Платформа делает свой интерфейс поверх облачного мониторинга: «чтобы командам было проще и чтобы мы могли сменить вендора». Через полгода в обёртке доступно 30 % возможностей, вендор выпустил три новые фичи, которых у вас нет, а инженеры научились ходить в консоль облака напрямую — и правильно сделали. Диагноз: обёртка над чужим API, который меняется быстрее вашего цикла разработки, обречена быть тормозом. Работающая альтернатива: не прятать инструмент, а добавлять то, чего в нём нет — единый идентификатор запроса, связь с каталогом сервисов и владельцами, учёт расхода по командам, шаблоны дашбордов. Это надстройка, а не обёртка: она не отнимает у инженера доступ к оригиналу.
2. Портал, которым никто не пользуется. Сделан красивый единый вход: каталог, дашборды, документация. Открывается два раза в месяц. Причина почти всегда одна: портал не встроен в момент, когда человеку нужно. Инженеру нужна телеметрия в двух ситуациях — когда пришёл алерт и когда что-то не работает при разработке. В первой он смотрит в уведомление; во второй — в терминал или IDE. Если ссылка на нужный дашборд не лежит прямо в тексте алерта, а svc logs в терминале не работает, портал проигрывает по числу кликов и умирает. Лечится не редизайном, а вклиниванием в существующие пути: ссылки в алертах, ссылки в пул-реквестах, команда в CLI, бот в чате инцидента. Подробнее про эту логику — в главе о самообслуживании.
3. «Мы сделали абстракцию» поверх трёх разных потребностей. Платформа заводит «единую систему событий» для операционной телеметрии, продуктовой аналитики и аудита — ведь это всё «события». Дальше начинается: аналитикам нужна полнота (семплировать нельзя), эксплуатации — свежесть (задержка в час бессмысленна), аудиту — неизменяемость и три года хранения. Одна система не может одновременно быть дешёвой, полной, свежей и вечной. Итог — либо счёт по худшему требованию из трёх, либо три недовольные группы пользователей. Правильный ход виден заранее: общий транспорт и общие идентификаторы, разные хранилища и разные контракты. См. абстракции и конвейеры данных.
4. Культ инструмента. «Нам нужен Kubernetes-native стек» или «переезжаем на Backstage» произносится раньше, чем сформулирована проблема. Внедрение занимает год, съедает всю ёмкость команды, и по итогу инженеры получают ровно то же, что имели, плюс новый интерфейс. Проверка: если вы не можете назвать конкретную задачу пользователя, которая сегодня решается за 40 минут, а после внедрения будет решаться за 5, — вы меняете инструмент, а не решаете проблему. Инструменты полезны, но каждый добавляет постоянную стоимость владения, и она не исчезает после запуска.
5. Наблюдаемость как гигиеническое требование. «Каждый сервис обязан экспортировать метрики» вписано в стандарт. Все экспортируют. Никто не смотрит. Через год выясняется, что 200 метрик на сервис не используются ни в одном дашборде и ни в одном алерте, но исправно занимают место и деньги. Здоровая практика: раз в квартал строить отчёт «метрики, которые никто ни разу не запросил» и удалять их. Обычно удаляется 40–60 % — и никто не замечает. Общая рамка выбора, что вообще стоит централизовать, а что оставить командам:
Цена владения инструментами: без рекламы
Ни один из этих инструментов не «лучше» другого в отрыве от контекста. Полезно другое — назвать постоянную стоимость владения, потому что её в презентациях обычно нет.
| Решение | За что платите деньгами | За что платите людьми | Когда это разумный выбор |
|---|---|---|---|
| Prometheus + Alertmanager | Почти ничего сверх железа | 0,2–0,5 FTE; не масштабируется горизонтально сам, история ограничена | До ~50 сервисов, один кластер |
| Mimir / Thanos / Cortex | Объектное хранилище, трафик | 0,5–1 FTE постоянно: это распределённая БД в вашей эксплуатации | Десятки миллионов рядов, долгая история |
| Loki | Объектное хранилище дешёвое | 0,3–0,7 FTE; модель «индекс только по меткам» требует дисциплины в схеме | Логи объёмом от единиц ТБ, важна цена |
| ClickHouse под логи и события | Железо и диски | 0,5–1 FTE, включая знание схем, TTL, партиционирования | Высокая кардинальность, аналитика по событиям |
| Tempo / Jaeger | Хранилище трейсов дешёвое | 0,3–0,6 FTE + настройка семплирования | Трассировка от десятков сервисов |
| Grafana как интерфейс | Бесплатно в OSS | 0,2 FTE: провижининг дашбордов как кода, иначе хаос | Почти всегда |
| SaaS (Datadog, New Relic, Grafana Cloud и т. п.) | Много и растёт нелинейно с объёмом; кардинальность и хосты тарифицируются отдельно | 0,2–0,4 FTE: управление квотами и счётом | Команда до ~15 инженеров либо когда 1 FTE дороже подписки |
| Backstage как каталог и вход | Бесплатно | 0,5–1 FTE постоянно: это фронтенд-приложение, которое вы теперь разрабатываете | Когда каталог уже нужен и есть кому его развивать |
Три честных замечания к таблице. Первое: своё почти всегда дешевле по счёту и дороже по людям, и переломная точка обычно проходит там, где стоимость одного инженера сравнивается с подпиской. Считайте в FTE, а не в лицензиях. Второе: у SaaS стоимость растёт быстрее, чем объём — тарифицируются хосты, кастомные метрики, уникальные ряды и удержание, и счёт легко удваивается от изменения, которое выглядит безобидно. Третье: миграция между стеками стоит примерно один человеко-год для сотни сервисов, даже если в приложениях стоит OpenTelemetry: переносятся дашборды, правила алертов, привычки людей и накопленная история. Это аргумент за коллектор посередине с самого начала — и аргумент против лёгкой смены решения раз в два года. Как проводить такие переезды — в главе о миграциях.
С чего начинать и в каком порядке
Порядок важен, потому что каждый шаг делает следующий дешевле, а неверный порядок сжигает год.
- Единый идентификатор запроса раньше всего остального. До хранилищ и дашбордов.
trace_idво всех логах и заголовки контекста во всех вызовах — это фундамент, без которого остальное не склеивается. Даже если трейсы вы ещё нигде не храните. - Каталог сервисов с владельцами. Без него телеметрия обезличена: вы не знаете, кому писать про перерасход и кто дежурит по сервису. Про каталог — следующая глава.
- Метрики RED в шаблоне сервиса. Автоматически, из коробки, без решений разработчика.
- Алерты по SLO вместо алертов по симптомам. Иначе шум съедает доверие к платформе быстрее, чем вы успеете что-то построить.
- Трейсы — после того как контекст прокидывается везде. Раньше они дадут порванные следы, и команды решат, что трассировка «не работает».
- Учёт расхода — как только объём стал заметным: разговор о деньгах задним числом всегда конфликтный. И только потом — профилирование и высокая кардинальность: без первых шести шагов смысла в них нет.
Для компании из 15 инженеров и 6 сервисов честный ответ другой: не стройте платформу наблюдаемости. Возьмите SaaS или один Prometheus с Grafana, договоритесь о формате логов и trace_id, потратьте сэкономленное время на продукт. Платформенная команда из четырёх человек при 15 инженерах — это 20 % штата на инструмент. Порог, за которым централизация начинает окупаться, обычно лежит где-то между 40 и 80 инженерами, и зависит он не от размера, а от числа границ между командами. Подробнее — в заключительной главе трека; про когнитивную нагрузку и границы команд — Team Topologies.
Типичные ошибки платформенной команды
- Мерить успех объёмом данных. «Мы собираем 40 ТБ в неделю» — это описание счёта, а не пользы.
- Тихо резать данные. Любое семплирование, любое удаление меток должно быть видимым, иначе инженер перестанет доверять цифрам — а доверие восстанавливается годами.
- Строить дашборды за команды. Платформа даёт базовый дашборд и хорошие заготовки; доменный дашборд может сделать только тот, кто знает домен. Попытка делать их централизованно превращает платформу в бюро заявок.
- Считать алерты своей ответственностью. Платформа отвечает за доставку алертов и за инструменты; за содержание и пороги отвечает владелец сервиса. Смешение ролей заканчивается тем, что платформенный дежурный ночью разбирается в чужой бизнес-логике.
- Игнорировать язык, на котором инструментирование даётся тяжело. Если в компании много Go, а вы планировали по опыту с Java, вы промахнётесь по срокам в разы.
- Обещать «полную наблюдаемость». Наблюдаемость не бывает полной, бывает достаточная для класса вопросов. Обещание, которое нельзя выполнить, работает против вас.
Мини-итог
- Наблюдаемость — сервис с сетевым эффектом: частичное принятие даёт меньше пользы, чем следует из процента подключённых. Поэтому принятие здесь важнее полноты возможностей сильнее, чем в любом другом платформенном сервисе.
- Контракт двух сторон: платформа отвечает за то, чтобы сигнал доехал, склеился и нашёлся; команда — за то, чтобы в сигнале был смысл. Всё, что не разделено явно, не делает никто.
- Инструментирование — свойство шаблона сервиса, а не проект внедрения. Коллектор между приложением и хранилищем нужен с первого дня, даже когда хранилище одно.
- Золотых путей должно быть несколько (веб-сервис, пакетная задача, клиент), а исключения — считаться: пятое исключение одного типа означает, что путь пора расширить.
- Метрики принятия: доля инцидентов, где первый диагноз получен в платформе; число живых альтернативных стеков; недельная активность инженеров. Терабайты и число дашбордов — метрики тщеславия.
- Экономика решается произведением измерений, а не числом метрик, и распределена по длинному хвосту: три команды дают 60 % счёта. Общие ограничения бьют по невиновным и почти ничего не экономят.
- Окупаемость считается в основном по ежедневной отладке, а не по MTTR: если модель не написана, платформа выглядит как налог — и однажды им станет. И у неё есть свои SLO, а собственная авария обязана иметь заранее написанный сценарий и независимый резервный контур.
Источники
- Charity Majors, Liz Fong-Jones, George Miranda. Observability Engineering — honeycomb.io/observability-engineering
- Google SRE Book, глава «Monitoring Distributed Systems» — sre.google/sre-book/monitoring-distributed-systems
- OpenTelemetry — документация, семантические соглашения и процессоры коллектора, включая хвостовое семплирование
- Prometheus: практика именования и «Cardinality is key»; Grafana Mimir / Loki / Tempo — grafana.com/docs
- Cindy Sridharan. Distributed Systems Observability — oreilly.com
- Noda, Storey, Forsgren, Greiler. DevEx (ACM Queue) и Forsgren et al. The SPACE of Developer Productivity (ACM Queue)
- Skelton, Pais. Team Topologies — teamtopologies.com
Что дальше
Инфраструктура как API: декларативность, каталог, шаблоны сервисов — про то, откуда берётся каталог с владельцами, без которого половина этой главы не работает: как сделать так, чтобы описание сервиса было одним декларативным документом, из которого рождаются и инфраструктура, и телеметрия, и дежурства.