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

1. מבוא

e0509e8a07ad5537.png

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

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

ניראות (observability) ופרופיל רציף (continuous delivery)

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

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

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

מה תפַתחו

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

44e243182ced442f.png

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

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

מה תלמדו

  • איך מטמיעים סוכן פרופיל
  • איך חוקרים את צוואר הבקבוק ב-Cloud Profiler

ב-Codelab הזה מוסבר איך להשתמש בסוכן פרופיל רציף באפליקציה שלכם.

מה צריך להכין

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

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

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

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

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

7a32e5469db69e9.png

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

7136b3ee36ebaf89.png

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

870a3cbd6541ee86.png

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

affdc444517ba805.png

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

הנושא הבא

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

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

הרצת פקודת skaffold

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

cd step4
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. אינסטרומנטציה של סוכן Cloud Profiler

מושג פרופיילינג רציף

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

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

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

הטמעת סוכן Cloud Profiler

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

step4/src/server/main.go

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

        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)
        healthpb.RegisterHealthServer(srv, svc)
        if err := srv.Serve(lis); err != nil {
                log.Fatalf("error serving server: %v", err)
        }
}

בפונקציה main מופיע קוד הגדרה של OpenTelemetry ושל gRPC, שבוצע בחלק 1 של Codelab. עכשיו נוסיף כאן אינסטרומנטציה לסוכן Cloud Profiler. כמו שעשינו בשביל initTracer(), אפשר לכתוב פונקציה בשם initProfiler() כדי לשפר את הקריאוּת.

step4/src/server/main.go

import (
        ...
        "cloud.google.com/go/profiler" // step5. add profiler package
        "cloud.google.com/go/storage"
        ...
)

// step5: add Profiler initializer
func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.0.0",
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

בואו נסתכל מקרוב על האפשרויות שצוינו באובייקט profiler.Config{}.

  • Service: שם השירות שאפשר לבחור ולהפעיל במרכז השליטה של Profiler
  • ServiceVersion: השם של גרסת השירות. ניתן להשוות בין קבוצות של נתוני פרופילים על סמך הערך הזה.
  • NoHeapProfiling: השבתת הפרופיילינג של צריכת הזיכרון
  • NoAllocProfiling: השבתת הפרופיילינג של הקצאת הזיכרון
  • NoGoroutineProfiling: השבתת פרופיילינג גורוטין
  • NoCPUProfiling: השבתת הפרופיילינג של המעבד (CPU)

ב-Codelab הזה, אנחנו מפעילים רק את הפרופיילינג של המעבד (CPU).

עכשיו צריך פשוט לקרוא לפונקציה הזו בפונקציה main. צריך לייבא את חבילת Cloud Profiler בבלוק הייבוא.

step4/src/server/main.go

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

        // step5. start profiler
        go initProfiler()
        // step5. end

        svc := NewServerService()
        // step2: add interceptor
        ...
}

חשוב לדעת: מבצעים קריאה לפונקציה initProfiler() עם מילת המפתח go. מכיוון ש-profiler.Start() חוסם אותו, צריך להריץ אותו בבורות אחר. עכשיו האפליקציה מוכנה לבנייה. חשוב להריץ את הפקודה go mod tidy לפני הפריסה.

go mod tidy

עכשיו פורסים את האשכול עם שירות השרת החדש.

skaffold dev

בדרך כלל לוקח כמה דקות לראות את תרשים הלהבות ב-Cloud Profiler. מקלידים "פרופילr" בתיבת החיפוש שלמעלה ולוחצים על סמל Profiler.

3d8ca8a64b267a40.png

לאחר מכן יופיע תרשים הלהבות הבא.

7f80797dddc0128d.png

סיכום

בשלב הזה הטמעתם סוכן של Cloud Profiler בשירות השרת, ואישרתם שהוא יוצר תרשים להבות (flame chart).

הנושא הבא

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

5. ניתוח תרשים הלהבה של Cloud Profiler

מהו Flame Graph?

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

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

בהתאם לכך, נסתכל על תרשים הלהבות שהתקבל.

7f80797dddc0128d.png

ניתוח Flame Graph

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

6d90760c6c1183cd.png

במקרה שלנו, ברור ש-grpc.(*Server).serveStreams.func1.2 גוזל את רוב זמן המעבד (CPU), ועל ידי בדיקה של מקבץ הקריאות מלמעלה למטה, רוב הזמן נמצא ב-main.(*serverService).GetMatchCount, שהוא ה-handler של שרת gRPC בשירות השרת.

בקטע GetMatchCount מוצגת סדרה של פונקציות regexp: regexp.MatchString ו-regexp.Compile. הם מגיעים מחבילה רגילה: כלומר, צריך לבדוק אותם היטב מנקודות מבט רבות, כולל ביצועים. עם זאת, התוצאה כאן מראה שהשימוש במשאבי זמן המעבד (CPU) גבוה ב-regexp.MatchString וב-regexp.Compile. על סמך העובדות האלה, ההנחה כאן היא שהשימוש ב-regexp.MatchString קשור לבעיות בביצועים. עכשיו נקרא את קוד המקור שבו נעשה שימוש בפונקציה.

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line, query := strings.ToLower(line), strings.ToLower(req.Query)
                        isMatch, err := regexp.MatchString(query, line)
                        if err != nil {
                                return resp, err
                        }
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

זה המקום שבו regexp.MatchString נקרא. אם תקראו את קוד המקור, ייתכן שתבחינו שהפונקציה מופעלת בתוך רכיב ה-for-loop שהוצב בו. לכן יכול להיות שהשימוש בפונקציה הזו שגוי. נבחן את GoDoc של regexp.

80b8a4ba1931ff7b.png

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

סיכום

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

הנושא הבא

בשלב הבא, עליך לעדכן את קוד המקור של שירות השרת ולאשר את השינוי מהגרסה 1.0.0.

6. עדכון קוד המקור והבדלים בין תרשימי הלהבות

עדכון של קוד המקור

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

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }

        // step6. considered the process carefully and naively tuned up by extracting
        // regexp pattern compile process out of for loop.
        query := strings.ToLower(req.Query)
        re := regexp.MustCompile(query)
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line = strings.ToLower(line)
                        isMatch := re.MatchString(line)
                        // step6. done replacing regexp with strings
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

כמו שאפשר לראות, תהליך ההידור של דפוס regexp נשלף מה-regexp.MatchString ועובר מחוץ ללולאת for.

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

step4/src/server/main.go

func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.1.0", // step6. update version
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

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

skaffold dev

אחר כך אפשר לטעון מחדש את מרכז הבקרה של Cloud Profiler ולראות איך הוא נראה.

283cfcd4c13716ad.png

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

e3a1456b4aada9a5.png

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

841dec77d8ba5595.png

שינוי הערך של 'השוואה אל' מהתפריט הנפתח 'גרסה' ומשנים את הערך של 'גרסה להשוואה'. ל-"1.0.0", הגרסה המקורית.

5553844292d6a537.png

תראו תרשים להבות מהסוג הזה. צורת התרשים זהה ל-1.1.0, אבל הצבעים שונים. במצב השוואה, משמעות הצבע היא:

  • כחול: הערך (צריכת המשאבים) מופחתת
  • כתום: הערך (צריכת משאבים) שנצברו
  • אפור: ניטרלי

בהתאם למקרא, נבחן מקרוב את הפונקציה. אם תלחצו על הסרגל שבו רוצים להגדיל את התצוגה, תוכלו לראות פרטים נוספים בתוך המקבץ. צריך ללחוץ על עמודת main.(*serverService).GetMatchCount. כמו כן, אם תעבירו את העכבר מעל העמודה, יוצגו פרטי ההשוואה.

ca08d942dc1e2502.png

כתוב שזמן המעבד הכולל קוצר מ-5.26 שניות ל-2.88 שניות (סך הכול הוא 10 שניות = חלון דגימה). זה שיפור משמעותי!

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

סיכום

בשלב הזה ביצעתם עריכה בשירות השרת ואישרתם את השיפור במצב ההשוואה של Cloud Profiler.

הנושא הבא

בשלב הבא, עליך לעדכן את קוד המקור של שירות השרת ולאשר את השינוי מהגרסה 1.0.0.

7. שלב נוסף: מאשרים את השיפור ב-Waterfall של Trace

ההבדלים בין מעקב מבוזר לבין פרופיילינג רציף

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

בשלב הזה נבחן את תרשים ה-Waterfall מהמעקב המבוזר (Cloud Trace) ונראה את ההבדל בין הפרופיילינג הרציף.

תרשים ה-Waterfall הזה הוא אחד מהמעקבים עם השאילתה 'love'. נדרשות כ-6.7 שניות (6,700 אלפיות השנייה) בסה"כ.

e2b7dec25926ee51.png

וזהו אחרי השיפור של אותה שאילתה. כמו שאפשר לראות, זמן האחזור הכולל הוא עכשיו 1.5 שניות (1,500 אלפיות השנייה), וזה שיפור עצום מההטמעה הקודמת.

feeb7207f36c7e5e.png

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

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

סיכום

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

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" (אדמין) > 'הגדרות', ולאחר מכן ללחוץ על 'להורדה' לחצן.

45aa37b7d5e1ddd1.png

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