به گزارش از وبسایت cncf، پروژه‌های CNCF که در این مطلب برجسته شده‌اند، اگر قبلاً یک خط‌لوله OpenTelemetry را اجرا می‌کنید، دید خوبی نسبت به برنامه‌های خود فراهم می‌کنند. 😊

این پست درباره موضوعی است که ممکن است هنوز آن را ندیده باشید: ترافیک شرق-به-غرب (east-west) بین سرویس‌ها که در سطح شبکه و بدون هیچ‌گونه تغییر در کد برنامه قابل مشاهده است. پروکسی Linkerd این معیارها را ارائه می‌دهد و هنگام mesh شدن یک بار کاری، بلافاصله برای هر درخواست معیارهای کلیدی را منتشر می‌کند — بدون نیاز به SDK، بازسازی تصویر یا تغییر در کد.

در ادامه نشان می‌دهیم این معیارها چگونه به نظر می‌رسند، در کجا با OTel همپوشانی دارند، در کجا مکمل هم هستند، و چطور می‌توان آن‌ها را به خط‌لوله موجود OTel Collector متصل کرد تا هر دو لایه در یک backend جمع شوند. اگر از فضای mesh آمده‌اید و نمی‌دانید OTel چه چیز اضافه‌ای می‌آورد، در این مطلب پاسخ را خواهید یافت.

محیط مرجع این آزمایش شامل موارد زیر است: K3s نسخه 1.34.6 (تک‌گره)، Linkerd 2.19+ (آزمایش‌شده روی edge-26.5.5، ژوئن 2026)، دموی OpenTelemetry (Bookstore) به‌عنوان بار کاری مشبک، و OTel Collector نسخه 0.118.0. پیکربندی جمع‌آوری و داشبورد Grafana به‌عنوان فایل دانلودی در انتهای این پست قرار دارد.

OTel چه پوششی دارد؟ مشخصات OpenTelemetry سه نوع سیگنال را تعریف می‌کند: Traces، Metrics و Logs. Traces جریان یک درخواست را در مرزهای سرویس دنبال می‌کند و نمودار کامل تماس را می‌دهد. Metrics اندازه‌گیری‌های عددی در طول زمان هستند (شمارنده‌ها، Gaugeها و هیستوگرام‌ها). Logs رویدادهای ساختاری هستند که برنامه منتشر می‌کند. آنچه ابزار خودکار می‌دهد معمولاً شامل تعداد درخواست‌های HTTP، زمان تماس با دیتابیس و عمق صف می‌شود؛ آنچه شما خودتان در کد منتشر می‌کنید، سیگنال‌های لایه کسب‌وکار را می‌سازد (مثل سفارش‌ها، آیتم‌های اضافه‌شده به سبد و تراکنش‌های پرداخت). مثال‌های دموی OTel شامل app_cart_add_item_latency_seconds، app_payment_transactions_total و app_recommendations_counter_total هستند که دانش دامنه (business semantics) را نشان می‌دهند. OTel کد شما را instrument می‌کند، اما به‌تنهایی ترافیک شبکه بین سرویس‌ها را نمی‌بیند مگر اینکه هر دو طرف instrument شده باشند.

معیارهای مشتق‌شده از mesh چه چیزی را پوشش می‌دهند؟ وقتی Linkerd یک sidecar proxy به یک pod تزریق می‌کند، آن پروکسی تمام ترافیک ورودی و خروجی آن بار کاری را رهگیری می‌کند و یک endpoint متریک Prometheus در پورت 4191 بر هر pod مش‌شده را در اختیار می‌گذارد. نیازی به تغییر در کد برنامه نیست: کافی است namespace را annotate کنید و استقرارها را rollout نمایید. محدودهٔ این دید شبکه‌ای معمولاً ترافیک L7 شرق-به-غرب بین بارهای کاری مش‌شده را در بر می‌گیرد؛ ترافیک شمال-به-جنوب موضوعی جداست.

یک یادداشت درباره نسخه‌ها: از زمان نگارش این مطلب، پروژه‌ی منبع باز Linkerd دیگر artifacts پایداری منتشر نمی‌کند و نسخه‌های edge آماده تولید هستند (مراجعه به linkerd.io/releases). توزیع سازمانی پشتیبانی‌شده Buoyant Enterprise for Linkerd (BEL) وجود دارد. آزمایشگاه این مطلب روی edge-26.5.5 اجرا شده است.

قبل از تزریق پروکسی، پورت متریک وجود ندارد. برای نمونه:

kubectl exec -n otel-demo ad-74784f8f59-4nmwp -- wget -qO- http://localhost:4191/metrics 2>&1

خروجی بالا نشان می‌دهد که در 4191 چیزی گوش نمی‌دهد. پس از استقرار و mesh شدن، هر پاد معمولاً از 1/1 به 2/2 آماده تغییر می‌کند؛ کانتینر دوم linkerd-proxy است. اجرای همان فرمان روی پاد جدید (با گزینه -c برای فرود در کانتینر برنامه در صورت نیاز) پاسخ کامل متریک‌ها را برمی‌گرداند. نمونه‌ای از آن خروجی:

# HELP request_total تعداد کل درخواست‌های HTTP.
request_total{direction="inbound", target_addr="10.42.0.217:8080", tls="true", client_id="otel-demo.otel-demo.serviceaccount.identity.linkerd.cluster.local", ...} 20

برچسب client_id هویت mTLS تماس‌گیرنده را نشان می‌دهد — چیزی که هیچ معیاری در سطح برنامه به شما نمی‌دهد: مدرکی که در هر شمارندهٔ درخواست مشخص می‌کند با چه کسی ارتباط برقرار شده است.

خانوادهٔ معیارهایی که این مطلب روی آن‌ها تمرکز دارد و از Linkerd 2.19+ در دسترسند عبارتند از: answer_total (تعداد کل پاسخ‌ها، با برچسب‌هایی مانند direction، classification، status_code و grpc_status)، answer_latency_ms (هیستوگرام تأخیر پاسخ)، و معیارهای سطح TCP مانند tcp_open_connections، tcp_read_bytes_total و tcp_write_bytes_total. این معیارها دید در سطح اتصال و نرخ موفقیت را به‌علاوهٔ جزئیات L7 فراهم می‌کنند. پروکسی همچنین خانوادهٔ معیارهای مسیر را نشان می‌دهد (outbound_http_route_* با هیستوگرام‌های زمان به ثانیه)، اما این‌ها کاردینالیتی بالاتری دارند؛ در این مطلب خط لوله عمداً برخی از آن‌ها را فیلتر می‌کند تا حجم داده زیاد نشود. مرجع کامل متریک‌های پروکسی در linkerd.io/docs/reference/proxy-metrics/ قرار دارد.

همپوشانی وجود دارد: نرخ درخواست، تأخیر و خطاها در هر دو لایه ظاهر می‌شوند اما به‌صورت متفاوت اندازه‌گیری می‌شوند. برای مثال نرخ درخواست: در سمت mesh متریک answer_total{layer=”mesh”} وجود دارد که توسط پروکسی برای هر پاسخ شمارش می‌شود؛ در سمت برنامه متریک app_frontend_requests_total توسط instrumentهای OTel تولید می‌شود. همان سیگنال پایه، اما نام متریک و مجموعهٔ برچسب‌ها متفاوت است — سری mesh شامل client_id، classification و grpc_status است و سری برنامه‌ها ابعادی را دارد که توسعه‌دهندگان انتخاب کرده‌اند.

تأخیر داستان مشابهی دارد: در سمت mesh، answer_latency_ms_bucket{layer=”mesh”} زمان بین دریافت header درخواست و تکمیل جریان پاسخ را اندازه‌گیری می‌کند؛ در سمت برنامه، app_cart_add_item_latency_seconds_bucket زمان ثبت‌شده توسط instrument سرویس cart را می‌سنجد. در یک پنل Grafana می‌توان این دو را کنار هم دید: مثلا “p99 latency: mesh layer vs app layer”. مقایسه نشان می‌دهد که اندازه‌گیری‌های mesh و برنامه یکسان نیستند، چون پروکسی زمان‌بندی در سطح شبکه را می‌سنجد و برنامه زمان پردازش داخلی را. شکاف بین این دو می‌تواند ناشی از سربار شبکه، صف‌بندی یا میان‌افزار کند باشد.

پس به کدام معیار اعتماد کنیم؟ برای هویت mTLS و میزان موفقیت شرق-به-غرب، به متریک‌های mesh اعتماد کنید چون پروکسی اتصال واقعی را مشاهده می‌کند. برای ابعاد تجاری و متریک‌های تخصصی حوزه (business metrics)، به متریک‌های برنامه (OTel) اعتماد کنید، چرا که تنها کد شما معنای تجاری درخواست را می‌داند. برای علت ریشه‌ای (root cause) به distributed traces اعتماد کنید، چون tracing نمودار تماس و گسترهٔ مشکل را نشان می‌دهد.

عدم همپوشانی را ببینیم: در دموی OpenTelemetry سرویس frontend مرتباً با سرویس ads تماس می‌گیرد. mesh چنین رویدادی را می‌بیند و ممکن است آن را به‌عنوان شکست گزارش کند:

answer_total{direction="outbound", status_code="200", dst_service="ad", classification="failure", grpc_status="14", ...} 6

در نگاه اول HTTP status ممکن است 200 را نشان دهد، اما gRPC وضعیت واقعی را در trailerها دارد (مثلاً grpc_status=”14″) که HTTP به‌تنهایی آن را نشان نمی‌دهد؛ پروکسی وضعیت gRPC را می‌خواند و این پاسخ را به‌عنوان شکست طبقه‌بندی می‌کند. در همان زمان یک ردیابی Jaeger برای همان عملیات معمولاً نشان می‌دهد کدام span دقیقاً ناموفق بوده، پیام خطا را و آدرس‌های کلاینت و سرور را — یعنی mesh مشکل را علامت می‌زند و tracing علت اصلی را نشان می‌دهد.

همین تفکیک در مورد متریک‌های تجاری نیز برقرار است: متریک‌هایی مثل app_payment_transactions_total و app_recommendations_counter_total در سمت برنامه ظاهر می‌شوند چون instrumentation برنامه آن‌ها را منتشر می‌کند؛ هیچ پروکسی‌ای نمی‌تواند بفهمد که یک درخواست خاص پرداخت بوده یا recommendation، چون آن دانش در کد دامنه زندگی می‌کند.

الگوی ادغام پیشنهادی این است که متریک‌های mesh را در همان backend متریک‌های OTel قرار دهید و با برچسب‌گذاری مناسب قابلیت تفکیک را حفظ کنید. مکانیزم پیشنهادی یک pipeline اختصاصی در OTel Collector شماست که متریک‌های Prometheus پروکسی را بخواند، فیلتر و برچسب‌گذاری کند و سپس به exporter مقصد بفرستد. نمونه‌ای از پیکربندی Collector در اینجا آورده شده است:

receivers:
  prometheus/mesh:
    config:
      scrape_configs:
        - job_name: linkerd-mesh
          scrape_interval: 30s
          kubernetes_sd_configs:
            - role: pod
          relabel_configs:
            - source_labels: [__meta_kubernetes_pod_container_name]
              action: keep
              regex: linkerd-proxy
            - source_labels: [__meta_kubernetes_pod_ip]
              action: replace
              target_label: __address__
              regex: (.+)
              replacement: $1:4191

processors:
  filter/mesh:
    # نمونهٔ فیلتر برای نگه‌داشتن خانواده‌های متریک منتخب (عبارات کوتاه‌شده برای خوانایی)
    metrics:
      include:
        match_type: strict
        metric_names: ["response_total", "response_latency_ms.*", "tcp_open_connections", "tcp_read_bytes_total", "tcp_write_bytes_total"]

service:
  pipelines:
    metrics/mesh:
      receivers: [prometheus/mesh]
      processors: [filter/mesh, resource/mesh, k8sattrs, batch]
      exporters: [prometheusremotewrite]

این پیکربندی نمونه نشان می‌دهد که چگونه receiver از Kubernetes pod discovery برای پیدا کردن کانتینرهای linkerd-proxy استفاده می‌کند، آدرس هدف را روی پورت 4191 می‌گذارد و سپس با processors مناسب متریک‌ها را فیلتر و برچسب‌گذاری می‌کند تا آماده ارسال به exporter شود. (فایل کامل و جزئیات بیشتر همراه با پست جهت دانلود قرار گرفته است.) 🚀

خلاصه اینکه: mesh و OTel مکمل یکدیگرند — mesh دید شبکه‌ای و هویت mTLS و نرخ‌های موفقیت شرق-به-غرب را می‌دهد، OTel دید تجاری، tracing و متریک‌های دقیقِ اپلیکیشن را می‌آورد. ترکیب هوشمندانهٔ این دو در یک pipeline واحد دید کامل‌تری از سلامت و عملکرد سرویس‌ها فراهم می‌آورد.