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

1. מבוא

505827108874614d.png

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

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

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

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

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

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

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

Distributed Trace

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

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

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

נתוני מעקב

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

מה תפַתחו

ב-Codelab הזה תלמדו איך להטמיע מידע על מעקב בשירותים שנקראים Shakespeare application (או Shakesapp) שפועלים באשכול Google Kubernetes Engine. הארכיטקטורה של Shakesapp היא כפי שמתואר בהמשך:

44e243182ced442f.png

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

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

מה תלמדו

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

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

מה תצטרכו

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

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

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

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

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

7a32e5469db69e9.png

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

7136b3ee36ebaf89.png

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

870a3cbd6541ee86.png

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

affdc444517ba805.png

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

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

15d0ef27a8fbab27.png

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

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

כדי להפעיל את Cloud Shell ממסוף Cloud, פשוט לוחצים על 'הפעלת 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 Console:

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 הזה נריץ אשכול של מיקרו-שירותים ב-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. שנה את הערך של התחום (zone) us-central1-f אם האזור של המאגר לא כולל את התחום (zone).

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

ב-codelab הזה, קראתי למאגר החדש trace-codelab. הפורמט של הארטיפקט הוא Docker וסוג המיקום הוא Region. בוחרים את האזור שקרוב לאזור שהגדרתם כברירת מחדל ב-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

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

הבא בתור

בשלב הבא, תבנו, תדחפו ותפרוסו את המיקרו-שירותים שלכם באשכול

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

הורדת חומרי ה-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: הגדרת 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

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

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

בעזרת OpenTelemetry, אפשר:

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

רכיבים ב-OpenTelemetry Trace

b01f7bb90188db0d.png

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

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

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

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

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

Instrument first span

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

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

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

step0/src/loadgen/main.go

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

מכיוון שגנרטור העומסים קורא לשירות הלקוח ב-HTTP עם net/http בפונקציה runQuery, אנחנו משתמשים בחבילת התוספים net/http ומפעילים את המדידה באמצעות התוסף של חבילת httptrace ו-otelhttp.

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

step0/src/loadgen/main.go

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

אחר כך מוסיפים אינסטרומנטציה בפונקציה runQuery כדי ליצור את יחידה לוגית למעקב המותאמת אישית באמצעות OpenTelemetry ויחידה לוגית למעקב שנוצרה אוטומטית מלקוח ה-HTTP המותאם אישית. מה עושים:

  1. קבלת Tracer מ-global 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

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

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

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 מ-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
}

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

קודם כול, מחליפים את רישום ה-handler ב-otelhttp Handler. בפונקציה 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. מחפשים את הפונקציה func (*clientService) handler()‎ ומוסיפים את האינסטרומנטציה של היחידה הלוגית למעקב באמצעות 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))
        ...

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

סיכום

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

הבא בתור

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

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

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

75310d8e0e3b1a30.png

הגדרה מראש של אינסטרומנטציה ללקוח gRPC

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

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() ומוסיפים את ה-interceptors לפונקציה.

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

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

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

הפעלת המיקרו-שירות ואישור העקבות

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

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

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

3ecf131423fc4c40.png

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

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

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

סיכום

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

הבא בתור

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

7. הוספת טווח משנה לניתוח טוב יותר

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

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

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

הפעלת המיקרו-שירות ואישור העקבות

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

skaffold dev

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

3d4a891aa30d7a32.png

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

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

סיכום

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

8. מזל טוב

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

כדי להרחיב את התרגול, אפשר לנסות את הנושאים הבאים לבד.

  • ההטמעה הנוכחית שולחת את כל הטווחים שנוצרו על ידי בדיקת תקינות. ‫(grpc.health.v1.Health/Check) איך מסננים את הטווחים האלה מ-Cloud Trace? רמז כאן.
  • התאמה בין יומני אירועים לבין טווחים, והסבר על אופן הפעולה ב-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' (ניהול הרשאות וניהול) > 'Settings' (הגדרות), ואז לוחצים על הלחצן 'SHUT DOWN' (כיבוי).

45aa37b7d5e1ddd1.png

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