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

1. מבוא

505827108874614d.png

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

ניראות (observability) של האפליקציה

ניראות (observability) ו-OpenTelemetry

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

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

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

ה-Codelab הזה הוא חלק ראשון בסדרה והוא עוסק ביצירת מעקבים מבוזרים במיקרו-שירותים (microservices) באמצעות OpenTelemetry ו-Cloud Trace. חלק 2 יעסוק ביצירת פרופילים רציפים באמצעות Cloud Profiler.

מעקב שהופץ

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

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

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

עקבות

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

מה תפַתחו

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

44e243182ced442f.png

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

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

מה תלמדו

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

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

מה צריך להכין

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

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

הגדרת סביבה בקצב עצמאי

אם אין לכם עדיין חשבון Google (Gmail או Google Apps), עליכם ליצור חשבון. נכנסים למסוף Google Cloud Platform ( console.cloud.google.com) ויוצרים פרויקט חדש.

אם כבר יש לכם פרויקט, לוחצים על התפריט הנפתח לבחירת פרויקט בפינה השמאלית העליונה של המסוף:

7a32e5469db69e9.png

ולוחצים על 'New project' (פרויקט חדש). בתיבת הדו-שיח שמתקבלת כדי ליצור פרויקט חדש:

7136b3ee36ebaf89.png

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

870a3cbd6541ee86.png

בתיבת הדו-שיח הבאה ליצירת פרויקט תוכלו להזין את פרטי הפרויקט החדש:

affdc444517ba805.png

חשוב לזכור את מזהה הפרויקט, שהוא שם ייחודי בכל הפרויקטים ב-Google Cloud (השם שלמעלה כבר תפוס ולא מתאים לכם, סליחה). בהמשך ב-Codelab הזה, המערכת תתייחס אליה בתור PROJECT_ID.

בשלב הבא, אם עדיין לא עשית זאת, יהיה עליך להפעיל חיוב ב-Developers Console כדי להשתמש במשאבים של Google Cloud ולהפעיל את Cloud Trace API.

15d0ef27a8fbab27.png

ההרצה של Codelab הזה לא אמורה לעלות לך יותר מכמה דולרים, אבל זה יכול להיות גבוה יותר אם תחליטו להשתמש ביותר משאבים או אם תשאירו אותם פועלים (עיינו בקטע 'ניקוי' בסוף המסמך). המחירים של Google Cloud Trace, Google Kubernetes Engine ו-Google Artifact Registry מפורטים במסמכי התיעוד הרשמיים.

משתמשים חדשים ב-Google Cloud Platform זכאים לתקופת ניסיון בחינם בשווי 300$, שמאפשרת ל-Codelab הזה בחינם לגמרי.

הגדרת Google Cloud Shell

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

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

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

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

צילום מסך מתאריך 2017-06-14 בשעה 22:13.43.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

אפשר לבחור מגוון אזורים שונים. מידע נוסף זמין במאמר אזורים ו אזורים.

מעבר להגדרת שפה

ב-Codelab הזה, אנחנו משתמשים ב-Go לכל קודי המקור. מריצים את הפקודה הבאה ב-Cloud Shell ומוודאים שהגרסה של Go היא 1.17 ואילך.

go version

פלט הפקודה

go version go1.18.3 linux/amd64

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

ב-Codelab הזה, תריצו אשכול של מיקרו-שירותים (microservices) ב-Google Kubernetes Engine (GKE). התהליך של Codelab הזה הוא כך:

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

הפעלת Kubernetes Engine

בשלב הראשון אנחנו מגדירים אשכול Kubernetes שבו Shakesapp פועל ב-GKE, ולכן עלינו להפעיל את GKE. עוברים לתפריט 'Kubernetes Engine'. ולוחצים על לחצן ההפעלה.

548cfd95bc6d344d.png

עכשיו אתם מוכנים ליצור אשכול Kubernetes.

יצירת אשכול Kubernetes

ב-Cloud Shell, מריצים את הפקודה הבאה כדי ליצור אשכול Kubernetes. צריך לוודא שערך התחום (zone) נמצא מתחת לאזור שבו תשתמשו ליצירת מאגר 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 מוכן לפריסה. בשלב הבא אנחנו מכינים רישום קונטיינרים לצורך דחיפה ופריסה של קונטיינרים. כדי לבצע את השלבים האלה, צריך להגדיר Artifact Registry (GAR) ו-skaffold כדי להשתמש בו.

הגדרה של Artifact Registry

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

45e384b87f7cf0db.png

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

d6a70f4cb4ebcbe3.png

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

9c2d1ce65258ef70.png

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

7a3c1f47346bea15.png

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

הגדרת Skaffold

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

פותחים שוב את Cloud Shell ובודקים אם skaffold מותקן. (Cloud Shell מתקינה את skaffold בסביבה כברירת מחדל). מריצים את הפקודה הבאה ובודקים את הגרסה של skaffold.

skaffold version

פלט הפקודה

v1.38.0

עכשיו אפשר לרשום את מאגר ברירת המחדל לשימוש ב-skaffold. כדי למצוא את נתיב הרישום, עוברים למרכז הבקרה של Artifact Registry ולוחצים על שם המאגר שהגדרתם בשלב הקודם.

7a3c1f47346bea15.png

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

e0f2ae2144880b8b.png

כשלוחצים על לחצן ההעתקה, תוצג תיבת דו-שיח בחלק התחתון של הדפדפן עם הודעה כמו:

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot; הועתק

חוזרים אל 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 שבו רצים מיקרו-שירותים של Codelab

הנושא הבא

בשלב הבא תפתחו, תדחוףו ותפרסו את המיקרו-שירותים (microservices) שלכם באשכול

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

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

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

ב-Codelab הזה, צריך לעדכן את קוד המקור שנמצא בתיקייה step0. אפשר גם לעיין בקוד המקור ב-step[1-6] תיקיות כדי לקבל את התשובות בשלבים הבאים. (חלק 1 מכסה את שלב 0 עד שלב 4, וחלק 2 מכסה את שלבים 5 ו-6)

הרצת פקודת skaffold

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

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 כמצופה.

הנושא הבא

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

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

מושג אינסטרומנטציה והפצה של מעקב

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

6be42e353b9bfd1d.png

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

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

OpenTelemetry עוזרת לכם:

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

רכיבים ב-OpenTelemetry Trace

b01f7bb90188db0d.png

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

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

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

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

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

היגוי הראשון של הכלי

שירות מחולל עומסים למכשירים

לוחצים על הלחצן 776a11bfb2122549.png בפינה הימנית העליונה של Cloud Shell כדי לפתוח את Cloud Shell Editor. פותחים את 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 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, אנחנו משתמשים בחבילת התרומה של 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. רוצה לקבל Tracer מ-TracerProvider בכל העולם באמצעות otel.Tracer()?
  2. יצירת טווח ברמה הבסיסית (root) באמצעות השיטה 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)
        }

סיימתם עם האינסטרומנטציה ביוצר טעינה (אפליקציית לקוח HTTP). חשוב לעדכן את go.mod ואת go.sum בפקודה go mod.

go mod tidy

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

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

bcaccd06691269f8.png

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

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 מכלי הטעינה ומפעילים אותה גם בפונקציה 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
}

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

קודם כול, אנחנו מחליפים את הרישום של ה-handler ב-handler של otelhttp. בפונקציה main, מחפשים את הקווים שבהם ה-handler של 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)

לאחר מכן אנחנו מגדירים את הטווח בפועל בתוך ה-handler. מוצאים את handler() של Func (*clientService) ומוסיפים אינסטרומנטציה ל-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))
        ...

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

סיכום

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

הנושא הבא

בשלב הבא, תגדירו לשירות הלקוח ולשירות השרת כדי לאשר את אופן ההפצה של Trace Context דרך gRPC.

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

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

75310d8e0e3b1a30.png

אינסטרומנטציה לפני פיתוח (pre-build) עבור לקוח gRPC

בסביבה העסקית של OpenTelemetry יש הרבה ספריות שימושיות שעוזרות למפתחים לשלב אפליקציות במכשירים. בשלב הקודם, השתמשנו באינסטרומנטציה של בנייה מראש עבור חבילה net/http. בשלב הזה, כשאנחנו מנסים להפיץ הקשר של מעקב דרך 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 שמחברים ל-spans חדשים בכל פעם שהלקוח שולח בקשות לשרת.

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

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

מפעילים את המיקרו-שירות (microservice) ומאשרים את המעקב

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

סיכום

בשלב הזה בוצעה אינסטרומנטציה של תקשורת שמבוססת על gRPC עם התמיכה מספריות הסביבה של OpenTelemetry.

הנושא הבא

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

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

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

שימוש ב-Cloud Trace Exporter

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

צריך לערוך את אותה פונקציה גם בשירות הלקוח ובשירות השרת.

מפעילים את המיקרו-שירות (microservice) ומאשרים את המעקב

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

skaffold dev

עכשיו לא רואים הרבה מידע על טווח תאריכים בפורמט של יומנים מובנים ב-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
...

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

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

3ecf131423fc4c40.png

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

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

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

סיכום

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

הנושא הבא

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

7. כדאי להוסיף תת-טווח לניתוח טוב יותר

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

3b63a1e471dddb8c.png

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

כך יוצרים תת-טווח בשרת

פותחים את main.go בשרת ומוצאים את הפונקציה readFiles. הפונקציה הזו שולחת בקשה ל-Google Cloud Storage לאחזור כל קובצי הטקסט של יצירות של שייקספיר. בפונקציה הזו אפשר ליצור תת-טווח, כמו מה שעשיתם לאינסטרומנטציה של שרת 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 חדש. בואו נראה איך זה הולך, על ידי הפעלת האפליקציה.

מפעילים את המיקרו-שירות (microservice) ומאשרים את המעקב

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

skaffold dev

ובוחרים מעקב אחד בשם query.request מרשימת המעקב. יוצג תרשים Waterfall דומה למעקב, מלבד טווח חדש מתחת ל-shakesapp.ShakespeareService/GetMatchCount. (התחום מוקף במלבן אדום למטה)

3d4a891aa30d7a32.png

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

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

סיכום

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

8. מזל טוב

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

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

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

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

פינוי מקום

אחרי שתעשו את ה-codelab הזה, צריך להפסיק את אשכול 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" (אדמין) &gt; 'הגדרות', ולאחר מכן ללחוץ על 'להורדה' לחצן.

45aa37b7d5e1ddd1.png

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