OpenTelemetry: слияние OpenTracing и OpenCensus и что это значит для наших микросервисов
Анонс OpenTelemetry заставил нас пересмотреть самописные метки трассировки и начать переход на семантические конвенции нового стандарта в managed-проектах.
Слияние OpenTracing и OpenCensus в единый проект OpenTelemetry - анонс в феврале 2019 под эгидой CNCF
В феврале пришла новость, которую в сообществе давно ждали и одновременно не верили: OpenTracing и OpenCensus объявили о слиянии в единый проект - OpenTelemetry под эгидой CNCF. Два конкурирующих стандарта инструментирования, которые несколько лет существовали параллельно и создавали головную боль всем, кто хотел просто писать трассировки не думая о деталях - решили объединиться.
Для нас это был хороший повод наконец-то честно посмотреть на то, что происходит с трассировкой в клиентских микросервисах.
Что было до анонса
Ситуация до слияния была, мягко говоря, веселой. У нас в нескольких проектах сложился зоопарк: в одном сервисе разработчики взяли OpenTracing с Jaeger, в другом - OpenCensus потому что там уже был Stackdriver, в третьем вообще самописный middleware который добавлял в заголовки correlation ID по какой-то своей логике. Когда запрос проходил через все три - сквозная трассировка превращалась в детективное расследование.
Spans назывались как придётся. В одном сервисе операция называлась db_query, в другом database.query, в третьем postgres_select. Grafana и Jaeger показывали что-то, но агрегировать это по имени операции было невозможно - никакой согласованности. Семантических конвенций никто не придерживался, потому что до анонса OpenTelemetry они были у каждого проекта свои и спорить было не о чем.
Что даёт слияние
OpenTelemetry - это не просто «давайте возьмём лучшее от обоих». Это новая спецификация с нуля: API, SDK, протокол (OTLP), и - самое важное для нас - семантические конвенции. Именно конвенции нас зацепили больше всего.
Семантические конвенции - это договоренность о том, как называть атрибуты и операции. db.system, db.statement, http.method, http.status_code - не случайные строки, а зафиксированные имена с определёнными правилами. Если все сервисы используют одни конвенции, можно писать запросы в Jaeger или любой другой backend по имени атрибута и получать осмысленный результат.
Пример того, как менялся span в одном из сервисов при рефакторинге:
# было - самописное
span.SetTag("query_type", "select")
span.SetTag("table", "orders")
# стало - по семантическим конвенциям OpenTelemetry
span.SetAttribute("db.system", "postgresql")
span.SetAttribute("db.operation", "SELECT")
span.SetAttribute("db.sql.table", "orders")
Разница кажется косметической, но когда таких атрибутов несколько десятков и они согласованы между всеми сервисами - dashboard в Grafana перестаёт требовать ручного перечисления каждого возможного имени тега.
Что стали делать
После анонса взяли один из managed-проектов с несколькими Python-сервисами и начали методично проходить по инструментированию. Задача была не переписать всё сразу, а понять масштаб расхождений и зафиксировать что именно нужно привести в порядок.
Аудит существующих тегов. Прошлись по коду и выписали все SetTag/SetAttribute вызовы. Получился список примерно из трёх десятков уникальных имён тегов на пять сервисов. Треть из них дублировали одно и то же с разными именами.
Маппинг на конвенции. Взяли черновик семантических конвенций OpenTelemetry (он уже опубликован, хотя и помечен как draft) и прошлись по нашему списку. Большинство атрибутов нашли прямые аналоги. Несколько самописных атрибутов, специфичных для бизнес-логики, решили оставить - конвенции не запрещают добавлять своё, главное не конфликтовать с зарезервированными именами.
Ручная замена в одном сервисе. Выбрали наименее критичный сервис (внутренний cron для агрегации данных) и заменили все теги на конвенционные. Никакого авто-инструментирования - только явные вызовы в коде. Заняло несколько часов, но зато видно где инструментирование было пропущено совсем.
Проблем при замене почти не было. Единственное место где споткнулись - атрибут error. В OpenTracing это булев тег, в черновике OpenTelemetry это атрибут error.type со строковым значением (тип исключения или описание). Несколько мест в коде устанавливали span.SetTag("error", true) - пришлось разобраться что именно там падало и поставить осмысленную строку.
Что отложили в сторону
OpenTelemetry как проект ещё очень сырой. SDK для разных языков - в разной степени готовности. OTLP-коллектор существует как ранний прототип. Миграция с OpenTracing на новые SDK - это отдельная задача, которую откладываем до тех пор, пока проект не устаканится. Сейчас разумнее начать с конвенций в существующем коде, не трогая transport layer.
Jaeger у нас и остаётся бэкендом - он умеет принимать данные по OpenTracing-протоколу, и менять это прямо сейчас не нужно. Когда OpenTelemetry-коллектор и Jaeger-экспортер из него дойдут до production-ready состояния - посмотрим на миграцию.
Где это живёт
В managed-проектах трассировка - это не только отладка, это ещё и аргумент перед заказчиком когда что-то медленно работает. «Смотрите, вот span на 400 мс - это ваш запрос к базе без индекса» работает лучше чем любые объяснения на словах. Поэтому стандартизировать инструментирование хочется не из любви к порядку, а потому что с согласованными именами гораздо проще строить алерты и дашборды.
Пока переход на конвенции OpenTelemetry идёт медленно - по одному сервису, в промежутках между основными задачами. Но теперь хотя бы понятно куда целиться.
- Thanos и remote_write: когда 90 дней метрик Prometheus перестают помещаться · 21 января 2019
- ArgoCD 0.x: GitOps-контроллер для Kubernetes, первое знакомство · 14 января 2019