VictoriaTraces: Tracing, Observability та OpenTelemetry
0 (0)

Автор |  19/05/2026
Click to rate this post!
[Total: 0 Average: 0]

На проекті потроху доросли до того, що пора вже мати повноцінний трейсінг – аби побудувати реальний observability, а не просто моніторинг.

Колись давно робив аналогічне з Jaeger – монстр, і десь він так в чорнетках 2019 чи 2020 року і залишився, ну а так як зараз у нас весь стек VictoriaMetrics – сама VictoriaMetrics для метрик та VictoriaLogs для логів – то і для трейсів будемо використовувати рішення від команди VictoriaMetrics – VictoriaTraces.

Тим більш VictoriaTraces набагато легша як в ресурсах – так і в налаштуванні. Мабуть, можна порівняти Loki та VictoriaLogs та Jaeger з VictoriaTraces – аналогічно набагато простіший сетап, набагато менше ресурсів CPU/RAM.

Цей пост планувався першим в серії по traces – тому тут спочатку буде більше теорії по Observability та OpenTelemetry, а далі вже запустимо VictoriaTraces в Kubernetes.

В попередньому пості OpenTelemetry: OTel Collectors в Kubernetes та інтеграція з VictoriaMetrics stack описаний чисто OpenTelemetry stack для метрик і логів, і в цьому пості буду на нього посилатись.

І вже в наступній, третій – подивимось як створювати трейси з Python.

Monitoring vs Observability

“Monitoring is a passive action. Observability is an active practice” – з чудової книги Learning OpenTelemetry, Setting Up and Operating a Modern Observability System.

Перше, про що хочеться написати окремо – це, власне, розібрати різницю між “monitoring” та “observability“.

Їх часто плутають або використовують як взаємозамінні – але хоча вони і дотичні, це все ж про різні концепти.

Отже, Monitoring – це коли ми заздалегідь знаємо, що може зламатись та налаштовуємо перевірки саме на це: “CPU більше 90% => алерт. Диск зайнятий більш ніж на 85% => алерт. Помилки 5xx на ALB => алерт.”

Тобто, ми відповідаємо на питання, які сформулювали наперед. По суті – це dashboard-driven підхід: дивимось на відомі метрики, реагуємо на відомі проблеми.

Observability – це коли система дозволяє відповідати на питання, які ми заздалегідь не формулювали: відбувається щось “дивне”, і ми можемо “розкопати” причину – навіть якщо ніколи раніше з таким не стикалися.

Ключове слово тут – explorability: можливість дослідити зв’язки і причини проблем.

Як приклад – виріс Backend API latency. Моніторинг просто скаже – “латенсі високий” (алерт спрацював), а observability дозволить виконати drill down – пройти весь ланцюжок і знайти root cause: latency спайк на конкретному endpoint? конкретний tenant? конкретний Kubernetes Pod? Може один upstream повільний? Тобто, ми йдемо від симптомудо причини через дані, які вже є в системі.

Власне, тому і кажуть про “three pillars of observability” – Metrics, Logs, Traces. Саме traces (distributed tracing) – це те, що зазвичай відрізняє “просто моніторинг” від observability на практиці, бо саме трейси дають можливість дослідити невідому проблему – бачити шлях запиту через сервіси та знаходити bottleneck, якого ми не передбачували.

Втім, observability – це не про якісь “магічні динамічні алерти”: ми все ще залишаємо в системі звичайні pre-defined алерти типу “якщо 5хх більше 1% – слати повідомлення в Slack”.

Але змінюється те, що відбувається після того, як алерт спрацював: ми не просто бачимо “цей домен повертає помилки” і йдемо вручну грепати логи в VictoriaLogs – а маємо можливість пройти повний шлях: від алерта в Slack – через AWS ALB – через Kubernetes Pod – до компонента в цьому Pod, і врешті-решт до конкретного method() в коді, який повертає помилку та до юзера, на запити від якого цей метод генерує помилки.

Тобто алертинг – це все ще “моніторинг” частина: observability починається в момент, коли алерт спрацював і тобі треба зрозуміти чому.

Observability  – це не про виявлення проблем, а про їх дослідження.

What is: Tracing

Tracing (або distributed tracing) – це спосіб відстежити шлях одного запиту через всю систему: від моменту, коли він прийшов на ALB – через Kubernetes Pod – до бази даних, зовнішнього API або LLM-виклику, і назад.

Далі будемо говорити про VictoriaTraces, яка побудована на VictoriaLogs – бо, власне, концепт трейсінгу той самий, що і у логів: сервіс записує кожен свій “чіх” – кожен виклик, кожну дію, кожен запит до зовнішніх систем. Різниця з “просто логгінгом” в тому, що у traces є ID, який об’єднує всі пов’язані виклики в дерево, що дозволяє будувати повний шлях запиту.

Один такий шлях називається trace. Trace складається зі spans, де кожен span це одна операція – конкретний HTTP-запит, SQL-запит, виклик іншого сервісу, обробка в черзі. Span-и зв’язані між собою в дерево через trace_id (спільний для всього trace) та parent_id (хто викликав цей span).

Виглядає це приблизно так:

trace_id: abc123

[HTTP GET /api/orders]                                        # root span (120ms)
  ├─ [auth-service: validate token]                           # child span (8ms)
  ├─ [orders-service: get orders]                             # child span (95ms)
  │    ├─ [PostgreSQL: SELECT * FROM orders WHERE user_id=42] # (80ms)
  │    └─ [Redis: GET cache:user:42]                          # (2ms)
  └─ [response serialization]                                 # child span (12ms)

Кожен span цього трейсу має поле з trace_id, кожен span має поля parent_id та span_id: у root span поле parent_id буде пусте, у другого span буде parent_id == span_id першого span цього трейсу і так далі.

Кожен span окрім часу виконання може містити атрибути – key-value пари з додатковим контекстом: частина атрибутів додається автоматично (HTTP method, status code, DB statement), частина – вручну розробниками (tenant_id, row_count, cache_hit). Чим більше контексту в атрибутах – тим більше можна дослідити не додаючи нових метрик чи логів.

Наприклад (це вже з наступного посту, там розберемо детальніше):

...
orders-api  |     "attributes": {
orders-api  |         "http.scheme": "http",
orders-api  |         "http.host": "172.25.0.3:8000",
orders-api  |         "net.host.port": 8000,
orders-api  |         "http.flavor": "1.1",
orders-api  |         "http.target": "/api/orders/by-customer/Vasya",
orders-api  |         "http.url": "http://172.25.0.3:8000/api/orders/by-customer/Vasya",
orders-api  |         "http.method": "GET",
orders-api  |         "http.server_name": "localhost:8000",
orders-api  |         "http.user_agent": "curl/8.20.0",
orders-api  |         "net.peer.ip": "172.25.0.1",
orders-api  |         "net.peer.port": 54900,
orders-api  |         "http.route": "/api/orders/by-customer/{name}",
orders-api  |         "customer.name": "Vasya",
orders-api  |         "customer.orders_count": 3,
orders-api  |         "http.status_code": 200
orders-api  |     },
...

Фактично, атрибути в traces – це labels в метриках Prometheus-формату, які ми потім можемо використовувати для пошуку трейсів і – головне – для кореляції пов’язаних метрик, логів та трейсів.

Приклад дебагу з tracing

Повернемось до прикладу, який описувався вище: маємо алерт в Slack, який каже, що Backend API latency на ендпоінт /coach виріс до 20 секунд:

VictoriaTraces: Tracing, Observability та OpenTelemetry

В алерті є лінк на Grafana dashboard зі статусом AWS Application Load Balancer, в дашборді є посилання на VictoriaLogs з логами ALB та Backend API, лінк на дашборду з Kubernetes Pods нашого Backend API та його AWS RDS.

Метрики в Grafana dashboard показують спайк, логи – нічого підозрілого. Без трейсів далі починається гадання – йдемо дивитись CPU/RAM на Kubernetes WorkerNodes, навантаження на пов’язані Pods, Grafana dashboard для AWS RDS з PostgreSQL, намагаємось побудувати картину – де у нас виникає проблема.

З трейсами ми відкриваємо повільні traces для цього endpoint і одразу бачимо: із загальних 120ms на обробку всього реквесту – 80ms йде на виконання одного SQL-запиту. Дивимось атрибути цього span-у – db.statement: SELECT * FROM orders WHERE user_id=42, індекс не використовується: root cause знайдено за хвилину.

What is: the OpenTelemetry

OpenTelemetry (OTel) – це перш за все набір загальних “правил” по тому як дані мають збиратись і які метадані в них мають бути присутні.

Вище згадувались “three pillars of observability” –  Metrics, Logs, Traces: кожна дія сервісу та його компоненту – це events, або Signals в термінології OTel.

OpenTelemetry та його OpenTelemetry Protocol (див. OTLP Specification 1.10.0) описують яким чином дані повинні передаватись (HTTP/gRPC), та які поля і заголовки вони повинні мати, таким чином уніфікуючи метрики, логи та трейси в єдиний формат.

З OTel ми збираємо ці сигнали на рівні коду, з Kubernetes Pods/Nodes або з AWS API, обробляємо, додаючи атрибути та об’єднуючи їх в загальний контекст, і передаємо до бекенду, в якому ці дані зберігаються – метрики до VictoriaMetrics, логи до VictoriaLogs, трейси до VictoriaTraces.

OpenTelemetry vs Prometheus

Коли ми працюємо VictoriaMetrics або Prometheus – у нас є звичний підхід до метрик: exporter виставляє endpoint /metrics, VictoriaMetrics з VMAgent ходить на цей ендпоінт та збирає метрики (PULL-модель). Формат метрик – простий текстовий типу metric_name{label="value"} 123.45.

В OpenTelemetry (OTel) інший підхід, бо зазвичай він працює за PUSH-моделлю: сервіс сам відправляє дані в OTel Collector, а той вже роутить їх куди треба – у VictoriaMetrics, VictoriaLogs, VictoriaTraces чи будь-який інший бекенд.

Втім, OTel Collector receivers можуть і самі виконувати запити до якихось API, наприклад – k8s_cluster робить запити до Kubernetes API /apis/apps/v1/deployments для отримання додаткової інформації по Kubernetes Pods.

Навіщо OpenTelemetry, якщо є Prometheus

Для метрик Prometheus формат і справді працює чудово, але Prometheus – це тільки метрики: він не вміє в трейси, не вміє в structured logs, і головне – не вміє зв’язати метрику, лог і трейс між собою: у нас вже є VictoriaLogs для логів, VictoriaMetrics для метрик – але все це окремі системи зі своїми форматами, тому зв’язати конкретну метрику з конкретним логом і конкретним трейсом – складно, бо в них немає спільного контексту.

OTel вирішує саме цю проблему: коли метрики, логи і трейси проходять через один SDK, то вони автоматично отримують спільний контекст – trace_id, service.name, deployment.environment, kubernetes.pod.name. В результаті ми з алерту по metric_name можемо перейти до трейсу конкретного запиту, а з трейсу – до логів конкретного span-у. Без OTel ці три системи живуть окремо, і зв’язувати їх доводиться вручну.

Компоненти OpenTelemetry

OpenTelemetry складається з трьох основних частин:

  • OTel SDK: вбудовується в код і генерує телеметрію
    • для auto-instrumentation це кілька рядків при старті сервісу – і ми одразу отримуємо span-и для HTTP, gRPC, SQL (див. Instrumentation нижче)
  • OTel Collector: окремий сервіс (DaemonSet або Deployment в Kubernetes), який приймає дані від SDK в сервісах, обробляє і відправляє далі до бекендів
    • той жеж Collector в ролі агента може сам збирати метрики і логи з Kubernetes чи AWS – теж описано в попередньому пості
  • OTLP (OpenTelemetry Protocol): це формат і протокол передачі даних, який працює поверх gRPC або HTTP і підтримується практично всіма сучасними бекендами – VictoriaMetrics, Grafana Tempo, Jaeger, Datadog

OpenTelemetry Instrumentation

Сам термін instrumentation в контексті OpenTelemetry і трейсингу – це процес додавання специфічного коду до сервісу або системи, який дозволяє виконувати observability цього коду.

Див. Instrumentation та Zero-code Instrumentation.

З OpenTelemetry є три шляхи виконати instrumentation:

  • zero-code instrumentation: ми взагалі нічого не міняємо в коді – виклик нашого сервісу відбувається через зовнішній wrapper, який перехоплює виклики нашого коду і сам додає потрібні дані
    • швидко, зручно – але найменш гнучко, бо не дає можливості самому вирішувати що і де додавати
  • auto instrumentation: OTel SDK вміє автоматично створювати span-и для HTTP-запитів, DB-клієнтів, gRPC-викликів, додавати необхідні атрибути
    • для auto-instrumentation використовуємо OTel SDK шляхом додавання бібліотек в наш код, який через власні методи і функції додає інформацію до викликів методів і функцій нашого коду
  • manual instrumentation: можемо додавати власні custom spans та атрибути в коді для бізнес-логіки, яку auto-instrumentation не бачить
    • наприклад, створювати span на обробку одного елементу в batch job, або атрибут order.total_items в SQL-виклику на span обробки замовлення

Як правило, починають з auto-instrumentation (щоб одразу отримати базову картину), а потім додають manual instrumentation поступово – там, де не вистачає контексту для дебагу конкретних проблем.

What is: VictoriaTraces

Документація – VictoriaTraces та Key concepts.

Репозиторій проекту – VictoriaTraces.

VictoriaTraces побудована на VictoriaLogs: вона отримує дані від OTel Collector у вигляді JSON в форматі OTLP та записує їх у власному форматі, трансформуючи імена полів.

Проект поки в статусі This project is currently a work in progress, тому зміни можливі – але вже цілком робочий.

Як і VictoriaLogs, VictoriaTraces формує stream fields, які використовують для оптимізації збереження даних та пошуку логів чи трейсів.

В результаті кожний записаний trace span зберігається як частина конкретного stream – аналогічно до того, як кожен log record у VictoriaLogs – це частина якогось одного конкретного log stream.

У VictoriaTraces для поля stream використовується атрибут service.name, і кожне унікальне значення в stream field впливає на те, скільки даних буде сформоване в VictoriaTraces storage та IndexDB, яка використовується для пошуку даних коли ми робимо sum by (label_name).

Див. VictoriaMetrics: Churn Rate, High cardinality, метрики та IndexDB – бо суть зберігання даних в VictoriaMetrics, VictoriaLogs та VictoriaTraces однакова.

Як і VictoriaMetrics та VictoriaLogs, у VictoriaTraces є власний VM UI, в якому з LogsQL можемо виконувати пошук трейсів:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Хоча для відображення дерева трейсів краще використовувати Grafana – далі зробимо.

Також див. документацію VictoriaTraces по Monitoring – можемо збирати метрики та Retention – трейси, як і логи та метрики, теж зберігаються на диску, тому треба мати на увазі зайняте місце.

І дуже смачна можливість – створювати власні метрики з traces, зробимо в цьому пості далі.

Запуск VictoriaTraces в Kubernetes

Як і у VictoriaLogs, VictoriaTraces є single instance та cluster mode для High Availability – але в моєму випадку single isntance вистачає з головою, тому поки будемо використовувати його.

Для запуску VictoriaTraces в Kubernetes є окремі Helm-чарти – victoria-traces-single та victoria-traces-cluster.

Документація по чарту – VictoriaTraces Single.

Додаємо репозиторій:

$ helm repo add vm https://victoriametrics.github.io/helm-charts/
$ helm repo update

Знаходимо останню доступну версію чарту:

$ helm search repo vm/victoria-traces-single
NAME                            CHART VERSION   APP VERSION     DESCRIPTION                                       
vm/victoria-traces-single       0.0.7           v0.8.0          The VictoriaTraces single Helm chart deploys Vi...

Пишемо values.yaml, дефолтні значення є в репозиторії чарту, наприклад:

victoria-traces-single:
  enabled: true
  server:
    mode: deployment
    ingress:
      enabled: true
      ingressClassName: alb
      annotations:
        alb.ingress.kubernetes.io/group.name: ops-1-33-internal-alb
        alb.ingress.kubernetes.io/target-type: ip
        alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:492***148:certificate/ad0ae28d-1843-412d-b3e1-05235186ea11
      hosts:
        - name: vmtraces.monitoring.1-33.ops.example.co
          path:
            - /
          port: http
    resources:
      requests:
        cpu: 100m
        memory: 256Mi
    persistentVolume:
      enabled: true
      storageClassName: gp3-retain
      size: 50Gi
    retentionPeriod: 30d
    vmServiceScrape:
      enabled: true

Я тут задав тип деплою з Deployment замість StatefulSet та додав Ingress через AWS ALB.

В persistentVolume створюємо диск, в retentionPeriod міняємо дефолтне значення 7 днів на місяць.

Деплоїмо, перевіряємо:

$ kk get deploy atlas-victoriametrics-vt-single-server
NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
atlas-victoriametrics-vt-single-server   1/1     1            1           44h

Перевіряємо Kubernetes Service:

$ kk get svc atlas-victoriametrics-vt-single-server
NAME                                     TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE
atlas-victoriametrics-vt-single-server   ClusterIP   None         <none>        10428/TCP   2d20h

VictoriaTraces приймає OTLP/HTTP на endpoint /insert/opentelemetry/v1/traces.

Можемо з curl пушнути трейс для тесту – відкриваємо порт:

$ kk port-forward svc/atlas-victoriametrics-vt-single-server 10428

Відправляємо JSON з полями, які потім буде створювати наш OTel SDK:

$ curl -v -X POST "http://localhost:10428/insert/opentelemetry/v1/traces" -H "Content-Type: application/json" -d "{\"resourceSpans\":[{\"resource\":{\"attributes\":[{\"key\":\"service.name\",\"value\":{\"stringValue\":\"test-curl\"}}]},\"scopeSpans\":[{\"scope\":{\"name\":\"manual-test\"},\"spans\":[{\"traceId\":\"aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb\",\"spanId\":\"cccccccccccccccc\",\"name\":\"test-span\",\"kind\":2,\"startTimeUnixNano\":\"$(date +%s)000000000\",\"endTimeUnixNano\":\"$(date +%s)000000000\",\"attributes\":[{\"key\":\"http.method\",\"value\":{\"stringValue\":\"GET\"}},{\"key\":\"http.route\",\"value\":{\"stringValue\":\"/api/test\"}}],\"status\":{\"code\":1}}]}]}]}"

Перевіряємо у VM UI – http://localhost:10428/select/vmui/:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Формат запитів – стандартний LogsQL:

{name="test-span"} trace_id:"aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb"

І, власне, VictoriaTraces готова до роботи. Залишилось додати instrumentation в наш код – детальніше в наступній частині, тут просто для прикладу як воно взагалі може виглядати – тут auto instrumentation для FastAPI та asyncpg:

import os
from fastapi import FastAPI, HTTPException
from contextlib import asynccontextmanager
import asyncpg

from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor, BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter


pool = None


@asynccontextmanager
async def lifespan(app: FastAPI):
    global pool
    pool = await asyncpg.create_pool(
        host=os.getenv("DB_HOST", "postgres-test"),
        port=int(os.getenv("DB_PORT", "5432")),
        user=os.getenv("DB_USER", "postgres"),
        password=os.getenv("DB_PASSWORD", "testpass"),
        database=os.getenv("DB_NAME", "demo"),
        min_size=2,
        max_size=10,
    )
    yield
    await pool.close()


# Set up OTel tracer provider
provider = TracerProvider()

# Console exporter (for local debugging)
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
# OTLP exporter to VictoriaTraces
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))

trace.set_tracer_provider(provider)

app = FastAPI(title="Orders API", lifespan=lifespan)

FastAPIInstrumentor.instrument_app(app)
AsyncPGInstrumentor().instrument()

@app.get("/healthz")
async def healthz():
    return {"status": "ok"}


@app.get("/api/orders")
async def list_orders():
    rows = await pool.fetch("SELECT * FROM orders ORDER BY id")
    return [dict(r) for r in rows]


@app.get("/api/orders/{order_id}")
async def get_order(order_id: int):
    row = await pool.fetchrow("SELECT * FROM orders WHERE id = $1", order_id)
    if not row:
        raise HTTPException(status_code=404, detail="Order not found")
    return dict(row)


@app.get("/api/orders/by-customer/{name}")
async def orders_by_customer(name: str):
    # catch currently processing span
    current_span = trace.get_current_span()
    # add attribute to the current span: customer name
    current_span.set_attribute("customer.name", name)
    # fetch orders from the database
    rows = await pool.fetch(
        "SELECT * FROM orders WHERE customer_name = $1 ORDER BY id", name
    )
    # add attribute to the current span: number of orders fetched
    current_span.set_attribute("customer.orders_count", len(rows))
    return [dict(r) for r in rows]

VMAlert та Recording Rules з VictoriaTraces

Цікава фішка – можемо мати Recording Rules, в яких буде використовуватись VictoriaTraces – див. Alerting with traces.

Логіка та сама, як і для Recording Rules і метрик з логів у VictoriaLogs: описуємо правило з type="vlogs"vmalert генерить метрику, а потім цю метрику можемо використати в алертах чи Grafana.

Єдиний нюанс тут – якщо вже є інстанс vmalert для логів, то треба робити другий інстанс – бо в Recording Rules тип однаковий (vlogs), і для самого VMAlert треба вказувати інший datasource.url.

Додавання VMAlert

vmalert можна встановити з Helm chart victoria-metrics-alert, або, як в моєму випадку при використанні victoria-metrics-k8s-stack і VictoriaMetrics Kubernetes Operator – то створити другий інстанс через kind=VMAlert.

Документація по самому vmalertтут>>>.

Приклад з kind: VMAlert:

apiVersion: operator.victoriametrics.com/v1beta1
kind: VMAlert
metadata:
  name: vmalert-traces
spec:
  datasource:
    url: http://atlas-victoriametrics-vt-single-server:10428
  remoteWrite:
    url: http://vmsingle-vm-k8s-stack:8428
  notifiers:
    - url: http://vmalertmanager-vm-k8s-stack.ops-monitoring-ns.svc.cluster.local:9093
  ruleSelector: 
    matchLabels:
      app: vmalert-traces

Тут:

  • datasource.url: задаємо ендпоінт VictoriaTraces – vmalert буде ходити до нього, аби отримати трейси
  • remoteWrite: куди писати згенеровані метрики

remoteRead тут optional – бо цей інстанс тільки генерить метрики.

А от notifiers обов’язковий – хоча алертів він генерити не буде.

В ruleSelector задаємо які саме VMRules використовувати – інакше в ConfigMap цього інстансу VMAlert будуть додані все VMRules.

Додавання VMRule

Спершу в самій VictoriaTraces перевіряємо якийсь запит, наприклад:

{resource_attr:service.name="kraken-prod"} "span_attr:http.route":!""
| stats by ("resource_attr:service.name", "span_attr:http.route", "span_attr:http.status_code") quantile(0.95, duration) p95_duration

Тут отримуємо всі spans з resource_attr:service.name="kraken-prod", вибираємо тільки ті, у яких присутній span_attr:http.route і рахуємо 95 percentile по полю duration:

VictoriaTraces: Tracing, Observability та OpenTelemetry

 

Описуємо сам VMRule, в labels  задаємо app="vmalert-traces" – по ньому ruleSelector буде вибирати тільки цей VMRule:

apiVersion: operator.victoriametrics.com/v1beta1
kind: VMRule
metadata:
  name: recording-rules-vmalert-traces
  labels:
    app: vmalert-traces
spec:
  groups:
    - name: Traces.VictoriaTraces.Logs.rules
      type: vlogs
      interval: 5m

      rules:

        # Target Status: metrics on events from Target Status logs
        - record: vmtraces:kraken:http:request_duration:p95
          expr: |
            {resource_attr:service.name=~"kraken-.*"} "span_attr:http.route":!""
            | stats by ("resource_attr:service.name", "span_attr:http.route", "span_attr:http.status_code") quantile(0.95, duration) p95_duration

Деплоїмо і перевіряємо об’єкти vmalert:

$ kk get vmalert 
NAME             STATUS        REPLICACOUNT   AGE
vm-k8s-stack     operational   1              343d
vmalert-traces   operational                  42m

Тут vm-k8s-stack – це дефолтний vmalert з чарту victoria-metrics-k8s-stack – він займається алертами і має Recording Rules для логів (потім, мабуть, винесу окремими інстансами – один для алертів, один для Recording Rules з логів, один для VictoriaTraces).

І, відповідно, маємо новий Kubernetes Pod:

$ kk get pod | grep vmalert 
vmalert-vm-k8s-stack-f6cdd77d9-mcnks                              2/2     Running     0               3d23h
vmalert-vmalert-traces-b8f77656c-jqzbp                            2/2     Running     0               4m29s

Для якого створена власна ConfigMap:

$ kk get pod vmalert-vmalert-traces-b8f77656c-jqzbp -o yaml | yq '.spec.volumes'
...
  {
    "configMap": {
      "defaultMode": 420,
      "name": "vm-vmalert-traces-rulefiles-0"
    },
    "name": "vm-vmalert-traces-rulefiles-0"
  },
...

В якій містяться правила з VMRule recording-rules-vmalert-traces:

$ kk describe cm vm-vmalert-traces-rulefiles-0 
Name:         vm-vmalert-traces-rulefiles-0
...
Data
====
ops-monitoring-ns-recording-rules-vmalert-traces.yaml:
----
groups:
- name: Traces.VictoriaTraces.Logs.rules
  interval: 5m
  rules:
  - record: vmtraces:kraken:http:request_duration:p95
    expr: |
      {resource_attr:service.name=~"kraken-.*"} "span_attr:http.route":!""
      | stats by ("resource_attr:service.name", "span_attr:http.route", "span_attr:http.status_code") quantile(0.95, duration) p95_duration

Деплоїмо і за хвилину маємо нові метрики в VictoriaMetrics:

VictoriaTraces: Tracing, Observability та OpenTelemetry

 

VictoriaTraces та Grafana

Аби зручно працювати в трейсами – додамо Grafana data source.

Для VictoriaTraces поки використовується дефолтний Jaeger, пізніше, думаю, створять власний плагін – для VictoriaMetrics спочатку теж використовувався звичайний Prometheus plugin, для VictoriaLogs був Loki plugin, потім команда вже додала свої.

Сервіс ми вже знаходили:

$ kk get svc | grep vt
atlas-victoriametrics-vt-single-server                   ClusterIP   None             <none>        10428/TCP                    24h

Перевіряємо Jaeger плагін:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Додаємо новий data source:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Задаємо URL як http://atlas-victoriametrics-vt-single-server:10428/select/jaeger:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Можна  включити Trace to Logs and Metrics – тоді в Grafana Explore по trace_id будуть генеруватись лінки на повязані метрики та логи:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Зберігаємо, перевіряємо:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Переходимо в Explore і шукаємо по Trace ID:

VictoriaTraces: Tracing, Observability та OpenTelemetry

VictoriaLogs, Derived Fields та VictoriaTraces

VictoriaLogs plugin підтримує створення derived fields: можна прямо з логів VictoriaLogs створювати лінк на VictoriaTraces.

Налаштовується в Grafana Connections > VictoriaLogs: вказуємо ім’я, вибираємо тип – в моєму випадку логи в JSON, тому через “Regex in log line”:

"trace_id":\s*"([a-f0-9]{32})"

В поле URL використовуємо ${__value.raw} – сюди буде підставлено значення trace_id.

В Example log message можна перевірити чи вірно працює regex:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Після чого відкриваємо лог з полем trace_id – справа з’явиться блок “Links”:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Який відкриє посилання на цей трейс:

VictoriaTraces: Tracing, Observability та OpenTelemetry

Власне, на цьому сьогодні все.

В наступному пості вже подивимось як використовувати OpenTelemetry instrumentation і з Python app створювати трейси.

Loading