כלי לשיפור הביצועים באפליקציה ב-Go (חלק 1: מעקב)

1. מבוא

505827108874614d.png

תאריך עדכון אחרון: 15 ביולי 2022

יכולת התצפית באפליקציה

יכולת תצפית ו-OpenTelemetry

המונח 'יכולת תצפית' משמש לתיאור מאפיין של מערכת. מערכת עם ניראות מאפשרת לצוותים לנפות באקטיביות באגים במערכת. בהקשר הזה, שלושת העמודים של יכולת התצפית – יומנים, מדדים ומעקב – הם הכלים הבסיסיים שבעזרתם המערכת יכולה לקבל יכולת תצפית.

OpenTelemetry היא קבוצה של מפרטים, ספריות וסוכנויות שמאיצות את המדידה ואת הייצוא של נתוני הטלמטריה (יומנים, מדדים ומעקב) הנדרשים לצורך יכולת התצפית. OpenTelemetry הוא פרויקט קהילתי ב-CNCF שמבוסס על סטנדרט פתוח. בעזרת הספריות שהפרויקט והסביבה העסקית שלו מספקים, המפתחים יכולים לבדוק את האפליקציות שלהם באופן ניטרלי לספקים ובמספר ארכיטקטורות.

בנוסף לשלושת העמודים של יכולת התצפית, יצירת פרופילים רציפה היא רכיב מפתח נוסף ליכולת התצפית, והיא מרחיבת את בסיס המשתמשים בתעשייה. Cloud Profiler הוא אחד מהכלים הראשונים שאפשר להשתמש בהם לצורך הניתוח, והוא מספק ממשק קל לניתוח מעמיק של מדדי הביצועים בסטאקים של קריאות לאפליקציות.

סדנת הקוד הזו היא חלק 1 בסדרה, והיא עוסקת בשימוש ב-OpenTelemetry וב-Cloud Trace לצורך הטמעת כלי למעקב אחר אירועים בענן במיקרו-שירותים. בחלק 2 נסביר על יצירת פרופילים רציפה באמצעות Cloud Profiler.

Distributed Trace

בין היומנים, המדדים והמעקבים, מעקב הוא טלמטריה שמציינת את זמן האחזור של חלק ספציפי בתהליך במערכת. במיוחד בעידן של מיקרו-שירותים, מעקב מבוזבז הוא הגורם העיקרי לזיהוי צווארי בקבוק של זמן אחזור במערכת הכוללת והמבוזרת.

כשמנתחים נתוני מעקב מבוזרים, התצוגה החזותית של נתוני המעקב היא המפתח להבנה מיידית של זמני האחזור הכוללים של המערכת. ב-distributed trace, אנחנו מטפלים בקבוצת קריאות לעיבוד בקשה אחת לנקודת הכניסה למערכת, בצורה של Trace שמכיל כמה Spans.

Span מייצג יחידת עבודה בודדת שבוצעה במערכת מבוזרת, ומתעדות את זמני ההתחלה והסיום שלה. לעתים קרובות יש בין span'ים יחסים היררכיים – בתמונה שבהמשך, כל ה-span'ים הקטנים יותר הם span'ים צאצאים של span גדול מסוג /messages, והם מורכבים ל-Trace אחד שמציג את נתיב העבודה במערכת.

מעקב

Google Cloud Trace הוא אחת מהאפשרויות לקצה העורפי של מעקב מבוזבז, והוא משולב היטב עם מוצרים אחרים ב-Google Cloud.

מה תפַתחו

בקודלאב הזה תלמדו איך להטמיע מידע על נתוני מעקב בשירותים שנקראים 'אפליקציית שייקספיר' (נקראת גם Shakesapp) שפועלים באשכול של Google Kubernetes Engine. הארכיטקטורה של Shakesapp מתוארת בהמשך:

44e243182ced442f.png

  • Loadgen שולח למחרוזת שאילתה ללקוח ב-HTTP
  • לקוחות מעבירים את השאילתה מ-loadgen לשרת ב-gRPC
  • השרת מקבל את השאילתה מהלקוח, מאחזר את כל כתבי שייקספיר בפורמט טקסט מ-Google Cloud Storage, מחפש את השורות שמכילות את השאילתה ומחזיר ללקוח את מספר השורה שתואמת לשאילתה

תצטרכו להטמיע את פרטי המעקב בבקשה. לאחר מכן, תטמיעו סוכן לניתוחי פרופיל בשרת ותבדקו את צוואר הבקבוק.

מה תלמדו

  • איך מתחילים לעבוד עם ספריות OpenTelemetry Trace בפרויקט Go
  • איך יוצרים span באמצעות הספרייה
  • איך להעביר הקשרים של span דרך החיבור בין רכיבי האפליקציה
  • איך שולחים נתוני מעקב ל-Cloud Trace
  • איך מנתחים את המעקב ב-Cloud Trace

בקודלאב הזה מוסבר איך להטמיע את המיקרו-שירותים. כדי להקל על ההבנה, הדוגמה הזו מכילה רק 3 רכיבים (גנרטור עומסים, לקוח ושרת), אבל אפשר להחיל את אותו תהליך שמתואר ב-codelab הזה על מערכות מורכבות וגדולות יותר.

מה צריך להכין

  • ידע בסיסי ב-Go
  • ידע בסיסי ב-Kubernetes

2. הגדרה ודרישות

הגדרת סביבה בקצב אישי

אם עדיין אין לכם חשבון Google‏ (Gmail או Google Apps), עליכם ליצור חשבון. נכנסים למסוף Google Cloud Platform‏ ( 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 Platform זכאים לתקופת ניסיון בחינם בשווי 300$‎, כך שהקודלאב הזה אמור להיות ללא תשלום.

הגדרת Google Cloud Shell

אפשר להפעיל את Google Cloud ו-Google Cloud Trace מרחוק מהמחשב הנייד, אבל בסדנת הקוד הזו נשתמש ב-Google Cloud Shell, סביבת שורת פקודה שפועלת ב-Cloud.

המכונה הווירטואלית הזו מבוססת על Debian, וטעונים בה כל הכלים הדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, משפרת מאוד את הביצועים והאימות של הרשת. כל מה שצריך לקודלאב הזה הוא דפדפן (כן, הוא פועל ב-Chromebook).

כדי להפעיל את Cloud Shell במסוף Cloud, פשוט לוחצים על Activate Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (ההקצאה והחיבור לסביבה אמורים להימשך רק כמה רגעים).

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

Screen Shot 2017-06-14 at 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:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-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

הגדרת אשכולות Google Kubernetes

בקודלאב הזה תלמדו להריץ אשכול של מיקרו-שירותים ב-Google Kubernetes Engine ‏ (GKE). התהליך ב-codelab הזה הוא:

  1. הורדת הפרויקט הבסיסי ל-Cloud Shell
  2. פיתוח מיקרו-שירותים בקונטיינרים
  3. העלאת קונטיינרים ל-Google Artifact Registry‏ (GAR)
  4. פריסת קונטיינרים ב-GKE
  5. שינוי קוד המקור של שירותים לצורך הטמעת כלי למעקב אחר נתונים
  6. מעבר לשלב 2

הפעלת Kubernetes Engine

קודם כול, מגדירים אשכול 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

הגדרת Artifact Registry ו-skaffold

עכשיו יש לנו אשכול Kubernetes מוכן לפריסה. בשלב הבא נתכונן לשימוש ב-Container Registry כדי לדחוף ולפרוס קונטיינרים. כדי לבצע את השלבים האלה, צריך להגדיר Artifact Registry ‏ (GAR) ו-skaffold כדי להשתמש בהם.

הגדרת Artifact Registry

עוברים לתפריט של Artifact Registry ומקישים על הלחצן ENABLE.

45e384b87f7cf0db.png

אחרי כמה רגעים יוצג דפדפן המאגר של GAR. לוחצים על הלחצן 'CREATE REPOSITORY' ומזינים את שם המאגר.

d6a70f4cb4ebcbe3.png

בקודלאב הזה, נתתי למאגר החדש את השם trace-codelab. הפורמט של הארטיפקט הוא 'Docker' וסוג המיקום הוא 'אזור'. בוחרים את האזור הקרוב לאזור שהגדרתם כתחום ברירת המחדל של Google Compute Engine. לדוגמה, בדוגמה הזו בחרנו ב-'us-central1-f' למעלה, לכן כאן בוחרים ב-'us-central1 (Iowa)'. לאחר מכן לוחצים על הלחצן 'יצירה'.

9c2d1ce65258ef70.png

עכשיו השם trace-codelab יופיע בדפדפן המאגר.

7a3c1f47346bea15.png

נגיע לכאן מאוחר יותר כדי לבדוק את נתיב הרישום.

הגדרה של Skaffold

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"

חוזרים ל-Cloud Shell. מריצים את הפקודה 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

בנוסף, צריך להגדיר את המרשם להגדרות של Docker. מריצים את הפקודה הבאה:

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:

  • הגדרת Cloud Shell
  • יצרתם מאגר Artifact Registry למאגר הקונטיינרים
  • הגדרת skaffold לשימוש במאגר הקונטיינרים
  • יצירת אשכול Kubernetes שבו פועלים המיקרו-שירותים של הקודלמאב

השלב הבא

בשלב הבא נתאר איך יוצרים, מעבירים ומפרסים את המיקרו-שירותים באשכול

3. פיתוח, דחיפה ופריסה של המיקרו-שירותים

הורדת החומר של ה-Codelab

בשלב הקודם הגדרנו את כל הדרישות המוקדמות ל-codelab הזה. עכשיו אתם מוכנים להריץ מיקרו-שירותים שלמים על גביהם. חומרי הקודלמעבדה מתארחים ב-GitHub, לכן צריך להוריד אותם לסביבת Cloud Shell באמצעות פקודת git הבאה.

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
  • manifests: קובצי מניפסט של Kubernetes
  • proto: הגדרת proto לתקשורת בין הלקוח לשרת
  • src: ספריות לקוד המקור של כל השירותים
  • skaffold.yaml: קובץ תצורה של skaffold

בשיעור ה-Codelab הזה תעדכנו את קוד המקור שנמצא בתיקייה step0. אפשר גם לעיין בקוד המקור בתיקיות step[1-6] כדי למצוא את התשובות לשלבים הבאים. (חלק 1 מכסה את שלבים 0 עד 4, וחלק 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 כדי להטמיע את פרטי המעקב.

4. אינסטרומנטציה ל-HTTP

המושג של כלי למעקב אחר נתונים והעברה שלהם

לפני שנערוך את קוד המקור, אסביר בקצרה איך פועלים עקבות מבוזרים באמצעות תרשים פשוט.

6be42e353b9bfd1d.png

בדוגמה הזו, אנחנו משתמשים בקוד כדי לייצא את המידע של Trace ו-Span ל-Cloud Trace ולהפיץ את ההקשר של המעקב בבקשה משירות loadgen לשירות השרת.

כדי ש-Cloud Trace יוכל לאסוף את כל ה-spans שיש להם את אותו מזהה Trace ולקבץ אותם למעקב אחד, האפליקציות צריכות לשלוח מטא-נתונים של מעקב, כמו מזהה מעקב ומזהה span. בנוסף, האפליקציה צריכה להפיץ את הקשרי המעקב (השילוב של מזהה המעקב ומזהה ה-span של ה-span ההורה) בשירותים מצד הלקוח שמבקשים את המעקב, כדי שהם יוכלו לדעת באיזה הקשר מעקב הם מטפלים.

OpenTelemetry עוזר לכם:

  • כדי ליצור מזהה Trace ID ומזהה Span ID ייחודיים
  • כדי לייצא את מזהה המעקב ואת מזהה ה-span לקצה העורפי
  • להעברת הקשרי המעקב לשירותים אחרים
  • להטמיע מטא-נתונים נוספים שיעזרו לנתח את העקבות

רכיבים ב-OpenTelemetry Trace

b01f7bb90188db0d.png

התהליך להטמעת מעקב אחר אפליקציות באמצעות OpenTelemetry הוא:

  1. יצירת גורם ייצוא
  2. יוצרים TracerProvider שמקשר את ה-exporter בשלב 1 ומגדירים אותו כ-global.
  3. הגדרת TextMapPropagaror כדי להגדיר את שיטת ההפצה
  4. אחזור של Tracer מ-TracerProvider
  5. יצירת Span מה-Tracer

בשלב הזה, אין צורך להבין את המאפיינים המפורטים בכל רכיב, אבל חשוב לזכור את הדברים הבאים:

  • הכלי לייצוא כאן ניתן לחיבור ל-TracerProvider
  • ב-TracerProvider נשמרות כל ההגדרות לגבי דגימת נתוני המעקב וייצוא שלהם
  • כל העקבות נארזים באובייקט Tracer

אחרי שהבנו את זה, נעבור לעבודה בפועל על הקוד.

הקטע הראשון של הכלי

שירות יצירת עומסי כלי

כדי לפתוח את Cloud Shell Editor, לוחצים על הלחצן 776a11bfb2122549.png בפינה הימנית העליונה של Cloud Shell. פותחים את step0/src/loadgen/main.go מהחלונית השמאלית של ה-Explorer ומאתרים את הפונקציה הראשית.

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. בהטמעה הנוכחית, הקטע מכיל 2 שורות ביומן שמתעדות את ההתחלה והסיום של קריאת הפונקציה. עכשיו נוסיף למידע של 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 בפורמט מובנה.

לאחר מכן, קוראים לה מהפונקציה הראשית. קוראים ל-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 ומזהה Span ייחודיים. 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 כדי ליצור את ה-span המותאם אישית באמצעות OpenTelemetry ואת ה-span שנוצר באופן אוטומטי מלקוח ה-HTTP המותאם אישית. עליכם לבצע את הפעולות הבאות:

  1. איך מקבלים דיווח על אירוע ב-TracerProvider ברמת האתר באמצעות otel.Tracer()
  2. יצירת span ברמה הבסיסית באמצעות השיטה 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

שירות לקוחות של כלי

בסעיף הקודם, הטמענו מכשירי מדידה בחלק שמוקף במלבן האדום בתרשים שבהמשך. הטמענו את פרטי ה-span בשירות ה-load generator. בדומה לשירות ליצירת עומסים, עכשיו צריך להטמיע את השירות של הלקוח. ההבדל ביחס לשירות ה-load generator הוא ששירות הלקוח צריך לחלץ את פרטי מזהה המעקב שהועברו משירות ה-load generator בכותרת ה-HTTP ולהשתמש במזהה כדי ליצור Spans.

bcaccd06691269f8.png

פותחים את Cloud Shell Editor ומוסיפים את החבילות הנדרשות, כמו שעשינו לשירות ה-load generator.

step0/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 של שירות הלקוח.

step0/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
}

עכשיו הגיע הזמן להוסיף רכיבי מעקב ל-spans. מכיוון ששירות הלקוח צריך לקבל בקשות HTTP משירות loadgen, צריך להטמיע את הכלי למעקב ב-handler. שרת ה-HTTP בשירות הלקוח מיושם באמצעות net/http, וניתן להשתמש בחבילה otelhttp כמו שעשינו ב-loadgen.

קודם כול, מחליפים את רישום ה-handler ב-otelhttp Handler. בפונקציה main, מחפשים את השורות שבהן הטיפול ב-HTTP רשום ב-http.HandleFunc().

step0/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 בפועל בתוך הטיפול. מחפשים את func (*clientService) handler(), ומוסיפים את הכלי למדידת זמן ביקורת (span) באמצעות trace.SpanFromContext().

step0/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
        ...

בעזרת המדידה הזו, מקבלים את הקטעים מתחילת השיטה handler ועד לסופה. כדי שיהיה קל לנתח את הקטעים, מוסיפים לשאילתה מאפיין נוסף ששומר את מספר ההתאמות. מוסיפים את הקוד הבא ממש לפני שורת היומן.

step0/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))
        ...

אחרי שמוסיפים את כל האמצעים למדידת ביצועים שמפורטים למעלה, השלמת את המדידה של ה-trace בין 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 פולט את ההודעות האלה. שימו לב שההורים של כל ה-spans של loadgen הם TraceID: 00000000000000000000000000000000, כי זהו ה-span ברמה הבסיסית, כלומר ה-span הראשון במעקב. בנוסף, תוכלו לראות שמאפיין ההטמעה "query" מכיל את מחרוזת השאילתה שמועברת לשירות הלקוח.

סיכום

בשלב הזה, הטמעתם את השירות ליצירת עומסים ואת שירות הלקוח שמתקשרים ב-HTTP, ואימתתם שאפשר להפיץ את ה-Trace Context בין השירותים ולייצא את פרטי ה-Span משני השירותים ל-stdout.

השלב הבא

בשלב הבא תוסיפו מכשירי מדידה לשירות הלקוח ולשירות השרת כדי לוודא איך להפיץ את ה-Trace Context דרך gRPC.

5. אינסטרומנטציה ל-gRPC

בשלב הקודם, הטמענו את המחצית הראשונה של הבקשה במיקרו-שירותים האלה. בשלב הזה אנחנו מנסים להשתמש בכלי למדידת התקשורת של gRPC בין שירות הלקוח לשירות השרת. (המלבן הירוק והסגול בתמונה שבהמשך)

75310d8e0e3b1a30.png

הטמעת מכשירי מדידה לפני ה-build ללקוח gRPC

הסביבה העסקית של OpenTelemetry כוללת הרבה ספריות שימושיות שעוזרות למפתחים להתקין כלי למדידת ביצועים באפליקציות. בשלב הקודם השתמשנו במדידה מראש ל-build של החבילה net/http. בשלב הזה, אנחנו מנסים להפיץ את Trace Context דרך gRPC, ולכן אנחנו משתמשים בספרייה.

קודם כול, מייבאים את חבילת ה-gRPC שנוצרה מראש שנקראת otelgrpc.

step0/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. מחפשים את הפונקציה mustConnGRPC ומוסיפים מנטרים של gRPC שמטמיעים קטעי קוד חדשים בכל פעם שהלקוח שולח בקשות לשרת.

step0/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. מוסיפים חבילה חדשה לקטע הייבוא, למשל:

step0/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 ולשירותי הלקוח.

step0/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
        ...

בשלב הבא, צריך להוסיף מנטרים של שרתים. בפונקציה main, מחפשים את המקום שבו grpc.NewServer() נקראת ומוסיפים לה נתוני מעקב.

step0/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] }
...

שמתם לב שלא הטמעתם שמות של קטעי טקסט, ושיצרתם קטעי טקסט באופן ידני באמצעות trace.Start() או span.SpanFromContext(). עם זאת, עדיין מקבלים מספר גדול של קטעי קוד (spans) כי מנטעים של gRPC יצרו אותם.

סיכום

בשלב הזה, צירפתם לקוד רכיבים למדידת תקשורת מבוססת-gRPC עם תמיכה מספריות הסביבה של OpenTelemetry.

השלב הבא

בשלב הבא נציג את המעקב באופן חזותי באמצעות Cloud Trace, ונלמד איך לנתח את ה-spans שנאספו.

6. הצגה חזותית של נתוני המעקב באמצעות Cloud Trace

הוספת מכשירי מעקב במערכת כולה באמצעות OpenTelemetry. עד עכשיו למדתם איך להטמיע שירותי HTTP ו-gRPC. למדתם איך להטמיע את הנתונים, אבל עדיין לא למדתם איך לנתח אותם. בקטע הזה נלמד איך מחליפים את היצוא של stdout ביצוא של Cloud Trace, ואיך לנתח את הטרייסים.

שימוש בייצוא של Cloud Trace

אחד המאפיינים החזקים של OpenTelemetry הוא היכולת להוסיף רכיבים. כדי להציג באופן חזותי את כל ה-spans שנאספו על ידי הכלי למדידת ביצועים, צריך פשוט להחליף את ה-exporter של stdout ב-exporter של 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

עכשיו לא מוצגים הרבה פרטי span בפורמט של יומנים מובְנים ב-stdout, כי החלפתם את הכלי לייצוא ב-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
...

עכשיו נבדוק אם כל ה-spans נשלחים כראוי ל-Cloud Trace. נכנסים למסוף Cloud ועוברים אל Trace list. קל לגשת אליו מתיבת החיפוש. לחלופין, אפשר ללחוץ על התפריט בחלונית הימנית. 8b3f8411bd737e06.png

לאחר מכן יופיעו הרבה נקודות כחולות בתרשים זמן האחזור. כל נקודה מייצגת נתיב אחד.

3ecf131423fc4c40.png

לוחצים על אחת מהן כדי לראות את הפרטים בתוך המעקב. 4fd10960c6648a03.png

כבר מהסקירה המהירה והפשוטה הזו, אתם יכולים לקבל הרבה תובנות. לדוגמה, בתרשים המפל אפשר לראות שהסיבה לזמן האחזור היא בעיקר ה-span בשם shakesapp.ShakespeareService/GetMatchCount. (ראו 1 בתמונה שלמעלה). אפשר לאשר זאת בטבלת הסיכום. (העמודה השמאלית ביותר מציגה את משך הזמן של כל טווח). בנוסף, המעקב הזה היה לשאילתה 'friend'. (ראו 2 בתמונה שלמעלה)

אחרי הניתוח הקצר הזה, יכול להיות שתבחינו שאתם צריכים לדעת על מרחבים מפורטים יותר בתוך השיטה GetMatchCount. בהשוואה למידע ב-stdout, התצוגה החזותית חזקה יותר. מידע נוסף על פרטי Cloud Trace זמין במסמכי התיעוד הרשמיים שלנו.

סיכום

בשלב הזה החלפתם את ה-exporter של stdout ב-Cloud Trace, והצגתם גרפיות של נתוני המעקב ב-Cloud Trace. בנוסף, למדתם איך להתחיל לנתח את הנתונים.

השלב הבא

בשלב הבא, תשנו את קוד המקור של שירות השרת כדי להוסיף span משני ב-GetMatchCount.

7. הוספת span משני לניתוח מדויק יותר

בשלב הקודם, גיליתם שהסיבה לזמן הנסיעה הלוך ושוב שנצפה ב-loadgen היא בעיקר התהליך בתוך השיטה GetMatchCount, הטיפול ב-gRPC, בשירות השרת. עם זאת, מכיוון שלא הטמענו כלים למעקב מלבד הטיפול, לא נוכל למצוא תובנות נוספות מהתרשים מסוג Waterfall. זהו מקרה נפוץ כשמתחילים להטמיע מיקרו-שירותים.

3b63a1e471dddb8c.png

בקטע הזה נשתמש ב-span משני שבו השרת קורא ל-Google Cloud Storage, כי לפעמים תהליך הקלט/פלט של רשת חיצונית נמשך זמן רב, וחשוב לזהות אם הקריאה היא הסיבה לכך.

הוספת מכשיר למדידה של span משני בשרת

פותחים את main.go בשרת ומאתרים את הפונקציה readFiles. הפונקציה הזו שולחת בקשה ל-Google Cloud Storage כדי לאחזר את כל קובצי הטקסט של כתבי שייקספיר. בפונקציה הזו אפשר ליצור span משני, כמו שעשיתם לצורך המדידה של שרת ה-HTTP בשירות הלקוח.

step0/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 מרשימת המעקבים. יוצג תרשים waterfall דומה של המעקב, מלבד span חדש בקטע shakesapp.ShakespeareService/GetMatchCount. (הקטע שמוקף במלבן האדום בהמשך)

3d4a891aa30d7a32.png

מהתרשים הזה אפשר לראות שהקריאה החיצונית ל-Google Cloud Storage תופסת חלק גדול מהזמן האחזור, אבל עדיין יש גורמים אחרים שתורמים לרוב זמן האחזור.

כבר צברתם הרבה תובנות רק מתוך כמה בדיקות בתרשים המפל של המעקב. איך מקבלים את פרטי הביצועים הנוספים באפליקציה? כאן נכנס לתמונה הכלי למעקב ביצועים, אבל בינתיים נציין שזהו סוף הקודלאב הזה ונשאיר את כל המדריכים בנושא כלי למעקב ביצועים לחלק 2.

סיכום

בשלב הזה, הוספת span נוסף לשירות השרת וקיבלתם תובנות נוספות לגבי זמן האחזור של המערכת.

8. מזל טוב

יצרתם ב-OpenTelemery מעקבים מבוזרים ואימתתם את זמני האחזור של הבקשות במיקרו-השירות ב-Google Cloud Trace.

כדי לתרגל את הנושאים האלה לעומק, תוכלו לנסות את הנושאים הבאים בעצמכם.

  • ההטמעה הנוכחית שולחת את כל ה-spans שנוצרו על ידי בדיקת בריאות. (grpc.health.v1.Health/Check) איך מסננים את ה-spans האלה מ-Cloud Traces? הרמז מופיע כאן.
  • יצירת מתאם בין יומני אירועים לבין קטעים (spans) וצפייה באופן הפעולה ב-Google Cloud Trace וב-Google Cloud Logging. הרמז מופיע כאן.
  • מחליפים שירות כלשהו בשירות בשפה אחרת ומנסים להטמיע אותו באמצעות OpenTelemetry בשפה הזו.

בנוסף, אם אתם רוצים לקבל מידע נוסף על הכלי לניתוח פרופיל, תוכלו לעבור לחלק 2. במקרה כזה, אפשר לדלג על הקטע 'ניקוי' שבהמשך.

ניקוי

בסיום הקודלאב, חשוב להפסיק את האשכול של 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 ואדמין' > 'הגדרות' בחלונית התפריט, ואז לוחצים על הלחצן 'השבתה'.

45aa37b7d5e1ddd1.png

לאחר מכן מזינים את מזהה הפרויקט (ולא את שם הפרויקט) בטופס בתיבת הדו-שיח ומאשרים את ההשבתה.