ابزاری برای عملکرد بهتر در برنامه شما در Go (قسمت 1: ردیابی)

۱. مقدمه

۵۰۵۸۲۷۱۰۸۸۷۴۶۱۴d.png

آخرین به‌روزرسانی: ۱۵-۰۷-۲۰۲۲

قابلیت مشاهده برنامه

مشاهده‌پذیری و OpenTelemetry

قابلیت مشاهده اصطلاحی است که برای توصیف یک ویژگی از یک سیستم به کار می‌رود. سیستمی با قابلیت مشاهده به تیم‌ها اجازه می‌دهد تا به طور فعال سیستم خود را اشکال‌زدایی کنند. در این زمینه، سه رکن قابلیت مشاهده؛ لاگ‌ها، معیارها و ردیابی‌ها، ابزار اساسی برای سیستم جهت دستیابی به قابلیت مشاهده هستند.

OpenTelemetry مجموعه‌ای از مشخصات، کتابخانه‌ها و عامل‌ها است که ابزارسازی و استخراج داده‌های تله‌متری (لاگ‌ها، معیارها و ردیابی‌ها) مورد نیاز برای مشاهده‌پذیری را تسریع می‌کند. OpenTelemetry یک پروژه استاندارد باز و مبتنی بر جامعه تحت CNCF است. با استفاده از کتابخانه‌هایی که پروژه و اکوسیستم آن ارائه می‌دهند، توسعه‌دهندگان می‌توانند برنامه‌های خود را به روشی بی‌طرف از فروشنده و در برابر معماری‌های متعدد، ابزارسازی کنند.

همچنین علاوه بر سه رکن مشاهده‌پذیری، پروفایل‌سازی مداوم یکی دیگر از مؤلفه‌های کلیدی برای مشاهده‌پذیری است و در حال گسترش پایگاه کاربر در صنعت است. Cloud Profiler یکی از مبتکران این حوزه است و رابط کاربری آسانی را برای بررسی دقیق‌تر معیارهای عملکرد در پشته‌های فراخوانی برنامه فراهم می‌کند.

این آزمایشگاه کد، بخش اول از این مجموعه است و به ابزار دقیق ردیابی‌های توزیع‌شده در میکروسرویس‌ها با OpenTelemetry و Cloud Trace می‌پردازد. بخش دوم، پروفایل‌سازی مداوم با Cloud Profiler را پوشش می‌دهد.

ردیابی توزیع‌شده

در میان لاگ‌ها، معیارها و ردگیری‌ها، ردگیری، تله‌متری است که میزان تأخیر بخش خاصی از فرآیند در سیستم را نشان می‌دهد. به خصوص در عصر میکروسرویس‌ها، ردگیری توزیع‌شده، محرک قوی برای یافتن گلوگاه‌های تأخیر در کل سیستم توزیع‌شده است.

هنگام تجزیه و تحلیل ردیابی‌های توزیع‌شده، تجسم داده‌های ردیابی کلید درک کلی تأخیرهای سیستم در یک نگاه است. در ردیابی توزیع‌شده، ما مجموعه‌ای از فراخوانی‌ها را برای پردازش یک درخواست واحد به نقطه ورودی سیستم در قالب ردیابی حاوی چندین Span مدیریت می‌کنیم.

Span نشان دهنده یک واحد کاری جداگانه است که در یک سیستم توزیع شده انجام می‌شود و زمان‌های شروع و پایان را ثبت می‌کند. Spanها اغلب روابط سلسله مراتبی بین یکدیگر دارند - در تصویر زیر، همه Spanهای کوچکتر، Spanهای فرزند یک Span بزرگ /messages هستند و در یک Trace جمع شده‌اند که مسیر کار را در یک سیستم نشان می‌دهد.

یک ردپا

Google Cloud Trace یکی از گزینه‌های موجود برای ردیابی توزیع‌شده در پس‌زمینه است و به خوبی با سایر محصولات Google Cloud یکپارچه شده است.

آنچه خواهید ساخت

در این آزمایشگاه کد، شما قرار است اطلاعات ردیابی ابزار را در سرویس‌هایی به نام "برنامه شکسپیر" (معروف به Shakesapp) که بر روی یک خوشه موتور Kubernetes گوگل اجرا می‌شود، ردیابی کنید. معماری Shakesapp به شرح زیر است:

44e243182ced442f.png

  • Loadgen یک رشته پرس‌وجو را به صورت HTTP به کلاینت ارسال می‌کند.
  • کلاینت‌ها در gRPC از طریق loadgen درخواست را به سرور ارسال می‌کنند.
  • سرور، کوئری را از کلاینت می‌پذیرد، تمام آثار Shakespare را در قالب متن از Google Cloud Storage دریافت می‌کند، خطوطی را که شامل کوئری هستند جستجو می‌کند و شماره خطی را که با کوئری مطابقت دارد به کلاینت برمی‌گرداند.

شما اطلاعات ردیابی را در سراسر درخواست بررسی خواهید کرد. پس از آن، یک عامل پروفایلر را در سرور تعبیه کرده و گلوگاه را بررسی خواهید کرد.

آنچه یاد خواهید گرفت

  • نحوه شروع کار با کتابخانه‌های OpenTelemetry Trace در پروژه Go
  • نحوه ایجاد دهانه با کتابخانه
  • نحوه انتشار span contextها در سراسر سیم بین اجزای برنامه
  • نحوه ارسال داده‌های ردیابی به Cloud Trace
  • نحوه تجزیه و تحلیل ردیابی در Cloud Trace

این آزمایشگاه کد توضیح می‌دهد که چگونه میکروسرویس‌های خود را ابزاربندی کنید. برای درک آسان، این مثال فقط شامل ۳ جزء (مولد بار، کلاینت و سرور) است، اما می‌توانید همان فرآیند توضیح داده شده در این آزمایشگاه کد را برای سیستم‌های پیچیده‌تر و بزرگتر اعمال کنید.

آنچه نیاز دارید

  • دانش اولیه از Go
  • دانش اولیه از Kubernetes

۲. تنظیمات و الزامات

تنظیم محیط خودتنظیم

اگر از قبل حساب گوگل (جیمیل یا برنامه‌های گوگل) ندارید، باید یکی ایجاد کنید . وارد کنسول پلتفرم ابری گوگل ( console.cloud.google.com ) شوید و یک پروژه جدید ایجاد کنید.

اگر از قبل پروژه‌ای دارید، روی منوی کشویی انتخاب پروژه در سمت چپ بالای کنسول کلیک کنید:

7a32e5469db69e9.png

و در پنجره‌ی باز شده روی دکمه‌ی «پروژه‌ی جدید» کلیک کنید تا یک پروژه‌ی جدید ایجاد شود:

7136b3ee36ebaf89.png

اگر از قبل پروژه‌ای ندارید، باید پنجره‌ای مانند این را برای ایجاد اولین پروژه خود ببینید:

870a3cbd6541ee86.png

پنجره‌ی بعدیِ ایجاد پروژه به شما امکان می‌دهد جزئیات پروژه‌ی جدید خود را وارد کنید:

affdc444517ba805.png

شناسه پروژه را به خاطر بسپارید، که یک نام منحصر به فرد در تمام پروژه‌های Google Cloud است (نام بالا قبلاً گرفته شده و برای شما کار نخواهد کرد، متاسفیم!). بعداً در این آزمایشگاه کد به آن PROJECT_ID گفته خواهد شد.

در مرحله بعد، اگر قبلاً این کار را نکرده‌اید، برای استفاده از منابع Google Cloud و فعال کردن Cloud Trace API ، باید صورتحساب را در کنسول توسعه‌دهندگان فعال کنید .

15d0ef27a8fbab27.png

اجرای این آزمایشگاه کد نباید بیش از چند دلار برای شما هزینه داشته باشد، اما اگر تصمیم به استفاده از منابع بیشتر بگیرید یا اگر آنها را در حال اجرا رها کنید (به بخش "پاکسازی" در انتهای این سند مراجعه کنید)، می‌تواند بیشتر هزینه داشته باشد. قیمت‌های Google Cloud Trace، Google Kubernetes Engine و Google Artifact Registry در اسناد رسمی ذکر شده است.

کاربران جدید پلتفرم ابری گوگل واجد شرایط دریافت یک دوره آزمایشی رایگان ۳۰۰ دلاری هستند که این کدلب را کاملاً رایگان می‌کند.

راه‌اندازی پوسته ابری گوگل

اگرچه می‌توان از راه دور و از طریق لپ‌تاپ، Google Cloud و Google Cloud Trace را مدیریت کرد، اما در این آزمایشگاه کد، از Google Cloud Shell ، یک محیط خط فرمان که در فضای ابری اجرا می‌شود، استفاده خواهیم کرد.

این ماشین مجازی مبتنی بر دبیان، تمام ابزارهای توسعه مورد نیاز شما را در خود جای داده است. این ماشین مجازی یک دایرکتوری خانگی ۵ گیگابایتی دائمی ارائه می‌دهد و در فضای ابری گوگل اجرا می‌شود که عملکرد شبکه و احراز هویت را تا حد زیادی بهبود می‌بخشد. این بدان معناست که تنها چیزی که برای این آزمایشگاه کد نیاز دارید یک مرورگر است (بله، روی کروم‌بوک هم کار می‌کند).

برای فعال کردن Cloud Shell از کنسول Cloud، کافیست روی Activate Cloud Shell کلیک کنید. gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (فقط چند لحظه طول می‌کشد تا آماده شود و به محیط متصل شود).

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSr Dc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

اسکرین شات 2017-06-14 ساعت 10.13.43 PM.png

پس از اتصال به Cloud Shell، باید ببینید که از قبل احراز هویت شده‌اید و پروژه از قبل روی PROJECT_ID شما تنظیم شده است.

gcloud auth list

خروجی دستور

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

خروجی دستور

[core]
project = <PROJECT_ID>

اگر به هر دلیلی پروژه تنظیم نشده باشد، کافیست دستور زیر را اجرا کنید:

gcloud config set project <PROJECT_ID>

به دنبال PROJECT_ID خود هستید؟ بررسی کنید که در مراحل راه‌اندازی از چه شناسه‌ای استفاده کرده‌اید یا آن را در داشبورد Cloud Console جستجو کنید:

۱۵۸fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell همچنین برخی از متغیرهای محیطی را به طور پیش‌فرض تنظیم می‌کند که ممکن است هنگام اجرای دستورات بعدی مفید باشند.

echo $GOOGLE_CLOUD_PROJECT

خروجی دستور

<PROJECT_ID>

در نهایت، منطقه پیش‌فرض و پیکربندی پروژه را تنظیم کنید.

gcloud config set compute/zone us-central1-f

شما می‌توانید مناطق مختلفی را انتخاب کنید. برای اطلاعات بیشتر، به بخش مناطق و نواحی مراجعه کنید.

تنظیمات زبان برو

در این آزمایشگاه کد، ما از Go برای تمام کد منبع استفاده می‌کنیم. دستور زیر را روی Cloud Shell اجرا کنید و بررسی کنید که آیا نسخه Go 1.17+ است یا خیر.

go version

خروجی دستور

go version go1.18.3 linux/amd64

راه‌اندازی یک کلاستر گوگل کوبرنتیز

در این آزمایشگاه کد، شما یک کلاستر از میکروسرویس‌ها را روی موتور کوبرنتیز گوگل (GKE) اجرا خواهید کرد. فرآیند این آزمایشگاه کد به شرح زیر است:

  1. پروژه پایه را در Cloud Shell دانلود کنید
  2. ساخت میکروسرویس‌ها در کانتینرها
  3. کانتینرها را در فهرست آثار باستانی گوگل (GAR) بارگذاری کنید
  4. کانتینرها را روی GKE مستقر کنید
  5. کد منبع سرویس‌ها را برای ابزار دقیق ردیابی تغییر دهید
  6. به مرحله ۲ بروید

فعال کردن موتور Kubernetes

ابتدا، ما یک کلاستر Kubernetes راه‌اندازی می‌کنیم که Shakesapp روی GKE اجرا می‌شود، بنابراین باید GKE را فعال کنیم. به منوی "Kubernetes Engine" بروید و دکمه ENABLE را فشار دهید.

548cfd95bc6d344d.png

اکنون آماده ایجاد یک خوشه Kubernetes هستید.

ایجاد خوشه Kubernetes

در Cloud Shell، دستور زیر را برای ایجاد یک کلاستر Kubernetes اجرا کنید. لطفاً تأیید کنید که مقدار منطقه، زیر منطقه‌ای است که برای ایجاد مخزن Artifact Registry استفاده خواهید کرد. اگر منطقه مخزن شما منطقه را پوشش نمی‌دهد، مقدار منطقه را us-central1-f تغییر دهید.

gcloud container clusters create otel-trace-codelab2 \
--zone us-central1-f \
--release-channel rapid \
--preemptible \
--enable-autoscaling \
--max-nodes 8 \
--no-enable-ip-alias \
--scopes cloud-platform

خروجی دستور

Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s).
Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done.     
Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403
kubeconfig entry generated for otel-trace-codelab2.
NAME: otel-trace-codelab2
LOCATION: us-central1-f
MASTER_VERSION: 1.23.6-gke.1501
MASTER_IP: 104.154.76.89
MACHINE_TYPE: e2-medium
NODE_VERSION: 1.23.6-gke.1501
NUM_NODES: 3
STATUS: RUNNING

ثبت آثار باستانی و تنظیم اسکلت

اکنون یک کلاستر Kubernetes آماده برای استقرار داریم. در مرحله بعد، برای ثبت کانتینرها جهت ارسال و استقرار کانتینرها، آماده می‌شویم. برای این مراحل، باید یک ثبت مصنوعات (GAR) راه‌اندازی کنیم و آن را skaffold کنیم تا از آن استفاده شود.

تنظیم رجیستری مصنوعات

به منوی «ثبت آثار باستانی» بروید و دکمه‌ی «فعال‌سازی» (ENABLE) را فشار دهید.

45e384b87f7cf0db.png

بعد از چند لحظه، مرورگر مخزن GAR را مشاهده خواهید کرد. روی دکمه "ایجاد مخزن" کلیک کنید و نام مخزن را وارد کنید.

d6a70f4cb4ebcbe3.png

در این codelab، من مخزن جدید را trace-codelab نامگذاری می‌کنم. قالب مصنوع "Docker" و نوع مکان "Region" است. منطقه‌ای را انتخاب کنید که به منطقه‌ای که برای منطقه پیش‌فرض Google Compute Engine تعیین کرده‌اید، نزدیک باشد. برای مثال، در این مثال "us-central1-f" در بالا انتخاب شده است، بنابراین در اینجا "us-central1 (Iowa)" را انتخاب می‌کنیم. سپس روی دکمه "CREATE" کلیک کنید.

9c2d1ce65258ef70.png

حالا عبارت "trace-codelab" را در مرورگر مخزن مشاهده می‌کنید.

7a3c1f47346bea15.png

بعداً برای بررسی مسیر رجیستری به اینجا برمی‌گردیم.

نصب اسکفولد

Skaffold ابزاری مفید برای ساخت میکروسرویس‌هایی است که روی Kubernetes اجرا می‌شوند. این ابزار گردش کار ساخت، ارسال و استقرار کانتینرهای برنامه‌ها را با مجموعه‌ای کوچک از دستورات مدیریت می‌کند. Skaffold به طور پیش‌فرض از Docker Registry به عنوان رجیستری کانتینر استفاده می‌کند، بنابراین باید Skaffold را طوری پیکربندی کنید که GAR را هنگام ارسال کانتینرها به آن تشخیص دهد.

دوباره Cloud Shell را باز کنید و تأیید کنید که آیا skaffold نصب شده است یا خیر. (Cloud Shell به طور پیش‌فرض skaffold را در محیط نصب می‌کند.) دستور زیر را اجرا کنید و نسخه skaffold را مشاهده کنید.

skaffold version

خروجی دستور

v1.38.0

اکنون می‌توانید مخزن پیش‌فرض را برای استفاده skaffold ثبت کنید. برای به دست آوردن مسیر رجیستری، به داشبورد Artifact Registry بروید و روی نام مخزنی که در مرحله قبل تنظیم کرده‌اید کلیک کنید.

7a3c1f47346bea15.png

سپس مسیرهای breadcrumbs را در بالای صفحه مشاهده خواهید کرد. کلیک کنید e157b1359c3edc06.png آیکون برای کپی کردن مسیر رجیستری به کلیپ بورد.

e0f2ae2144880b8b.png

با کلیک بر روی دکمه کپی، کادر محاوره‌ای در پایین مرورگر با پیامی مانند زیر مشاهده خواهید کرد:

«us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab» کپی شده است

به پوسته ابری برگردید. دستور skaffold config set default-repo را با مقداری که از داشبورد کپی کرده‌اید، اجرا کنید.

skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab

خروجی دستور

set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox

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

gcloud auth configure-docker us-central1-docker.pkg.dev --quiet

خروجی دستور

{
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud",
    "us-central1-docker.pkg.dev": "gcloud"
  }
}
Adding credentials for: us-central1-docker.pkg.dev

حالا می‌توانید به مرحله بعدی بروید و یک کانتینر Kubernetes روی GKE راه‌اندازی کنید.

خلاصه

در این مرحله، محیط codelab خود را تنظیم می‌کنید:

  • راه‌اندازی پوسته ابری
  • یک مخزن Artifact Registry برای رجیستری کانتینر ایجاد کرد.
  • تنظیم skaffold برای استفاده از رجیستری کانتینر
  • یک کلاستر Kubernetes ایجاد کردیم که میکروسرویس‌های codelab در آن اجرا می‌شوند.

بعدی

در مرحله بعد، شما میکروسرویس‌های خود را ساخته، به کلاستر اضافه و مستقر خواهید کرد.

۳. ساخت، اجرا و استقرار میکروسرویس‌ها

دانلود مطالب codelab

در مرحله قبل، تمام پیش‌نیازهای این codelab را تنظیم کردیم. اکنون آماده‌اید تا کل میکروسرویس‌ها را روی آنها اجرا کنید. مطالب codelab در GitHub میزبانی می‌شود، بنابراین آنها را با دستور git زیر در محیط Cloud Shell دانلود کنید.

cd ~
git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git
cd opentelemetry-trace-codelab-go

ساختار دایرکتوری پروژه به صورت زیر است:

.
├── README.md
├── step0
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step1
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step2
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step3
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step4
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step5
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
└── step6
    ├── manifests
    ├── proto
    ├── skaffold.yaml
    └── src
  • مانیفست‌ها: فایل‌های مانیفست Kubernetes
  • proto: تعریف proto برای ارتباط بین کلاینت و سرور
  • src: دایرکتوری‌های مربوط به کد منبع هر سرویس
  • skaffold.yaml: فایل پیکربندی برای skaffold

در این آزمایشگاه کد، کد منبع موجود در پوشه step0 را به‌روزرسانی خواهید کرد. همچنین می‌توانید برای پاسخ‌های مراحل بعدی به کد منبع موجود در پوشه‌های step[1-6] مراجعه کنید. (بخش 1 مراحل 0 تا step4 و بخش 2 مراحل 5 و 6 را پوشش می‌دهد)

دستور skaffold را اجرا کنید

در نهایت، شما آماده‌اید تا کل محتوا را روی کلاستر Kubernetes که ایجاد کرده‌اید، بسازید، بارگذاری کنید و مستقر کنید. به نظر می‌رسد که این شامل چندین مرحله است، اما در واقع skaffold همه چیز را برای شما انجام می‌دهد. بیایید این کار را با دستور زیر امتحان کنیم:

cd step0
skaffold dev

به محض اجرای دستور، خروجی لاگ docker build را مشاهده می‌کنید و می‌توانید تأیید کنید که آنها با موفقیت به رجیستری منتقل شده‌اند.

خروجی دستور

...
---> Running in c39b3ea8692b
 ---> 90932a583ab6
Successfully built 90932a583ab6
Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1
The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice]
cc8f5a05df4a: Preparing
5bf719419ee2: Preparing
2901929ad341: Preparing
88d9943798ba: Preparing
b0fdf826a39a: Preparing
3c9c1e0b1647: Preparing
f3427ce9393d: Preparing
14a1ca976738: Preparing
f3427ce9393d: Waiting
14a1ca976738: Waiting
3c9c1e0b1647: Waiting
b0fdf826a39a: Layer already exists
88d9943798ba: Layer already exists
f3427ce9393d: Layer already exists
3c9c1e0b1647: Layer already exists
14a1ca976738: Layer already exists
2901929ad341: Pushed
5bf719419ee2: Pushed
cc8f5a05df4a: Pushed
step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001

پس از بارگذاری تمام کانتینرهای سرویس، استقرار Kubernetes به طور خودکار آغاز می‌شود.

خروجی دستور

sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997
Tags used in deployment:
 - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe
 - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8
 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a
Starting deploy...
 - deployment.apps/clientservice created
 - service/clientservice created
 - deployment.apps/loadgen created
 - deployment.apps/serverservice created
 - service/serverservice created

پس از استقرار، گزارش‌های واقعی برنامه را که به stdout در هر کانتینر ارسال می‌شوند، مانند این خواهید دید:

خروجی دستور

[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:16 {"match_count":3040}
[loadgen] 2022/07/14 06:33:16 query 'love': matched 3040
[client] 2022/07/14 06:33:19 {"match_count":463}
[loadgen] 2022/07/14 06:33:19 query 'tear': matched 463
[loadgen] 2022/07/14 06:33:20 query 'world': matched 728
[client] 2022/07/14 06:33:20 {"match_count":728}
[client] 2022/07/14 06:33:22 {"match_count":463}
[loadgen] 2022/07/14 06:33:22 query 'tear': matched 463

توجه داشته باشید که در این مرحله، می‌خواهید هرگونه پیامی را از سرور مشاهده کنید. بسیار خب، بالاخره آماده‌اید تا برنامه خود را با OpenTelemetry برای ردیابی توزیع‌شده سرویس‌ها تجهیز کنید.

قبل از شروع به ابزاربندی سرویس، لطفاً کلاستر خود را با Ctrl-C خاموش کنید.

خروجی دستور

...
[client] 2022/07/14 06:34:57 {"match_count":1}
[loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1
^CCleaning up...
 - W0714 06:34:58.464305   28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.
 - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

خلاصه

در این مرحله، شما مواد codelab را در محیط خود آماده کرده‌اید و تأیید کرده‌اید که skaffold مطابق انتظار اجرا می‌شود.

بعدی

در مرحله بعد، کد منبع سرویس loadgen را برای ابزار دقیق اطلاعات ردیابی تغییر خواهید داد.

۴. ابزار دقیق برای HTTP

مفهوم ابزار دقیق ردیابی و انتشار

قبل از ویرایش کد منبع، اجازه دهید به طور خلاصه نحوه عملکرد ردپاهای توزیع‌شده را در یک نمودار ساده توضیح دهم.

6be42e353b9bfd1d.png

در این مثال، ما کد را برای ارسال اطلاعات Trace و Span به Cloud Trace و انتشار زمینه ردیابی در سراسر درخواست از سرویس loadgen به سرویس سرور، تنظیم می‌کنیم.

برنامه‌ها باید متادیتای Trace مانند Trace ID و Span ID را ارسال کنند تا Cloud Trace بتواند تمام spanهایی که Trace ID یکسانی دارند را در یک trace جمع‌آوری کند. همچنین، برنامه باید در هنگام درخواست سرویس‌های پایین‌دستی، زمینه‌های trace (ترکیبی از Trace ID و Span ID مربوط به span والد) را منتشر کند تا بتواند از اینکه کدام زمینه trace را مدیریت می‌کنند، آگاه باشد.

OpenTelemetry به شما کمک می‌کند:

  • برای تولید Trace ID و Span ID منحصر به فرد
  • برای ارسال Trace ID و Span ID به backend
  • برای انتشار زمینه‌های ردیابی به سرویس‌های دیگر
  • برای جاسازی فراداده‌های اضافی که به تجزیه و تحلیل ردپاها کمک می‌کنند

اجزا در OpenTelemetry Trace

b01f7bb90188db0d.png

فرآیند ردیابی برنامه با OpenTelemetry به شرح زیر است:

  1. ایجاد یک صادرکننده
  2. یک TracerProvider ایجاد کنید که export را در 1 متصل کند و آن را سراسری (global) تنظیم کنید.
  3. TextMapPropagaror را برای تنظیم روش انتشار تنظیم کنید
  4. ردیاب را از TracerProvider دریافت کنید
  5. تولید Span از Tracer

در حال حاضر، نیازی به درک جزئیات خواص هر جزء ندارید، اما مهمترین نکته‌ای که باید به خاطر داشته باشید این است:

  • صادرکننده در اینجا به TracerProvider قابل اتصال است
  • TracerProvider تمام پیکربندی‌های مربوط به نمونه‌برداری و خروجی گرفتن از ردپا را در خود نگه می‌دارد.
  • همه ردپاها در شیء Tracer قرار دارند

با درک این موضوع، بیایید به سراغ کار کدنویسی واقعی برویم.

دهانه اول ابزار

خدمات ژنراتور بار ابزار دقیق

با فشار دادن دکمه، ویرایشگر Cloud Shell را باز کنید 776a11bfb2122549.png در بالا سمت راست Cloud Shell. از پنجره اکسپلورر در پنل سمت چپ step0/src/loadgen/main.go را باز کنید و تابع main را پیدا کنید.

step0/src/loadgen/main.go

func main() {
        ...
        for range t.C {
                log.Printf("simulating client requests, round %d", i)
                if err := run(numWorkers, numConcurrency); err != nil {
                        log.Printf("aborted round with error: %v", err)
                }
                log.Printf("simulated %d requests", numWorkers)
                if numRounds != 0 && i > numRounds {
                        break
                }
                i++
        }
}

در تابع اصلی، حلقه‌ای را می‌بینید که تابع را فراخوانی می‌کند و در آن run . در پیاده‌سازی فعلی، این بخش دارای ۲ خط لاگ است که شروع و پایان فراخوانی تابع را ثبت می‌کنند. حال بیایید اطلاعات Span را برای ردیابی تأخیر فراخوانی تابع، ابزاربندی کنیم.

ابتدا، همانطور که در بخش قبل اشاره شد، بیایید کل پیکربندی‌های OpenTelemetry را تنظیم کنیم. بسته‌های OpenTelemetry را به شرح زیر اضافه کنید:

step0/src/loadgen/main.go

import (
        "context" // step1. add packages
        "encoding/json"
        "fmt"
        "io"
        "log"
        "math/rand"
        "net/http"
        "net/url"
        "time"
        // step1. add packages
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
        "go.opentelemetry.io/otel/trace"
        // step1. end add packages
)

برای خوانایی بیشتر، یک تابع راه‌اندازی به نام initTracer ایجاد می‌کنیم و آن را در تابع main فراخوانی می‌کنیم.

step0/src/loadgen/main.go

// step1. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // create a stdout exporter to show collected spans out to stdout.
        exporter, err := stdout.New(stdout.WithPrettyPrint())
        if err != nil {
                return nil, err
        }

        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

ممکن است متوجه شده باشید که روش راه‌اندازی OpenTelemetry همانطور که در بخش قبل توضیح داده شد، است. در این پیاده‌سازی، ما از یک صادرکننده stdout استفاده می‌کنیم که تمام اطلاعات ردیابی را در قالبی ساختاریافته به stdout صادر می‌کند.

سپس آن را از تابع main فراخوانی می‌کنید. تابع initTracer() را فراخوانی کنید و مطمئن شوید که هنگام بستن برنامه، تابع TracerProvider.Shutdown() را نیز فراخوانی می‌کنید.

step0/src/loadgen/main.go

func main() {
        // step1. setup OpenTelemetry
        tp, err := initTracer()
        if err != nil {
                log.Fatalf("failed to initialize TracerProvider: %v", err)
        }
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step1. end setup

        log.Printf("starting worder with %d workers in %d concurrency", numWorkers, numConcurrency)
        log.Printf("number of rounds: %d (0 is inifinite)", numRounds)
        ...

پس از اتمام تنظیمات، باید یک Span با یک Trace ID و Span ID منحصر به فرد ایجاد کنید. OpenTelemetry یک کتابخانه مفید برای آن فراهم می‌کند. بسته‌های جدید بیشتری را به کلاینت HTTP ابزار اضافه کنید.

step0/src/loadgen/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "math/rand"
        "net/http"
        "net/http/httptrace" // step1. add packages
        "net/url"
        "time"
        // step1. add packages
        "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        // step1. end add packages
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
        "go.opentelemetry.io/otel/trace"
)

از آنجا که مولد بار، سرویس کلاینت را در HTTP با تابع net/http در runQuery فراخوانی می‌کند، ما از پکیج contrib برای net/http استفاده می‌کنیم و ابزار دقیق را با پسوند httptrace و پکیج otelhttp فعال می‌کنیم.

ابتدا یک متغیر سراسری به نام httpClient به پکیج اضافه می‌کنیم تا درخواست‌های HTTP را از طریق کلاینتِ ابزاربندی‌شده فراخوانی کنیم.

step0/src/loadgen/main.go

var httpClient = http.Client{
        Transport: otelhttp.NewTransport(http.DefaultTransport)
}

در مرحله بعد، ابزار دقیق را در تابع runQuery اضافه کنید تا با استفاده از OpenTelemetry و span تولید شده خودکار از کلاینت HTTP سفارشی، span سفارشی ایجاد شود. کاری که شما انجام خواهید داد به شرح زیر است:

  1. با استفاده از otel.Tracer() ‎ یک Tracer از TracerProvider سراسری دریافت کنید.
  2. ایجاد یک محدوده ریشه با استفاده از متد Tracer.Start()
  3. پایان دادن به ریشه در یک زمان دلخواه (در این مورد، پایان تابع runQuery )

step0/src/loadgen/main.go

        reqURL.RawQuery = v.Encode()
        // step1. replace http.Get() with custom client call
        // resp, err := http.Get(reqURL.String())

        // step1. instrument trace
        ctx := context.Background()
        tr := otel.Tracer("loadgen")
        ctx, span := tr.Start(ctx, "query.request", trace.WithAttributes(
                semconv.TelemetrySDKLanguageGo,
                semconv.ServiceNameKey.String("loadgen.runQuery"),
                attribute.Key("query").String(s),
        ))
        defer span.End()
        ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx))
        req, err := http.NewRequestWithContext(ctx, "GET", reqURL.String(), nil)
        if err != nil {
                return -1, fmt.Errorf("error creating HTTP request object: %v", err)
        }
        resp, err := httpClient.Do(req)
        // step1. end instrumentation
        if err != nil {
                return -1, fmt.Errorf("error sending request to %v: %v", reqURL.String(), err)
        }

حالا کار شما با ابزار دقیق در loadgen (برنامه کلاینت HTTP) تمام شده است. لطفاً مطمئن شوید که go.mod و go.sum خود را با دستور go mod به‌روزرسانی کرده‌اید.

go mod tidy

خدمات مشتری ابزار دقیق

در بخش قبل، بخشی که در مستطیل قرمز رنگ در شکل زیر مشخص شده است را instrumentation کردیم. ما اطلاعات span را در سرویس load generator instrumentation کردیم. مشابه سرویس load generator، اکنون باید سرویس client را instrumentation کنیم. تفاوت آن با سرویس load generator این است که سرویس client باید اطلاعات Trace ID منتشر شده از سرویس load generator در هدر HTTP را استخراج کرده و از ID برای تولید Spans استفاده کند.

bcaccd06691269f8.png

ویرایشگر Cloud Shell را باز کنید و بسته‌های مورد نیاز را مانند کاری که برای سرویس مولد بار انجام دادیم، اضافه کنید.

مرحله 0/src/client/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "net/http"
        "net/url"
        "os"
        "time"

        "opentelemetry-trace-codelab-go/client/shakesapp"
        // step1. add new import
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        "go.opentelemetry.io/otel/trace"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials/insecure"
        // step1. end new import
)

دوباره، باید OpenTelemtry را تنظیم کنیم. فقط تابع initTracer را از loadgen کپی و جایگذاری کنید و آن را در تابع main سرویس کلاینت نیز فراخوانی کنید.

مرحله 0/src/client/main.go

// step1. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // create a stdout exporter to show collected spans out to stdout.
        exporter, err := stdout.New(stdout.WithPrettyPrint())
        if err != nil {
                return nil, err
        }

        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

حالا وقت آن رسیده که spanها را instrumentation کنیم. از آنجایی که سرویس کلاینت باید درخواست‌های HTTP را از سرویس loadgen بپذیرد، باید handler را instrumentation کند. سرور HTTP در سرویس کلاینت با net/http پیاده‌سازی شده است و می‌توانید از بسته otelhttp مانند کاری که در loadgen انجام دادیم، استفاده کنید.

ابتدا، ثبت هندلر را با otelhttp Handler جایگزین می‌کنیم. در تابع main ، خطوطی را پیدا کنید که در آنها هندلر HTTP با http.HandleFunc() ثبت شده است.

مرحله 0/src/client/main.go

        // step1. change handler to intercept OpenTelemetry related headers
        // http.HandleFunc("/", svc.handler)
        otelHandler := otelhttp.NewHandler(http.HandlerFunc(svc.handler), "client.handler")
        http.Handle("/", otelHandler)
        // step1. end intercepter setting
        http.HandleFunc("/_genki", svc.health)

سپس، span واقعی را درون handler ابزاربندی می‌کنیم. تابع func (*clientService) handler() را پیدا کنید و ابزاربندی span را با trace.SpanFromContext() اضافه کنید.

مرحله 0/src/client/main.go

func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) {
        ...
        ctx := r.Context()
        ctx, cancel := context.WithCancel(ctx)
        defer cancel()
        // step1. instrument trace
        span := trace.SpanFromContext(ctx)
        defer span.End()
        // step1. end instrument
        ...

با این ابزار، شما spanها را از ابتدای متد handler تا انتهای آن دریافت می‌کنید. برای اینکه spanها به راحتی قابل تجزیه و تحلیل باشند، یک ویژگی اضافی اضافه کنید که تعداد تطبیق یافته را به پرس و جو ذخیره کند. درست قبل از خط log، کد زیر را اضافه کنید.

مرحله 0/src/client/main.go

func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) {
        ...
        // step1. add span specific attribute
        span.SetAttributes(attribute.Key("matched").Int64(resp.MatchCount))
        // step1. end adding attribute
        log.Println(string(ret))
        ...

با تمام ابزارهای بالا، ردیابی ابزار بین loadgen و کلاینت را تکمیل کردید. بیایید ببینیم چگونه کار می‌کند. کد را دوباره با skaffold اجرا کنید.

skaffold dev

پس از مدتی اجرای سرویس‌ها روی کلاستر GKE، حجم عظیمی از پیام‌های لاگ مانند این را مشاهده خواهید کرد:

خروجی دستور

[loadgen] {
[loadgen]       "Name": "query.request",
[loadgen]       "SpanContext": {
[loadgen]               "TraceID": "cfa22247a542beeb55a3434392d46b89",
[loadgen]               "SpanID": "18b06404b10c418b",
[loadgen]               "TraceFlags": "01",
[loadgen]               "TraceState": "",
[loadgen]               "Remote": false
[loadgen]       },
[loadgen]       "Parent": {
[loadgen]               "TraceID": "00000000000000000000000000000000",
[loadgen]               "SpanID": "0000000000000000",
[loadgen]               "TraceFlags": "00",
[loadgen]               "TraceState": "",
[loadgen]               "Remote": false
[loadgen]       },
[loadgen]       "SpanKind": 1,
[loadgen]       "StartTime": "2022-07-14T13:13:36.686751087Z",
[loadgen]       "EndTime": "2022-07-14T13:14:31.849601964Z",
[loadgen]       "Attributes": [
[loadgen]               {
[loadgen]                       "Key": "telemetry.sdk.language",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "go"
[loadgen]                       }
[loadgen]               },
[loadgen]               {
[loadgen]                       "Key": "service.name",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "loadgen.runQuery"
[loadgen]                       }
[loadgen]               },
[loadgen]               {
[loadgen]                       "Key": "query",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "faith"
[loadgen]                       }
[loadgen]               }
[loadgen]       ],
[loadgen]       "Events": null,
[loadgen]       "Links": null,
[loadgen]       "Status": {
[loadgen]               "Code": "Unset",
[loadgen]               "Description": ""
[loadgen]       },
[loadgen]       "DroppedAttributes": 0,
[loadgen]       "DroppedEvents": 0,
[loadgen]       "DroppedLinks": 0,
[loadgen]       "ChildSpanCount": 5,
[loadgen]       "Resource": [
[loadgen]               {
[loadgen]                       "Key": "service.name",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "unknown_service:loadgen"
...

صادرکننده‌ی stdout این پیام‌ها را منتشر می‌کند. متوجه خواهید شد که والدین تمام spanهای ایجاد شده توسط loadgen دارای TraceID: 00000000000000000000000000000000 ، زیرا این span ریشه است، یعنی اولین span در trace. همچنین متوجه می‌شوید که ویژگی embed شده "query" دارای رشته‌ی پرس‌وجویی است که به سرویس کلاینت ارسال می‌شود.

خلاصه

در این مرحله، شما سرویس تولیدکننده بار و سرویس کلاینت را که از طریق HTTP ارتباط برقرار می‌کنند، تجهیز کرده‌اید و تأیید کرده‌اید که می‌توانید با موفقیت Trace Context را بین سرویس‌ها منتشر کنید و اطلاعات Span را از هر دو سرویس به stdout صادر کنید.

بعدی

در مرحله بعد، سرویس کلاینت و سرویس سرور را برای تأیید نحوه انتشار Trace Context از طریق gRPC، ابزارسنجی خواهید کرد.

۵. ابزار دقیق برای gRPC

در مرحله قبل، نیمه اول درخواست را در این میکروسرویس‌ها به صورت ابزاری پیاده‌سازی کردیم. در این مرحله، سعی می‌کنیم ارتباط gRPC بین سرویس کلاینت و سرویس سرور را به صورت ابزاری پیاده‌سازی کنیم. (مستطیل سبز و بنفش در تصویر زیر)

75310d8e0e3b1a30.png

ابزار دقیق پیش ساخته برای کلاینت gRPC

اکوسیستم OpenTelemetry کتابخانه‌های مفید زیادی را ارائه می‌دهد که به توسعه‌دهندگان در ابزارسازی برنامه‌ها کمک می‌کند. در مرحله قبل، ما از ابزارسازی پیش‌ساخته برای پکیج net/http استفاده کردیم. در این مرحله، از آنجایی که سعی داریم Trace Context را از طریق gRPC منتشر کنیم، از کتابخانه برای آن استفاده می‌کنیم.

ابتدا، بسته‌ی از پیش ساخته شده‌ی gRPC به نام otelgrpc را وارد می‌کنید.

مرحله 0/src/client/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "net/http"
        "net/url"
        "os"
        "time"

        "opentelemetry-trace-codelab-go/client/shakesapp"
        // step2. add prebuilt gRPC package (otelgrpc) 
        "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        "go.opentelemetry.io/otel/trace"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials/insecure"
)

این بار، سرویس کلاینت یک کلاینت gRPC در مقابل سرویس سرور است، بنابراین باید کلاینت gRPC را instrument کنید. تابع mustConnGRPC را پیدا کنید و interceptor های gRPC را اضافه کنید که هر بار کلاینت درخواست‌هایی به سرور ارسال می‌کند، span های جدید را instrument می‌کنند.

مرحله 0/src/client/main.go

// Helper function for gRPC connections: Dial and create client once, reuse.
func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {
        var err error
        // step2. add gRPC interceptor
        interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
        *conn, err = grpc.DialContext(ctx, addr,
                grpc.WithTransportCredentials(insecure.NewCredentials()),
                grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(interceptorOpt)),
                grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(interceptorOpt)),
                grpc.WithTimeout(time.Second*3),
        )
        // step2: end adding interceptor
        if err != nil {
                panic(fmt.Sprintf("Error %s grpc: failed to connect %s", err, addr))
        }
}

از آنجا که شما قبلاً OpenTelemetry را در بخش قبلی تنظیم کرده‌اید، نیازی به انجام آن ندارید.

ابزار دقیق از پیش ساخته شده برای سرور gRPC

مانند کاری که برای کلاینت gRPC انجام دادیم، ابزار دقیق از پیش ساخته شده را برای سرور gRPC فراخوانی می‌کنیم. بسته جدید را به بخش واردات مانند زیر اضافه کنید:

مرحله 0/src/server/main.go

import (
        "context"
        "fmt"
        "io/ioutil"
        "log"
        "net"
        "os"
        "regexp"
        "strings"

        "opentelemetry-trace-codelab-go/server/shakesapp"

        "cloud.google.com/go/storage"
        // step2. add OpenTelemetry packages including otelgrpc
        "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
        "go.opentelemetry.io/otel"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        "google.golang.org/api/iterator"
        "google.golang.org/api/option"
        "google.golang.org/grpc"
        healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

از آنجا که این اولین بار است که سرور را ابزار دقیق می‌کنید، ابتدا باید OpenTelemetry را راه‌اندازی کنید، مشابه کاری که برای loadgen و سرویس‌های کلاینت انجام دادیم.

مرحله 0/src/server/main.go

// step2. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // create a stdout exporter to show collected spans out to stdout.
        exporter, err := stdout.New(stdout.WithPrettyPrint())
        if err != nil {
                return nil, err
        }
        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

func main() {
        ...

        // step2. setup OpenTelemetry
        tp, err := initTracer()
        if err != nil {
                log.Fatalf("failed to initialize TracerProvider: %v", err)
        }
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup
        ...

و در مرحله بعد، باید interceptor های سرور را اضافه کنید. در تابع main ، مکانی را که grpc.NewServer() فراخوانی می‌شود پیدا کنید و interceptor ها را به تابع اضافه کنید.

مرحله 0/src/server/main.go

func main() {
        ...
        svc := NewServerService()
        // step2: add interceptor
        interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
        srv := grpc.NewServer(
                grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)),
                grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)),
        )
        // step2: end adding interceptor
        shakesapp.RegisterShakespeareServiceServer(srv, svc)
        ...

میکروسرویس را اجرا کنید و ردیابی را تأیید کنید

سپس کد اصلاح‌شده خود را با دستور skaffold اجرا کنید.

skaffold dev

حالا دوباره، شما مجموعه‌ای از اطلاعات span را در stdout می‌بینید.

خروجی دستور

...
[server] {
[server]        "Name": "shakesapp.ShakespeareService/GetMatchCount",
[server]        "SpanContext": {
[server]                "TraceID": "89b472f213a400cf975e0a0041649667",
[server]                "SpanID": "96030dbad0061b3f",
[server]                "TraceFlags": "01",
[server]                "TraceState": "",
[server]                "Remote": false
[server]        },
[server]        "Parent": {
[server]                "TraceID": "89b472f213a400cf975e0a0041649667",
[server]                "SpanID": "cd90cc3859b73890",
[server]                "TraceFlags": "01",
[server]                "TraceState": "",
[server]                "Remote": true
[server]        },
[server]        "SpanKind": 2,
[server]        "StartTime": "2022-07-14T14:05:55.74822525Z",
[server]        "EndTime": "2022-07-14T14:06:03.449258891Z",
[server]        "Attributes": [
...
[server]        ],
[server]        "Events": [
[server]                {
[server]                        "Name": "message",
[server]                        "Attributes": [
...
[server]                        ],
[server]                        "DroppedAttributeCount": 0,
[server]                        "Time": "2022-07-14T14:05:55.748235489Z"
[server]                },
[server]                {
[server]                        "Name": "message",
[server]                        "Attributes": [
...
[server]                        ],
[server]                        "DroppedAttributeCount": 0,
[server]                        "Time": "2022-07-14T14:06:03.449255889Z"
[server]                }
[server]        ],
[server]        "Links": null,
[server]        "Status": {
[server]                "Code": "Unset",
[server]                "Description": ""
[server]        },
[server]        "DroppedAttributes": 0,
[server]        "DroppedEvents": 0,
[server]        "DroppedLinks": 0,
[server]        "ChildSpanCount": 0,
[server]        "Resource": [
[server]                {
...
[server]        ],
[server]        "InstrumentationLibrary": {
[server]                "Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
[server]                "Version": "semver:0.33.0",
[server]                "SchemaURL": ""
[server]        }
[server] }
...

متوجه می‌شوید که هیچ نام span ای را تعبیه نکرده‌اید و span ها را به صورت دستی با trace.Start() یا span.SpanFromContext() ایجاد کرده‌اید. با این حال تعداد زیادی span دریافت می‌کنید زیرا interceptor های gRPC آنها را تولید کرده‌اند.

خلاصه

در این مرحله، شما ارتباطات مبتنی بر gRPC را با پشتیبانی کتابخانه‌های اکوسیستم OpenTelemetry تجهیز کردید.

بعدی

در مرحله بعد، شما در نهایت ردیابی را با Cloud Trace تجسم خواهید کرد و یاد خواهید گرفت که چگونه محدوده‌های جمع‌آوری‌شده را تجزیه و تحلیل کنید.

۶. با Cloud Trace ردیابی را تجسم کنید

شما با OpenTelemetry ردیابی‌ها را در کل سیستم ابزار دقیق کرده‌اید. تاکنون یاد گرفته‌اید که چگونه سرویس‌های HTTP و gRPC را ابزار دقیق کنید. اگرچه نحوه ابزار دقیق کردن آنها را یاد گرفته‌اید، اما هنوز یاد نگرفته‌اید که چگونه آنها را تجزیه و تحلیل کنید. در این بخش، صادرکنندگان stdout را با صادرکنندگان Cloud Trace جایگزین خواهید کرد و یاد خواهید گرفت که چگونه ردیابی‌های خود را تجزیه و تحلیل کنید.

از صادر کننده Cloud Trace استفاده کنید

یکی از ویژگی‌های قدرتمند OpenTelemetry قابلیت اتصال آن است. برای تجسم تمام span های جمع‌آوری‌شده توسط ابزار دقیق خود، کاری که باید انجام دهید این است که صادرکننده stdout را با صادرکننده Cloud Trace جایگزین کنید.

فایل‌های main.go هر سرویس را باز کنید و تابع initTracer() را پیدا کنید. خط مربوط به تولید یک صادرکننده stdout را حذف کنید و به جای آن یک صادرکننده Cloud Trace ایجاد کنید.

step0/src/loadgen/main.go

import (
        ...
        // step3. add OpenTelemetry for Cloud Trace package
        cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
)

// step1. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // step3. replace stdout exporter with Cloud Trace exporter
        // cloudtrace.New() finds the credentials to Cloud Trace automatically following the
        // rules defined by golang.org/x/oauth2/google.findDefaultCredentailsWithParams.
        // https://pkg.go.dev/golang.org/x/oauth2/google#FindDefaultCredentialsWithParams
        exporter, err := cloudtrace.New()
        // step3. end replacing exporter
        if err != nil {
                return nil, err
        }

        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

شما باید همین تابع را در سرویس کلاینت و سرور نیز ویرایش کنید.

میکروسرویس را اجرا کنید و ردیابی را تأیید کنید

بعد از ویرایش، طبق معمول کلاستر را با دستور skaffold اجرا کنید.

skaffold dev

حالا اطلاعات زیادی در قالب لاگ‌های ساختاریافته در stdout نمی‌بینید، زیرا exporter را با Cloud Trace جایگزین کرده‌اید.

خروجی دستور

[loadgen] 2022/07/14 15:01:07 simulated 20 requests
[loadgen] 2022/07/14 15:01:07 simulating client requests, round 37
[loadgen] 2022/07/14 15:01:14 query 'sweet': matched 958
[client] 2022/07/14 15:01:14 {"match_count":958}
[client] 2022/07/14 15:01:14 {"match_count":3040}
[loadgen] 2022/07/14 15:01:14 query 'love': matched 3040
[client] 2022/07/14 15:01:15 {"match_count":349}
[loadgen] 2022/07/14 15:01:15 query 'hello': matched 349
[client] 2022/07/14 15:01:15 {"match_count":484}
[loadgen] 2022/07/14 15:01:15 query 'faith': matched 484
[loadgen] 2022/07/14 15:01:15 query 'insolence': matched 14
[client] 2022/07/14 15:01:15 {"match_count":14}
[client] 2022/07/14 15:01:21 {"match_count":484}
[loadgen] 2022/07/14 15:01:21 query 'faith': matched 484
[client] 2022/07/14 15:01:21 {"match_count":728}
[loadgen] 2022/07/14 15:01:21 query 'world': matched 728
[client] 2022/07/14 15:01:22 {"match_count":484}
[loadgen] 2022/07/14 15:01:22 query 'faith': matched 484
[loadgen] 2022/07/14 15:01:22 query 'hello': matched 349
[client] 2022/07/14 15:01:22 {"match_count":349}
[client] 2022/07/14 15:01:23 {"match_count":1036}
[loadgen] 2022/07/14 15:01:23 query 'friend': matched 1036
[loadgen] 2022/07/14 15:01:28 query 'tear': matched 463
...

حالا بیایید بررسی کنیم که آیا همه spanها به درستی به Cloud Trace ارسال شده‌اند یا خیر. به کنسول Cloud دسترسی پیدا کنید و به "لیست ردیابی" بروید. دسترسی به آن از طریق کادر جستجو آسان است. در غیر این صورت، می‌توانید روی منوی سمت چپ کلیک کنید. 8b3f8411bd737e06.png

سپس می‌بینید که تعداد زیادی نقطه آبی در سراسر نمودار تأخیر توزیع شده‌اند. هر نقطه نشان دهنده یک رد واحد است.

3ecf131423fc4c40.png

روی یکی از آنها کلیک کنید تا جزئیات داخل ردپا را ببینید. 4fd10960c6648a03.png

حتی با این نگاه سریع و ساده، شما از قبل نکات زیادی را می‌دانید. برای مثال، از نمودار آبشاری ، می‌توانید ببینید که علت تأخیر عمدتاً به دلیل محدوده‌ای به نام shakesapp.ShakespeareService/GetMatchCount است. (به شماره ۱ در تصویر بالا مراجعه کنید) می‌توانید این موضوع را از جدول خلاصه تأیید کنید. (سمت راست، مدت زمان هر محدوده را نشان می‌دهد.) همچنین، این ردیابی برای پرس‌وجوی "friend" بود. (به شماره ۲ در تصویر بالا مراجعه کنید)

با توجه به این تحلیل‌های کوتاه، ممکن است متوجه شده باشید که باید محدوده‌های جزئی‌تری را در متد GetMatchCount بدانید. در مقایسه با اطلاعات stdout، تجسم‌سازی قدرتمند است. برای کسب اطلاعات بیشتر در مورد جزئیات Cloud Trace، لطفاً به مستندات رسمی ما مراجعه کنید.

خلاصه

در این مرحله، شما صادرکننده stdout را با Cloud Trace جایگزین کردید و ردپاها را در Cloud Trace به صورت بصری نمایش دادید. همچنین یاد گرفتید که چگونه شروع به تجزیه و تحلیل ردپاها کنید.

بعدی

در مرحله بعد، کد منبع سرویس سرور را برای اضافه کردن یک sub span در GetMatchCount تغییر خواهید داد.

۷. برای تحلیل بهتر، زیربخش (sub span) اضافه کنید

در مرحله قبل، متوجه شدید که علت زمان رفت و برگشت مشاهده شده از loadgen، عمدتاً فرآیند درون متد GetMatchCount، یعنی هندلر gRPC، در سرویس سرور است. با این حال، از آنجا که ما چیزی غیر از هندلر را ابزارسازی نکرده‌ایم، نمی‌توانیم بینش بیشتری از نمودار آبشاری پیدا کنیم. این یک مورد رایج است که ما شروع به ابزارسازی میکروسرویس‌ها می‌کنیم.

3b63a1e471dddb8c.png

در این بخش، ما قصد داریم یک زیربخش را که در آن سرور، Google Cloud Storage را فراخوانی می‌کند، بررسی کنیم، زیرا معمولاً برخی از ورودی/خروجی‌های شبکه خارجی مدت زمان زیادی طول می‌کشد و شناسایی اینکه آیا این فراخوانی علت است یا خیر، مهم است.

ابزار دقیق یک زیرشاخه در سرور

main.go در سرور باز کنید و تابع readFiles را پیدا کنید. این تابع درخواستی را به فضای ذخیره‌سازی ابری گوگل برای دریافت تمام فایل‌های متنی آثار شکسپیر فراخوانی می‌کند. در این تابع، می‌توانید یک sub span ایجاد کنید، مانند کاری که برای ابزار دقیق سرور HTTP در سرویس کلاینت انجام دادید.

مرحله 0/src/server/main.go

func readFiles(ctx context.Context, bucketName, prefix string) ([]string, error) {
        type resp struct {
                s   string
                err error
        }

        // step4: add an extra span
        span := trace.SpanFromContext(ctx)
        span.SetName("server.readFiles")
        span.SetAttributes(attribute.Key("bucketname").String(bucketName))
        defer span.End()
        // step4: end add span
        ...

و این تمام چیزی بود که برای اضافه کردن یک span جدید نیاز داشتیم. بیایید با اجرای برنامه ببینیم که چطور پیش می‌رود.

میکروسرویس را اجرا کنید و ردیابی را تأیید کنید

بعد از ویرایش، طبق معمول کلاستر را با دستور skaffold اجرا کنید.

skaffold dev

و یک ردیابی به نام query.request از لیست ردیابی انتخاب کنید. نمودار آبشاری ردیابی مشابهی را مشاهده خواهید کرد، به جز یک span جدید در زیر shakesapp.ShakespeareService/GetMatchCount . (span که در زیر با مستطیل قرمز محصور شده است)

3d4a891aa30d7a32.png

چیزی که اکنون می‌توانید از این نمودار بفهمید این است که فراخوانی خارجی به فضای ذخیره‌سازی ابری گوگل (Google Cloud Storage) مقدار زیادی تأخیر را اشغال می‌کند، اما هنوز چیزهای دیگری هستند که بخش عمده‌ای از تأخیر را ایجاد می‌کنند.

همین الان با چند نگاه به نمودار آبشاری ردیابی، بینش‌های زیادی به دست آوردید. چگونه جزئیات عملکرد بیشتر را در برنامه خود به دست می‌آورید؟ در اینجا پروفایلر وارد می‌شود، اما فعلاً، بیایید این آزمایشگاه کد را به پایان برسانیم و تمام آموزش‌های پروفایلر را به بخش دوم واگذار کنیم.

خلاصه

در این مرحله، شما یک span دیگر را در سرویس سرور ابزار دقیق کردید و بینش‌های بیشتری در مورد تأخیر سیستم به دست آوردید.

۸. تبریک

شما با موفقیت ردیابی‌های توزیع‌شده را با OpenTelemery ایجاد کردید و تأخیر درخواست‌ها را در سراسر میکروسرویس در Google Cloud Trace تأیید کردید.

برای تمرین‌های طولانی‌تر، می‌توانید موضوعات زیر را خودتان امتحان کنید.

  • پیاده‌سازی فعلی تمام span های تولید شده توسط بررسی سلامت را ارسال می‌کند. ( grpc.health.v1.Health/Check ) چگونه می‌توان آن span ها را از Cloud Traces فیلتر کرد؟ راهنمایی اینجا است.
  • گزارش‌های رویداد را با spanها مرتبط کنید و ببینید که چگونه در Google Cloud Trace و Google Cloud Logging کار می‌کند. راهنمایی اینجا است.
  • یک سرویس را با سرویسی به زبان دیگر جایگزین کنید و سعی کنید آن را با OpenTelemetry برای آن زبان تجهیز کنید.

همچنین، اگر مایلید پس از این درباره پروفایلر اطلاعات بیشتری کسب کنید، لطفاً به بخش ۲ بروید. در این صورت می‌توانید از بخش پاکسازی زیر صرف نظر کنید.

تمیز کردن

پس از این آزمایش کد، لطفاً خوشه Kubernetes را متوقف کنید و حتماً پروژه را حذف کنید تا هزینه‌های غیرمنتظره‌ای روی Google Kubernetes Engine، Google Cloud Trace و Google Artifact Registry دریافت نکنید.

ابتدا، کلاستر را حذف کنید. اگر کلاستر را با skaffold dev اجرا می‌کنید، فقط باید Ctrl-C را فشار دهید. اگر کلاستر را با skaffold run اجرا می‌کنید، دستور زیر را اجرا کنید:

skaffold delete

خروجی دستور

Cleaning up...
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

پس از حذف کلاستر، از قسمت منو، گزینه "IAM & Admin" > "Settings" را انتخاب کرده و سپس روی دکمه "SHUT DOWN" کلیک کنید.

45aa37b7d5e1ddd1.png

سپس شناسه پروژه (نه نام پروژه) را در فرم موجود در کادر محاوره‌ای وارد کنید و خاموش شدن را تأیید کنید.