Բոլոր դասերը
DevOps և ամպԱռաջադեմ12 րոպե ընթերցումԱրժե իմանալ

OpenTelemetry

Մեկ բաց ստանդարտ trace-երի, metric-ների և log-երի համար, որպեսզի կարողանաս հետևել request-ին բոլոր service-ներով։

  • Traces
  • Spans
  • Collector
  • Context propagation
Քայլ 1/6
Checkout-ը տևում է 8 վայրկյան, ու օգտատերերը կատաղած են։ Ժամանակն է կանչել դետեկտիվներին՝ metrics, traces և logs։

30 վայրկյանում

OpenTelemetry-ն (OTel) open-source, vendor-ից անկախ ստանդարտ է telemetry հավաքելու համար՝ trace-եր (մեկ request-ի ճանապարհը համակարգով), metric-ներ (թվեր ժամանակի ընթացքում) և log-եր։ Կոդդ մեկ անգամ instrument ես անում OTel SDK-ներով, տվյալները OTLP ձևաչափով ուղարկում ես Collector, և այնտեղից export անում ցանկացած backend՝ Grafana, Jaeger, Datadog, Honeycomb, New Relic, Elastic կամ cloud provider։ Սա CNCF project է, որին աջակցում են գրեթե բոլոր observability vendor-ները։

Իմ ամենօրյա stack-ում չէ․ գիտեմ հիմնական գաղափարները և մանրամասները կսովորեմ, երբ նախագիծը դա պահանջի։

Ինչու է սա կարևոր

  • Մի քանի service-ներով, queue-ներով և բազաներով համակարգում միայն log-երը չեն կարող ասել, թե ինչու մեկ request-ը դանդաղ էր․ distributed trace-ը ցույց է տալիս յուրաքանչյուր քայլը և դրա ժամանակը։
  • Այն վերջ է դնում vendor lock-in-ին․ instrumentation-ը գրվում է մեկ անգամ բաց API-ի դեմ, իսկ backend-ը փոխելը Collector-ի կոնֆիգուրացիայի փոփոխություն է, ոչ թե կոդի վերագրում։
  • Auto-instrumentation-ը HTTP-ի, բազաների, queue-ների և framework-երի համար րոպեների ընթացքում օգտակար trace-եր է տալիս՝ նախքան որևէ custom span գրելը։
  • Թիմերը այն ընդունում են, երբ դուրս են գալիս պարզ uptime ստուգումների և error tracking-ի սահմաններից, և պետք է պատասխանեն «ո՞ւր է գնում ժամանակը» հարցին service-ների միջով։

Ինչպես է աշխատում

01Երեք signal-ները և ինչպես են դրանք կապված

Telemetry-ն գալիս է երեք հիմնական signal-ով։ Trace-ը մեկ request-ի պատմությունն է․ այն span-երի ծառ է, որտեղ յուրաքանչյուր span աշխատանքի մեկ միավոր է (HTTP handler, SQL query, payment API-ի կանչ)՝ սկզբի ժամանակով, տևողությամբ, status-ով և attribute-ներով, ինչպես http.route-ը կամ db.system-ը։ Metric-ները ժամանակի ընթացքում ագրեգացված թվային չափումներ են՝ request-ների հաճախականություն, error rate, latency histogram-ներ, queue-ի խորություն, հիշողություն։ Դրանք էժան է պահել, և հիանալի են dashboard-ների ու alert-ների համար։ Log-երը event-ների timestamp-ով գրառումներ են։ OpenTelemetry-ի ուժը correlation-ն է․ span-ի ներսում գրված log-երը կրում են նրա trace ID-ն և span ID-ն, իսկ metric-ները կարող են կրել exemplar-ներ, որոնք հղում են օրինակելի trace-երին։ Այսպիսով, p95 latency-ի alert-ը քեզ տանում է դանդաղ trace-ի, իսկ trace-ը՝ ճշգրիտ log տողերի։ Trace-երը, metric-ները և log-երը specification-ում stable են, իսկ profiling-ը ավելացվում է որպես չորրորդ signal։

  • Trace = span-երի ծառ մեկ request-ի համար։
  • Metric-ներ = էժան ագրեգատներ alert-ների և dashboard-ների համար։
  • Log-եր = event-ներ, որոնք կապված են trace ID-ով և span ID-ով։

02Ճարտարապետություն՝ API, SDK, exporter-ներ և Collector

OTel-ը պատասխանատվությունները մաքուր է բաժանում։ API-ն այն է, ինչ library-ները և քո կոդը կանչում են span-եր ստեղծելու և metric-ներ գրանցելու համար․ ինքնին այն ոչինչ չի անում, ուստի library-ները կարող են անվտանգ կախված լինել դրանից։ SDK-ն իրականացումն է, որը կարգավորում ես հավելվածում․ այն որոշում է sampling-ը, տվյալները batch է անում և փոխանցում exporter-ներին։ Exporter-ները տվյալները ուղարկում են դուրս, սովորաբար OTLP-ով (OpenTelemetry Protocol), 4317 port-ով gRPC-ի համար կամ 4318-ով HTTP-ի համար։ Instrumentation library-ները patch են անում հայտնի framework-երը և client-ները (Express, Fastify, Next.js, Laravel, pg, mysql, Redis, HTTP client-ներ)՝ span-եր ավտոմատ ստեղծելու համար։ Collector-ը առանձին service է, որը ստանում է telemetry-ն, մշակում այն (batching, ֆիլտրում, զգայուն attribute-ների հեռացում, sampling) և export անում մեկ կամ մի քանի backend։ Կարող ես այն գործարկել որպես agent յուրաքանչյուր հավելվածի կողքին, որպես կենտրոնական gateway կամ երկուսն էլ։ Resource-ները նկարագրում են, թե ով է ուղարկել տվյալները՝ service.name, service.version, deployment.environment.name։

03Node.js service-ի instrumentation

Node.js-ում ամենաարագ սկիզբը SDK-ն է auto-instrumentations փաթեթով։ Տեղադրիր @opentelemetry/sdk-node, @opentelemetry/auto-instrumentations-node և OTLP exporter, ապա փոքր setup ֆայլը բեռնիր հավելվածի կոդից առաջ՝ ES module-ների համար օգտագործելով node --import։ Կարգավորիր ստանդարտ environment variable-ներով, ոչ թե hard-coded արժեքներով, որպեսզի նույն image-ը աշխատի բոլոր միջավայրերում՝ OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_RESOURCE_ATTRIBUTES և OTEL_TRACES_SAMPLER։ Next.js-ն ունի ներկառուցված instrumentation.ts hook և @vercel/otel helper-ը։ Laravel-ը և PHP-ն օգտագործում են OpenTelemetry PHP extension-ը և auto-instrumentation package-ներ։ Սկսիր auto-instrumentation-ից, նայիր իրական trace-երին, ապա custom span-եր ավելացրու միայն կարևոր բիզնես քայլերի շուրջ։

js
// instrumentation.mjs: run with  node --import ./instrumentation.mjs server.mjs
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
 
// Endpoint and service name come from OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_SERVICE_NAME
const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter(),
  metricReader: new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter() }),
  instrumentations: [getNodeAutoInstrumentations({ "@opentelemetry/instrumentation-fs": { enabled: false } })],
});
 
sdk.start();
process.on("SIGTERM", () => sdk.shutdown().finally(() => process.exit(0)));

04Custom span-եր և attribute-ներ

Auto-instrumentation-ը ցույց է տալիս HTTP և բազայի կանչերը, բայց ոչ քո բիզնես տրամաբանությունը։ Span ավելացրու իմաստալից քայլերի շուրջ, ինչպես «գնի հաշվարկ», «պաշարի ամրագրում» կամ «invoice PDF-ի գեներացում», և կցիր attribute-ներ, որոնք հետո կօգնեն որոնել՝ order.items_count, tenant.tier, cache.hit։ Տարածված անունների համար հետևիր semantic convention-ներին (http.request.method, db.system.name, messaging.system), որպեսզի backend-երը հասկանան դրանք։ Հիշիր attribute-ների cardinality-ի մասին․ order ID-ն span-ի վրա նորմալ է, քանի որ span-ը մեկ event է, բայց երբեք user ID-ներ կամ ID-ներով URL-ներ մի դիր metric attribute-ների մեջ, որովհետև յուրաքանչյուր եզակի արժեք նոր time series է ստեղծում։ Exception-ները գրանցիր span-ի վրա և դիր error status, որպեսզի ձախողված request-ները հեշտ գտնվեն։ Երբեք գաղտնաբառեր, token-ներ կամ քարտերի ամբողջական համարներ մի դիր attribute-ներում։

ts
import { trace, SpanStatusCode } from "@opentelemetry/api";
 
const tracer = trace.getTracer("checkout");
 
export async function placeOrder(cart: { items: unknown[] }) {
  return tracer.startActiveSpan("checkout.place_order", async (span) => {
    span.setAttribute("order.items_count", cart.items.length);
    try {
      const order = await saveOrder(cart); // pg / HTTP calls become child spans
      span.setAttribute("order.id", order.id);
      return order;
    } catch (err) {
      span.recordException(err as Error);
      span.setStatus({ code: SpanStatusCode.ERROR });
      throw err;
    } finally {
      span.end();
    }
  });
}

05Collector-ի գործարկում

Telemetry-ն հավելվածներից ուղիղ vendor ուղարկելը աշխատում է, բայց մեջտեղում Collector-ը քեզ վերահսկողություն է տալիս։ Հավելվածները OTLP-ն արագ ուղարկում են լոկալ Collector և վերադառնում աշխատանքի․ Collector-ը batch, retry և buffer է անում, եթե backend-ը դանդաղ է, ուստի հավելվածդ չի տուժում։ Դա ճիշտ վայրն է միջավայրի attribute-ներ ավելացնելու, health check-երի նման աղմկոտ span-երը հեռացնելու, զգայուն տվյալները մաքրելու և tail sampling կիրառելու համար։ Այն կարող է նաև scrape անել Prometheus metric-ներ և կարդալ log ֆայլեր, ուստի դառնում է քո միակ telemetry pipeline-ը։ Processor-ների և exporter-ների ամբողջական հավաքածուի համար օգտագործիր contrib distribution-ը (otelcol-contrib) կամ OpenTelemetry Collector Builder-ով կառուցիր մինիմալը։ Յուրաքանչյուր pipeline-ում memory_limiter processor-ը միշտ դիր առաջինը, իսկ batch-ը՝ վերջինը, և հետևիր Collector-ի սեփական metric-ներին՝ կորած տվյալների համար։

yaml
# otel-collector.yaml
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 }
  attributes/scrub:
    actions: [{ key: http.request.header.authorization, action: delete }]
  batch: {}
exporters:
  otlphttp: { endpoint: https://otlp.example.com }
service:
  pipelines:
    traces: { receivers: [otlp], processors: [memory_limiter, attributes/scrub, batch], exporters: [otlphttp] }
    metrics: { receivers: [otlp], processors: [memory_limiter, batch], exporters: [otlphttp] }

Օգտագործեք, երբ

  • Գործարկում ես մի քանի service, queue կամ background worker և պետք է հետևես request-ին դրանց միջով։
  • Ուզում ես խուսափել մեկ observability vendor-ից կախվածությունից կամ ապագայում պլանավորում ես backend-ը փոխել։
  • Production-ում latency-ն և error-ները debug անելու համար պետք են իրար կապված trace-եր, metric-ներ և log-եր։
  • Քո պլատֆորմը կամ framework-ը արդեն native աջակցում է OTel-ին (Next.js, Kubernetes, cloud load balancer-ներ, շատ բազաներ)։

Մի՛ օգտագործեք, երբ

  • Մեկ փոքր monolith, որտեղ error tracking-ը, uptime ստուգումները և լավ log-երը արդեն պատասխանում են բոլոր հարցերին։
  • Թիմ, որը ժամանակ չունի tracing backend գործարկելու կամ դրա համար վճարելու․ telemetry-ն, որին ոչ ոք չի նայում, միայն ծախս է։
  • Երբ hosted գործիքի ներկառուցված agent-ը արդեն ծածկում է քո կարիքները, և տեղափոխելիությունը քեզ համար կարևոր չէ։

Այլընտրանքներ

  • Vendor agent-ներ (Datadog, New Relic)

    Ավելի արագ all-in-one կարգավորում փայլեցված UI-ներով, բայց proprietary instrumentation և usage-based հաշիվներ։ Մեծ մասն այժմ ընդունում է նաև OTLP։

  • Sentry

    Առաջին հերթին error tracking, tracing-ով․ նրա JavaScript SDK-ն ներսից կառուցված է OpenTelemetry-ի վրա։

  • Միայն Prometheus + log-եր

    Metric-ներ և log-եր առանց trace-երի․ ավելի պարզ և էժան, բայց չես կարող հետևել մեկ request-ին service-ների միջով։

  • Cloud-native գործիքներ

    AWS X-Ray, Google Cloud Trace, Azure Monitor․ բոլորն այժմ ընդունում են OpenTelemetry տվյալներ։

Լավագույն պրակտիկաներ

  1. Յուրաքանչյուր service-ի վրա սահմանիր service.name, service.version և deployment.environment.name։
  2. Կարգավորիր OTEL_* environment variable-ներով, որպեսզի կոդը նույնը մնա բոլոր միջավայրերում։
  3. Սկսիր auto-instrumentation-ից, ապա ավելացրու մի քանի custom span բիզնեսի համար կրիտիկական քայլերի շուրջ։
  4. Տվյալներն ուղարկիր Collector-ով՝ memory_limiter և batch processor-ներով։
  5. Trace-երը sample արա գիտակցաբար․ head sampling՝ ծավալի համար, tail sampling՝ error-ները և դանդաղ request-ները պահելու համար։
  6. Trace ID-ները ներդրիր log-երի մեջ, որպեսզի կարողանաս log տողից անցնել նրա trace-ին։
  7. Metric attribute-ների cardinality-ն ցածր պահիր և երբեք secret-ներ կամ անձնական տվյալներ մի գրանցիր attribute-ներում։

Տարածված սխալներ

  • Instrumentation-ը բեռնել այն բանից հետո, երբ հավելվածն արդեն import է արել իր library-ները, ուստի ոչինչ patch չի արվում։
  • Մոռանալ service.name-ը, ուստի ամեն service երևում է որպես unknown_service։
  • User ID-ներ կամ ամբողջական URL-ներ դնել metric label-ներում և պայթեցնել time series-ների քանակը։
  • Կոտրել context propagation-ը queue-ների կամ custom HTTP client-ների միջով՝ ստանալով իրարից անջատ trace-եր։
  • Production-ում պահել trace-երի 100%-ը և ստանալ հսկայական storage հաշիվ։

Խորը վերլուծություն

06Tail sampling մեծ մասշտաբով

Tail sampling-ը պահում է ամբողջական trace-եր՝ ելնելով նրանից, թե ինչ է տեղի ունեցել դրանցում, բայց trace-ի span-երը գալիս են շատ service-ներից տարբեր ժամանակներում։ Collector-ը span-երը պահում է հիշողության մեջ որոշման սպասման ժամանակահատվածում (հաճախ 10–30 վայրկյան), ապա գնահատում policy-ները՝ status_code ERROR, latency շեմից բարձր, կոնկրետ attribute-ներ կամ probabilistic տոկոս մնացածի համար։ Մի քանի Collector replica-ի դեպքում մեկ trace-ի բոլոր span-երը պետք է հասնեն նույն instance-ին, ուստի գործարկում ես երկու շերտ՝ agent-ներ կամ առաջին gateway շերտ, որն օգտագործում է load-balancing exporter-ը՝ key արված ըստ trace ID-ի, ապա sampling շերտ։ Հիշողությունը չափիր traffic × սպասման ժամանակ բանաձևով և հետևիր processor-ի dropped-traces metric-ներին։ Հիշիր, որ sample-ից դուրս մնացած trace-երը ազդում են նաև span-երից ստացված metric-ների վրա․ RED metric-ները (rate, errors, duration) գեներացրու spanmetrics connector-ով sampling-ից առաջ, որպեսզի dashboard-ները ճշգրիտ մնան։

yaml
processors:
  tail_sampling:
    decision_wait: 15s
    num_traces: 100000
    policies:
      - { name: errors, type: status_code, status_code: { status_codes: [ERROR] } }
      - { name: slow, type: latency, latency: { threshold_ms: 1000 } }
      - { name: baseline, type: probabilistic, probabilistic: { sampling_percentage: 5 } }

07Propagation queue-ների և async աշխատանքի միջով

Ավտոմատ propagation-ը ծածկում է սինխրոն HTTP-ն և gRPC-ն։ Ասինխրոն աշխատանքը ուշադրություն է պահանջում։ Երբ job ես publish անում Redis, RabbitMQ կամ Kafka, ակտիվ context-ը ներդրիր message header-ների մեջ, երբ worker-ը վերցնում է այն, extract արա և սկսիր consumer span։ Semantic convention-ները սա մոդելավորում են producer և consumer span kind-երով։ Երկար հետաձգված job-ը դիզայնի հարց է առաջացնում․ արդյո՞ք job-ի span-երը պետք է լինեն այն ստեղծած request-ի child-եր։ Կարճ pipeline-ների համար՝ այո, բայց մեկ ժամ անց աշխատող job-ը տարօրինակ, շատ երկար trace է ստեղծում։ Այլընտրանքը նոր trace-ն է՝ span link-ով դեպի սկզբնական context, ինչը պահպանում է կապը՝ առանց trace-ը ձգելու։ Batch consumer-ները պետք է օգտագործեն link-եր դեպի շատ producer context-ներ։ Baggage-ը կարող է service-ների միջով կրել արժեքներ, ինչպես tenant ID-ն, բայց այն ուղարկվում է յուրաքանչյուր request-ով, ուստի փոքր պահիր և երբեք secret-ներ մի դիր դրա մեջ։

ts
import { context, propagation, trace } from "@opentelemetry/api";
 
// Producer: carry the trace context inside the job payload
export function withTraceHeaders<T extends object>(payload: T) {
  const headers: Record<string, string> = {};
  propagation.inject(context.active(), headers);
  return { ...payload, otel: headers };
}
 
// Consumer: continue the trace when the job runs
export function runJob(job: { otel?: Record<string, string> }, work: () => Promise<void>) {
  const parent = propagation.extract(context.active(), job.otel ?? {});
  return context.with(parent, () =>
    trace.getTracer("worker").startActiveSpan("job.process", (span) => work().finally(() => span.end())),
  );
}

08Metric-ների դիզայն՝ instrument-ներ, temporality և cardinality

OTel metric-ները օգտագործում են instrument-ներ՝ Counter միայն աճող բաների համար (request-ներ, ուղարկված byte-եր), UpDownCounter աճող և նվազող արժեքների համար (queue-ի չափ, ակտիվ connection-ներ), Histogram բաշխումների համար (latency, payload-ի չափ) և Gauge-ներ ընթացիկ արժեքների համար։ Histogram-ներն են, որ տալիս են p95 և p99․ exponential histogram-ները լավ ճշգրտություն են տալիս ավելի քիչ bucket-ներով։ Temporality-ն կարևոր է export անելիս․ Prometheus-ի ոճի backend-երը սպասում են cumulative արժեքներ, մինչդեռ որոշ vendor-ներ նախընտրում են delta, և exporter-ը կարելի է կարգավորել երկու ձևով էլ։ Cardinality-ն արժեքի հիմնական շարժիչն է․ attribute արժեքների յուրաքանչյուր եզակի համադրություն առանձին time series է։ http.route=/orders/:id-ն նորմալ է, իսկ հում URL-ը՝ /orders/123, աղետ է։ Օգտագործիր view-եր attribute-ները հեռացնելու կամ վերանվանելու և histogram-ի bucket-ները փոխելու համար՝ առանց instrumentation կոդին դիպչելու։

09Overhead, անվտանգություն և աստիճանական ներդրում

Instrumentation-ը անվճար չէ, բայց սովորաբար փոքր է՝ CPU-ի մի քանի տոկոս, հիմնականում span-եր ստեղծելուց և export անելուց։ Այն ցածր պահիր batch span processor-ով (երբեք simple processor-ը production-ում), sampling-ով և աղմկոտ instrumentation-ների անջատմամբ, ինչպես file system-ի կամ DNS-ի span-երը։ Export-ը երբեք չպետք է block անի request-ները․ եթե backend-ը անջատված է, SDK-ն տվյալները դեն է նետում իր queue-ի լցվելուց հետո՝ հավելվածը դանդաղեցնելու փոխարեն։ Զգայուն տվյալները մաքրիր երկու մակարդակում՝ խուսափիր դրանք կոդում հավաքելուց և Collector-ում հեռացրու header-ները, query string-երը և SQL պարամետրերը։ Ներդրիր service առ service՝ սկսելով edge-ից և ամենակրիտիկական ճանապարհից, և համեմատիր latency-ն առաջ և հետո։ Collector-ին վերաբերվիր որպես production ենթակառուցվածքի՝ health check-եր, resource limit-ներ, հորիզոնական scaling և alert-ներ կորած span-երի վրա։

Իրական իրավիճակներ

01

Trace-երը կանգ են առնում queue-ի վրա

Խնդիր

Trace-երը ցույց են տալիս API request-ը, որը job է դնում queue-ի մեջ, բայց worker-ի span-երը երևում են որպես առանձին trace-եր, ուստի ոչ ոք չի տեսնում պատվերի ամբողջական ճանապարհը։

Ինչպես լուծել

Context-ը չի տեղափոխվում queue-ի միջով։ Ստուգիր, թե արդյոք կա instrumentation քո queue library-ի համար, և միացրու այն և՛ producer-ում, և՛ consumer-ում։ Եթե չկա, context-ը ձեռքով ներդրիր job payload-ի մեջ propagation.inject-ով queue-ի մեջ դնելիս, իսկ worker-ում extract արա և handler-ը գործարկիր context.with-ի ներսում։ Որոշիր parent-child-ի և link-երի միջև․ վայրկյանների ընթացքում աշխատող job-երի համար parent-child-ը տալիս է մեկ ընթեռնելի trace, հետաձգված կամ batch job-երի համար սկսիր նոր trace՝ span link-ով։ Ավելացրու messaging.system և messaging.destination.name attribute-ներ։ Backend-ում ստուգիր, որ producer span-ը և consumer span-ը այժմ կիսում են trace ID կամ link։

02

Tracing-ի հաշիվը աճում է traffic-ից ավելի արագ

Խնդիր

Բոլոր service-ները default կարգավորումներով instrument անելուց հետո observability vendor-ի հաշիվը կրկնապատկվեց։ Պահված trace-երի մեծ մասը health check-եր և արագ հաջող request-ներ են։

Ինչպես լուծել

Ավելացրու Collector gateway, եթե չկա։ Health check-երի և readiness probe-երի span-երը հեռացրու filter processor-ով։ Ավելացրու tail sampling՝ պահիր error-ների 100%-ը և քո SLO շեմից դանդաղ request-ները, իսկ մնացածից՝ 2–10%։ RED metric-ները գեներացրու spanmetrics connector-ով sampling-ից առաջ, որպեսզի dashboard-ները և alert-ները ճշգրիտ մնան։ SDK-ում անջատիր ցածր արժեքով instrumentation-ները (fs, dns, net)։ Վերանայիր attribute-ները մեծ արժեքների համար, ինչպես SQL տեքստը կամ payload-ները, և կրճատիր կամ հեռացրու դրանք։ Վերջում յուրաքանչյուր service-ի համար budget սահմանիր և նայիր span-երի քանակին ըստ service-ի՝ ամենաաղմկոտները գտնելու համար։

03

Migration vendor agent-ից OpenTelemetry

Խնդիր

Ընկերությունը օգտագործում է proprietary APM agent և ուզում է գնահատել մեկ այլ backend՝ առանց տեղափոխման ընթացքում տեսանելիությունը կորցնելու։

Ինչպես լուծել

Deploy արա Collector OTLP receiver-ներով։ Մեկ service instrument արա OTel SDK-ով և auto-instrumentation-ներով՝ պահպանելով նույն service անունները, ինչ նախկինում։ Կարգավորիր Collector-ը, որ export անի և՛ ընթացիկ vendor-ին (մեծ մասն ընդունում է OTLP), և՛ նոր backend-ին, որպեսզի դրանք համեմատես նույնական տվյալների վրա։ Service-ները տեղափոխիր մեկ առ մեկ՝ հին agent-ը հեռացնելով միայն այն բանից հետո, երբ OTel տարբերակը հավասար ծածկույթ ցույց տա․ երբեք մի գործարկիր երկու agent, որոնք patch են անում նույն library-ները մեկ process-ում։ Հիմնական dashboard-ները և alert-ները վերստեղծիր semantic convention անուններով։ Գնահատումից հետո vendor-ը փոխելը կամ պահելը Collector-ի կոնֆիգուրացիայի փոփոխություն է՝ առանց կոդի փոփոխությունների։

Արագ ուղեցույց

Տեղադրել Node SDK-ն և auto-instrumentation-ը
npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-http
Zero-code սկիզբ Node.js-ի համար
node --require @opentelemetry/auto-instrumentations-node/register app.js
Service-ին անուն տալ
OTEL_SERVICE_NAME=checkout-api
Ուղղել Collector-ին
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
Ավելացնել resource attribute-ներ
OTEL_RESOURCE_ATTRIBUTES=service.version=1.4.2,deployment.environment.name=production
Պահել նոր trace-երի 10%-ը
OTEL_TRACES_SAMPLER=parentbased_traceidratio OTEL_TRACES_SAMPLER_ARG=0.1
Debug՝ span-երը տպել console-ում
OTEL_TRACES_EXPORTER=console
Գործարկել լոկալ Collector
docker run -p 4317:4317 -p 4318:4318 -v ./otel-collector.yaml:/etc/otelcol-contrib/config.yaml otel/opentelemetry-collector-contrib
Լոկալ trace UI
docker run -p 16686:16686 -p 4318:4318 jaegertracing/jaeger
W3C trace header-ի ձևաչափը
traceparent: 00-<32-hex trace-id>-<16-hex span-id>-01

Հարցեր և պատասխաններ

Q1Ո՞րն է տարբերությունը trace-ի և span-ի միջև։

Span-ը մեկ ժամանակաչափված գործողություն է՝ մուտքային HTTP request, բազայի query, մեկ այլ service-ի կանչ կամ բիզնես տրամաբանության մի հատված։ Այն ունի անուն, սկզբի և ավարտի ժամանակ, status, attribute-ներ և parent։ Trace-ը span-երի ամբողջ ծառն է, որոնք կիսում են մեկ trace ID, և ներկայացնում է մեկ request-ի ճանապարհը համակարգով։ Root span-ը սովորաբար առաջին մուտքի կետն է, օրինակ՝ request-ը API gateway-ում, իսկ child span-երը ցույց են տալիս դրա առաջացրած աշխատանքը։ Trace-ի waterfall-ին նայելով՝ տեսնում ես, թե ուր է գնացել ժամանակը և որտեղ է տեղի ունեցել error-ը, ինչը առանձին log-երը չեն կարող ցույց տալ։

Q2Ի՞նչ է context propagation-ը։

Context propagation-ը այն է, թե ինչպես է trace-ը շարունակվում process-ների սահմաններից այն կողմ։ Երբ A service-ը կանչում է B service-ը, A-ի SDK-ն ընթացիկ trace ID-ն և span ID-ն ներդնում է ելքային request-ի մեջ, default-ով որպես W3C traceparent header, գումարած tracestate և ըստ ցանկության baggage։ B service-ի instrumentation-ը այն extract է անում և իր span-երը ստեղծում որպես A-ի span-ի child-եր, ուստի երկուսն էլ երևում են մեկ trace-ում։ HTTP-ի և gRPC-ի համար դա տեղի է ունենում ավտոմատ։ Message queue-ների համար context-ը պետք է տեղափոխես message header-ներում կամ attribute-ներում և extract անես consumer-ում, ինչը շատ messaging instrumentation-ներ անում են։ Եթե propagation-ը կոտրվում է, ստանում ես առանձին, իրարից անջատ trace-եր։

Q3Ինչո՞ւ օգտագործել Collector՝ հավելվածից ուղիղ export անելու փոխարեն։

Collector-ը հավելվածդ անջատում է backend-ից։ Հավելվածը ուղարկում է մոտակա endpoint և շարունակում աշխատանքը, իսկ Collector-ը հոգում է batching-ը, retry-ները, compression-ը և buffering-ը, երբ backend-ը դանդաղ է։ Այն կենտրոնացնում է մշակումը՝ attribute-ների ավելացում, health check-երի ֆիլտրում, զգայուն տվյալների հեռացում և tail sampling, որին պետք է տեսնել ամբողջական trace-երը, և որը հնարավոր չէ անել մեկ service-ի ներսում։ Այն նաև թույլ է տալիս փոխել կամ ավելացնել backend-եր՝ առանց հավելվածները նորից deploy անելու, և migration-ի ընթացքում նույն տվյալները ուղարկել երկու vendor-ի։ Ուղիղ export-ը նորմալ է արագ սկզբի կամ մեկ փոքր service-ի համար, բայց production համակարգերը սովորաբար Collector ունեն։

Q4Ի՞նչ է sampling-ը, և ո՞ր տեսակը կօգտագործես։

Sampling-ը որոշում է, թե որ trace-երը պահել, որովհետև բարձր traffic-ի դեպքում յուրաքանչյուր trace պահելը թանկ է։ Head sampling-ը որոշում է trace-ի սկզբում, օրինակ՝ պահելով 10%-ը parent-based TraceIdRatio sampler-ով։ Այն էժան է և հետևողական service-ների միջով, բայց կարող է կորցնել քեզ հետաքրքրող հազվադեպ error-ը։ Tail sampling-ը որոշում է trace-ի ավարտից հետո՝ Collector-ում, ուստի կարող է պահել բոլոր error-ները, բոլոր դանդաղ request-ները և մնացածի փոքր տոկոսը։ Դրա համար պետք է, որ trace-ի բոլոր span-երը հասնեն նույն Collector instance-ին, և այն հիշողություն է օգտագործում։ Տարածված համադրություն է SDK-ում թեթև head sampling-ը գումարած tail sampling policy-ներ gateway Collector-ում։

Q5Ի՞նչ չի անում OpenTelemetry-ն։

OpenTelemetry-ն հավաքում և տեղափոխում է telemetry-ն․ այն չի պահում, չի վիզուալացնում և alert չի անում։ Քեզ դեռ պետք է backend՝ Grafana Tempo-ով, Prometheus-ով կամ Mimir-ով և Loki-ով, Jaeger trace-երի համար կամ կոմերցիոն service, ինչպես Honeycomb-ը, Datadog-ը կամ New Relic-ը։ Այն նաև չի որոշում, թե ինչն է կարևոր․ դու ես ընտրում service-ների անունները, attribute-ները, sampling-ը և թե որ բիզնես գործողություններն են արժանի span-երի։ Եվ այն չի փոխարինում error tracking-ին՝ grouping-ով, release tracking-ով և օգտատիրոջ կոնտեքստով, ահա թե ինչու շատ թիմեր այն զուգակցում են Sentry-ի հետ։ Մտածիր դրա մասին որպես ստանդարտ «ջրմուղ», որը քո observability stack-ի մնացած մասը փոխարինելի է դարձնում։

Սենիոր մակարդակի հարցեր

S1Ինչպե՞ս կներդնես OpenTelemetry-ն տարբեր թիմերին պատկանող 40 service-ներում։

Դիտարկիր այն որպես platform product։ Յուրաքանչյուր լեզվի համար տրամադրիր բարակ ներքին package կամ base image, որը SDK-ն, resource attribute-ները, propagator-ները և exporter-ները կարգավորում է OTEL_* variable-ներից, որպեսզի թիմերը ավելացնեն մեկ dependency՝ setup նախագծելու փոխարեն։ Collector-ները գործարկիր որպես agent-ներ (DaemonSet կամ sidecar), գումարած gateway շերտ, որը պատկանում է platform թիմին՝ sampling-ի, մաքրման և routing-ի համար։ Հրապարակիր convention-ներ՝ service-ների անվանում, պարտադիր attribute-ներ, ինչպես tenant tier-ը, որ բիզնես գործողություններն են span ստանում, և cardinality-ի կանոններ metric-ների համար։ Սկսիր request-ի ճանապարհից՝ edge-ից մինչև բազա, որպեսզի trace-երը անմիջապես օգտակար լինեն, ապա ընդլայնիր։ Ընդունումը չափիր span-երի ծածկույթով ըստ service-ի։ Trade-off-ը կենտրոնական վերահսկողությունն է՝ ընդդեմ թիմերի ինքնավարության․ խելամիտ default-ներով «հարթեցված ճանապարհը» սովորաբար հաղթում է։

S2Head sampling, tail sampling, թե՞ առանց sampling-ի․ ինչպե՞ս ես որոշում։

Սկսիր հարցերից և budget-ից։ Եթե traffic-ը ցածր է, պահիր ամեն ինչ․ դա ամենապարզն է, և երբեք տվյալներ չես կորցնում։ Ավելի մեծ ծավալների դեպքում head sampling-ը parent-based ratio sampler-ով էժան է, Collector-ի հիշողության արժեք չունի և trace-երը ամբողջական է պահում service-ների միջով, բայց այն «կույր» է․ հազվադեպ error-ները դեն են նետվում նույն տոկոսով, ինչ հաջողությունները։ Tail sampling-ը պահում է կարևորը (error-ներ, դանդաղ request-ներ, կոնկրետ tenant-ներ) և դեն նետում ձանձրալի traffic-ը, բայց պահանջում է stateful Collector շերտ trace ID-ն հաշվի առնող load balancing-ով, որոշման պատուհանին համապատասխան հիշողություն, և այն latency է ավելացնում, մինչև տվյալները կհայտնվեն։ Շատ թիմեր համադրում են դրանք՝ առատաձեռն head sampling շատ «տաք» service-ներից ծավալը սահմանափակելու համար, tail sampling՝ խելացի ընտրության համար, և span-երից ստացված metric-ներ, որոնք հաշվարկվում են sampling-ից առաջ, որպեսզի ագրեգատ թվերը ճիշտ մնան։

S3Ինչպե՞ս կապել OpenTelemetry trace-երը log-երի հետ գոյություն ունեցող logging համակարգում։

Նպատակն այն է, որ request-ի ներսում գրված յուրաքանչյուր log տող պարունակի trace_id և span_id։ Node.js-ում pino-ի և winston-ի instrumentation package-ները դրանք ավտոմատ ներդնում են log record-ների մեջ․ PHP-ում, Java-ում և Python-ում կան նմանատիպ ինտեգրացիաներ կամ OTel logs bridge-ը։ Պահիր քո գոյություն ունեցող logger-ը․ OTel-ի logs API-ն նախատեսված է ծանոթ library-ներից bridge արվելու համար, ոչ թե ուղիղ կանչվելու։ Ապա կամ log-երն ուղարկիր Collector-ով OTLP-ով, կամ շարունակիր դրանք առաքել որպես JSON ֆայլեր և համոզվիր, որ trace_id դաշտը index է արված։ Backend-ում կարգավորիր link trace view-ից դեպի trace_id-ով ֆիլտրված log query, և log տողերից՝ դեպի trace-եր։ Trade-off-ը ծավալն է․ բոլոր log-երը OTLP-ով ուղարկելը կենտրոնացնում է pipeline-ը, բայց մեծացնում է Collector-ի բեռը, ուստի debug log-երը ֆիլտրիր վաղ։

Բառարան

Span
Մեկ ժամանակաչափված գործողություն trace-ում՝ անունով, տևողությամբ, status-ով, attribute-ներով և parent span-ով։
Trace
Բոլոր span-երը, որոնք կիսում են մեկ trace ID, և ցույց են տալիս մեկ request-ի ճանապարհը համակարգով։
OTLP
OpenTelemetry Protocol-ը trace-եր, metric-ներ և log-եր ուղարկելու համար՝ gRPC-ով (port 4317) կամ HTTP-ով (port 4318)։
Collector
Ինքնուրույն service, որը ստանում, մշակում և export է անում telemetry-ն մեկ կամ մի քանի backend։
Resource
Attribute-ներ, որոնք նկարագրում են telemetry-ի աղբյուրը, ինչպես service.name-ը, տարբերակը և միջավայրը։
Semantic conventions
Ստանդարտ attribute անուններ (http.request.method, db.system.name), որպեսզի գործիքները հասկանան ցանկացած աղբյուրից եկած telemetry-ն։
Context propagation
Trace և span ID-ների փոխանցումը service-ների միջև, սովորաբար W3C traceparent header-ում։
Tail sampling
Որոշել, թե որ trace-երը պահել դրանց ավարտից հետո, օրինակ՝ պահելով բոլոր error-ները և դանդաղ request-ները։
Cardinality
Metric-ում attribute արժեքների եզակի համադրությունների քանակը․ բարձր cardinality-ն բազմապատկում է storage-ի արժեքը։

Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։

Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։