На проекті потроху доросли до того, що пора вже мати повноцінний трейсінг – аби побудувати реальний 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 секунд:
В алерті є лінк на 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 обробки замовлення
- наприклад, створювати span на обробку одного елементу в batch job, або атрибут
Як правило, починають з 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 можемо виконувати пошук трейсів:
Хоча для відображення дерева трейсів краще використовувати 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/:
Формат запитів – стандартний 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:
Описуємо сам 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 та 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 плагін:
Додаємо новий data source:
Задаємо URL як http://atlas-victoriametrics-vt-single-server:10428/select/jaeger:
Можна включити Trace to Logs and Metrics – тоді в Grafana Explore по trace_id будуть генеруватись лінки на повязані метрики та логи:
Зберігаємо, перевіряємо:
Переходимо в Explore і шукаємо по Trace ID:
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:
Після чого відкриваємо лог з полем trace_id – справа з’явиться блок “Links”:
Який відкриє посилання на цей трейс:
Власне, на цьому сьогодні все.
В наступному пості вже подивимось як використовувати OpenTelemetry instrumentation і з Python app створювати трейси.
![]()













