Платформенная инженерия Наблюдаемость как платформа: единые метрики, логи и трейсы
0%

Наблюдаемость как платформа: единые метрики, логи и трейсы

Наблюдаемость как платформа: единые метрики, логи и трейсы

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 важнее 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. Никакой рекламы: инструмент хороший, но он добавляет вам сервис в эксплуатацию, а не убирает.

Золотой путь наблюдаемости и что делать с теми, кому он не подходит

Золотой путь здесь формулируется коротко: сервис, созданный по шаблону, в день первого деплоя получает всё нужное без единого решения со стороны разработчика. Конкретно — что именно «всё»:

  1. Метрики RED (запросы, ошибки, длительность) для всех входящих и исходящих вызовов, автоматически.
  2. Структурные логи, где trace_id подставляется сам, а поиск по нему открывает и логи, и трейс.
  3. Трейсы со сквозным контекстом через HTTP, gRPC, шину и фоновые задачи.
  4. Базовый дашборд, собранный из контракта сервиса, а не нарисованный руками.
  5. Алерты по объявленным SLO — с бюджетом ошибок и правилами выгорания (SLO, бюджет ошибок).
  6. Ссылки: из алерта — в дашборд, из дашборда — в трейсы за тот же интервал, из трейса — в логи того же спана. Именно эти переходы экономят минуты в 03:00, и именно их обычно забывают сделать.
  7. Строка расхода: сколько сервис стоит по каждому сигналу.

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

  • Пакетная обработка и 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». А разница между «подключено» и «пользуются» видна в том, как проходит один и тот же инцидент.

Разница здесь не в объёме собранных данных — во втором случае их не больше. Разница в трёх переходах: трейс → логи, логи → деплои, диагноз → человек. Каждый из них стоит платформенной команде дней работы (единый идентификатор, общее временное окно, события деплоя как отдельный поток), и каждый экономит минуты в самый дорогой момент. Это и есть предметная работа платформы наблюдаемости — не «собрать данные», а сократить число переключений между инструментами до нуля.

Жизненный цикл телеметрии сервиса

Наблюдаемость сервиса — не бинарное состояние «есть/нет»: у неё есть жизненный цикл, и у платформы должна быть позиция по каждому переходу, включая последний, о котором забывают все.

Состояние «Заброшенный» — это то, что отличает платформу с эксплуатацией от платформы, которую построили и оставили. Через два-три года в любой достаточно большой компании 10–20 % потока телеметрии — данные сервисов, которых уже нет, или сервисов без владельца. Они стоят реальных денег и мешают поиску. Процесс должен быть заранее описан: обнаружение (нет владельца в каталоге или дашборд не открывали 90 дней), уведомление, срок ожидания, отключение с возможностью включить обратно за час. Без последнего пункта отключение никто не решится провести.

Экономика: почему без счёта наблюдаемость становится налогом

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

Кардинальность растёт произведением измерений: одна лишняя метка одного сервиса даёт больше рядов, чем вся остальная платформа

Ключевой момент, который стоит объяснять командам буквально этой картинкой: число метрик не влияет почти ни на что, а число значений у метки влияет на всё. Метрика с десятью метками низкой кардинальности дешевле, чем одна метрика с одной меткой на 50 000 значений. Инженер, который «просто добавил user_id, чтобы было удобнее искать», действовал разумно в рамках своего сервиса и не имел ни одного способа узнать, во что это обойдётся. Виновата не команда — виновата платформа, которая не показала цену в момент решения.

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

Распределение объёма логов по 120 сервисам: три сервиса дают 61 процент, длинный хвост даёт единицы процентов

Практический вывод неочевиден и очень важен для тона всей работы платформы: общие ограничения — это почти всегда неверный ход. Письмо «всем командам сократить логи на 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 качества данных, а не трубы

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

Минимум, который обязан быть у любой платформы наблюдаемости:

  1. Watchdog-алерт — правило, которое срабатывает постоянно и шлёт наружу сигнал «я жив». Пропал сигнал — значит, умерла система алертинга, и об этом надо узнать от внешнего сторожа, а не от пользователей. Такой сторож должен работать вне вашей инфраструктуры: Dead Man’s Snitch, healthchecks-сервис или просто крон в другом облаке.
  2. Независимый путь доставки алертов. Если алерты идут через тот же кластер, что и хранилище, авария кластера означает тишину.
  3. Минимальный резервный контур. Не копия платформы, а несколько десятков жизненно важных метрик, собираемых отдельным простым Prometheus в другом домене отказа, плюс внешняя синтетическая проверка ключевых пользовательских сценариев. Внешняя проверка ценна ещё и тем, что отвечает на вопрос «а вообще работает?» независимо от всей вашей телеметрии.
  4. Написанный сценарий работы вслепую. Как откатить релиз, если дашбордов нет. Кто принимает решение. Где лежат прямые доступы. Проверяется на учениях — см. хаос-инженерию и учения.

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

Типовые провалы

Пять сценариев, которые повторяются из компании в компанию. Каждый начинается с разумного намерения.

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: переносятся дашборды, правила алертов, привычки людей и накопленная история. Это аргумент за коллектор посередине с самого начала — и аргумент против лёгкой смены решения раз в два года. Как проводить такие переезды — в главе о миграциях.

С чего начинать и в каком порядке

Порядок важен, потому что каждый шаг делает следующий дешевле, а неверный порядок сжигает год.

  1. Единый идентификатор запроса раньше всего остального. До хранилищ и дашбордов. trace_id во всех логах и заголовки контекста во всех вызовах — это фундамент, без которого остальное не склеивается. Даже если трейсы вы ещё нигде не храните.
  2. Каталог сервисов с владельцами. Без него телеметрия обезличена: вы не знаете, кому писать про перерасход и кто дежурит по сервису. Про каталог — следующая глава.
  3. Метрики RED в шаблоне сервиса. Автоматически, из коробки, без решений разработчика.
  4. Алерты по SLO вместо алертов по симптомам. Иначе шум съедает доверие к платформе быстрее, чем вы успеете что-то построить.
  5. Трейсы — после того как контекст прокидывается везде. Раньше они дадут порванные следы, и команды решат, что трассировка «не работает».
  6. Учёт расхода — как только объём стал заметным: разговор о деньгах задним числом всегда конфликтный. И только потом — профилирование и высокая кардинальность: без первых шести шагов смысла в них нет.

Для компании из 15 инженеров и 6 сервисов честный ответ другой: не стройте платформу наблюдаемости. Возьмите SaaS или один Prometheus с Grafana, договоритесь о формате логов и trace_id, потратьте сэкономленное время на продукт. Платформенная команда из четырёх человек при 15 инженерах — это 20 % штата на инструмент. Порог, за которым централизация начинает окупаться, обычно лежит где-то между 40 и 80 инженерами, и зависит он не от размера, а от числа границ между командами. Подробнее — в заключительной главе трека; про когнитивную нагрузку и границы команд — Team Topologies.

Типичные ошибки платформенной команды

  • Мерить успех объёмом данных. «Мы собираем 40 ТБ в неделю» — это описание счёта, а не пользы.
  • Тихо резать данные. Любое семплирование, любое удаление меток должно быть видимым, иначе инженер перестанет доверять цифрам — а доверие восстанавливается годами.
  • Строить дашборды за команды. Платформа даёт базовый дашборд и хорошие заготовки; доменный дашборд может сделать только тот, кто знает домен. Попытка делать их централизованно превращает платформу в бюро заявок.
  • Считать алерты своей ответственностью. Платформа отвечает за доставку алертов и за инструменты; за содержание и пороги отвечает владелец сервиса. Смешение ролей заканчивается тем, что платформенный дежурный ночью разбирается в чужой бизнес-логике.
  • Игнорировать язык, на котором инструментирование даётся тяжело. Если в компании много Go, а вы планировали по опыту с Java, вы промахнётесь по срокам в разы.
  • Обещать «полную наблюдаемость». Наблюдаемость не бывает полной, бывает достаточная для класса вопросов. Обещание, которое нельзя выполнить, работает против вас.

Мини-итог

  • Наблюдаемость — сервис с сетевым эффектом: частичное принятие даёт меньше пользы, чем следует из процента подключённых. Поэтому принятие здесь важнее полноты возможностей сильнее, чем в любом другом платформенном сервисе.
  • Контракт двух сторон: платформа отвечает за то, чтобы сигнал доехал, склеился и нашёлся; команда — за то, чтобы в сигнале был смысл. Всё, что не разделено явно, не делает никто.
  • Инструментирование — свойство шаблона сервиса, а не проект внедрения. Коллектор между приложением и хранилищем нужен с первого дня, даже когда хранилище одно.
  • Золотых путей должно быть несколько (веб-сервис, пакетная задача, клиент), а исключения — считаться: пятое исключение одного типа означает, что путь пора расширить.
  • Метрики принятия: доля инцидентов, где первый диагноз получен в платформе; число живых альтернативных стеков; недельная активность инженеров. Терабайты и число дашбордов — метрики тщеславия.
  • Экономика решается произведением измерений, а не числом метрик, и распределена по длинному хвосту: три команды дают 60 % счёта. Общие ограничения бьют по невиновным и почти ничего не экономят.
  • Окупаемость считается в основном по ежедневной отладке, а не по MTTR: если модель не написана, платформа выглядит как налог — и однажды им станет. И у неё есть свои SLO, а собственная авария обязана иметь заранее написанный сценарий и независимый резервный контур.

Источники

Что дальше

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

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

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

Доска запросов