1. מבוא
תאריך עדכון אחרון: 15 ביולי 2022
יכולת התצפית באפליקציה
יכולת תצפית ו-OpenTelemetry
המונח 'יכולת תצפית' משמש לתיאור מאפיין של מערכת. מערכת עם ניראות מאפשרת לצוותים לנפות באקטיביות באגים במערכת. בהקשר הזה, שלושת העמודים של יכולת התצפית – יומנים, מדדים ומעקב – הם הכלים הבסיסיים שבעזרתם המערכת יכולה לקבל יכולת תצפית.
OpenTelemetry היא קבוצה של מפרטים, ספריות וסוכנויות שמאיצות את המדידה ואת הייצוא של נתוני הטלמטריה (יומנים, מדדים ומעקב) הנדרשים לצורך יכולת התצפית. OpenTelemetry הוא פרויקט קהילתי ב-CNCF שמבוסס על סטנדרט פתוח. בעזרת הספריות שהפרויקט והסביבה העסקית שלו מספקים, המפתחים יכולים לבדוק את האפליקציות שלהם באופן ניטרלי לספקים ובמספר ארכיטקטורות.
בנוסף לשלושת העמודים של יכולת התצפית, יצירת פרופילים רציפה היא רכיב מפתח נוסף ליכולת התצפית, והיא מרחיבת את בסיס המשתמשים בתעשייה. Cloud Profiler הוא אחד מהכלים הראשונים שאפשר להשתמש בהם לצורך הניתוח, והוא מספק ממשק קל לניתוח מעמיק של מדדי הביצועים בסטאקים של קריאות לאפליקציות.
סדנת הקוד הזו היא חלק 1 בסדרה, והיא עוסקת בשימוש ב-OpenTelemetry וב-Cloud Trace לצורך הטמעת כלי למעקב אחר אירועים בענן במיקרו-שירותים. בחלק 2 נסביר על יצירת פרופילים רציפה באמצעות Cloud Profiler.
Distributed Trace
בין היומנים, המדדים והמעקבים, מעקב הוא טלמטריה שמציינת את זמן האחזור של חלק ספציפי בתהליך במערכת. במיוחד בעידן של מיקרו-שירותים, מעקב מבוזבז הוא הגורם העיקרי לזיהוי צווארי בקבוק של זמן אחזור במערכת הכוללת והמבוזרת.
כשמנתחים נתוני מעקב מבוזרים, התצוגה החזותית של נתוני המעקב היא המפתח להבנה מיידית של זמני האחזור הכוללים של המערכת. ב-distributed trace, אנחנו מטפלים בקבוצת קריאות לעיבוד בקשה אחת לנקודת הכניסה למערכת, בצורה של Trace שמכיל כמה Spans.
Span מייצג יחידת עבודה בודדת שבוצעה במערכת מבוזרת, ומתעדות את זמני ההתחלה והסיום שלה. לעתים קרובות יש בין span'ים יחסים היררכיים – בתמונה שבהמשך, כל ה-span'ים הקטנים יותר הם span'ים צאצאים של span גדול מסוג /messages, והם מורכבים ל-Trace אחד שמציג את נתיב העבודה במערכת.
Google Cloud Trace הוא אחת מהאפשרויות לקצה העורפי של מעקב מבוזבז, והוא משולב היטב עם מוצרים אחרים ב-Google Cloud.
מה תפַתחו
בקודלאב הזה תלמדו איך להטמיע מידע על נתוני מעקב בשירותים שנקראים 'אפליקציית שייקספיר' (נקראת גם Shakesapp) שפועלים באשכול של Google Kubernetes Engine. הארכיטקטורה של Shakesapp מתוארת בהמשך:
- Loadgen שולח למחרוזת שאילתה ללקוח ב-HTTP
- לקוחות מעבירים את השאילתה מ-loadgen לשרת ב-gRPC
- השרת מקבל את השאילתה מהלקוח, מאחזר את כל כתבי שייקספיר בפורמט טקסט מ-Google Cloud Storage, מחפש את השורות שמכילות את השאילתה ומחזיר ללקוח את מספר השורה שתואמת לשאילתה
תצטרכו להטמיע את פרטי המעקב בבקשה. לאחר מכן, תטמיעו סוכן לניתוחי פרופיל בשרת ותבדקו את צוואר הבקבוק.
מה תלמדו
- איך מתחילים לעבוד עם ספריות OpenTelemetry Trace בפרויקט Go
- איך יוצרים span באמצעות הספרייה
- איך להעביר הקשרים של span דרך החיבור בין רכיבי האפליקציה
- איך שולחים נתוני מעקב ל-Cloud Trace
- איך מנתחים את המעקב ב-Cloud Trace
בקודלאב הזה מוסבר איך להטמיע את המיקרו-שירותים. כדי להקל על ההבנה, הדוגמה הזו מכילה רק 3 רכיבים (גנרטור עומסים, לקוח ושרת), אבל אפשר להחיל את אותו תהליך שמתואר ב-codelab הזה על מערכות מורכבות וגדולות יותר.
מה צריך להכין
- ידע בסיסי ב-Go
- ידע בסיסי ב-Kubernetes
2. הגדרה ודרישות
הגדרת סביבה בקצב אישי
אם עדיין אין לכם חשבון Google (Gmail או Google Apps), עליכם ליצור חשבון. נכנסים למסוף Google Cloud Platform ( console.cloud.google.com) ויוצרים פרויקט חדש.
אם כבר יש לכם פרויקט, לוחצים על התפריט הנפתח לבחירת פרויקט בפינה הימנית העליונה של המסוף:
ולוחצים על הלחצן 'פרויקט חדש' בתיבת הדו-שיח שנפתחת כדי ליצור פרויקט חדש:
אם עדיין אין לכם פרויקט, אמורה להופיע תיבת דו-שיח כזו כדי ליצור את הפרויקט הראשון:
בתיבת הדו-שיח הבאה ליצירת פרויקט תוכלו להזין את הפרטים של הפרויקט החדש:
חשוב לזכור את מזהה הפרויקט, שהוא שם ייחודי לכל הפרויקטים ב-Google Cloud (השם שלמעלה כבר נלקח ולא יתאים לכם, סליחה!). בהמשך הקודה לאימון נתייחס אליו בתור PROJECT_ID.
לאחר מכן, אם עדיין לא עשיתם זאת, תצטרכו להפעיל את החיוב במסוף למפתחים כדי להשתמש במשאבים של Google Cloud ולהפעיל את Cloud Trace API.
השלמת הקודלאב הזה לא אמורה לעלות יותר מכמה דולרים, אבל העלות עשויה להיות גבוהה יותר אם תחליטו להשתמש במשאבים נוספים או אם תמשיכו להפעיל אותם (ראו את הקטע 'ניקוי' בסוף המסמך הזה). התעריפים של Google Cloud Trace, Google Kubernetes Engine ו-Google Artifact Registry מפורטים במסמכים הרשמיים.
- תמחור של חבילת התפעול של Google Cloud | Operations Suite
- Pricing | Kubernetes Engine Documentation
- תמחור של Artifact Registry | מסמכי התיעוד של Artifact Registry
משתמשים חדשים ב-Google Cloud Platform זכאים לתקופת ניסיון בחינם בשווי 300$, כך שהקודלאב הזה אמור להיות ללא תשלום.
הגדרת Google Cloud Shell
אפשר להפעיל את Google Cloud ו-Google Cloud Trace מרחוק מהמחשב הנייד, אבל בסדנת הקוד הזו נשתמש ב-Google Cloud Shell, סביבת שורת פקודה שפועלת ב-Cloud.
המכונה הווירטואלית הזו מבוססת על Debian, וטעונים בה כל הכלים הדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, משפרת מאוד את הביצועים והאימות של הרשת. כל מה שצריך לקודלאב הזה הוא דפדפן (כן, הוא פועל ב-Chromebook).
כדי להפעיל את Cloud Shell במסוף Cloud, פשוט לוחצים על Activate Cloud Shell (ההקצאה והחיבור לסביבה אמורים להימשך רק כמה רגעים).
אחרי שתתחברו ל-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:
ב-Cloud Shell מוגדרים גם כמה משתני סביבה כברירת מחדל, שיכולים להיות שימושיים כשמריצים פקודות בעתיד.
echo $GOOGLE_CLOUD_PROJECT
פלט הפקודה
<PROJECT_ID>
לסיום, מגדירים את אזור ברירת המחדל ואת הגדרות הפרויקט.
gcloud config set compute/zone us-central1-f
אפשר לבחור מגוון תחומים שונים. מידע נוסף זמין במאמר אזורים ותחומים.
כניסה להגדרת השפה
בסדנת הקוד הזו אנחנו משתמשים ב-Go לכל קוד המקור. מריצים את הפקודה הבאה ב-Cloud Shell ומוודאים שגרסת Go היא 1.17 ואילך.
go version
פלט הפקודה
go version go1.18.3 linux/amd64
הגדרת אשכולות Google Kubernetes
בקודלאב הזה תלמדו להריץ אשכול של מיקרו-שירותים ב-Google Kubernetes Engine (GKE). התהליך ב-codelab הזה הוא:
- הורדת הפרויקט הבסיסי ל-Cloud Shell
- פיתוח מיקרו-שירותים בקונטיינרים
- העלאת קונטיינרים ל-Google Artifact Registry (GAR)
- פריסת קונטיינרים ב-GKE
- שינוי קוד המקור של שירותים לצורך הטמעת כלי למעקב אחר נתונים
- מעבר לשלב 2
הפעלת Kubernetes Engine
קודם כול, מגדירים אשכול Kubernetes שבו Shakesapp פועל ב-GKE, לכן צריך להפעיל את GKE. עוברים לתפריט Kubernetes Engine ולוחצים על הלחצן ENABLE.
עכשיו אתם מוכנים ליצור אשכול Kubernetes.
יצירת אשכול Kubernetes
ב-Cloud Shell, מריצים את הפקודה הבאה כדי ליצור אשכול Kubernetes. מוודאים שהערך של הדומיין נמצא באזור שבו תשתמשו ליצירת המאגר ב-Artifact Registry. משנים את ערך האזור us-central1-f
אם אזור המאגר לא מכסה את האזור.
gcloud container clusters create otel-trace-codelab2 \ --zone us-central1-f \ --release-channel rapid \ --preemptible \ --enable-autoscaling \ --max-nodes 8 \ --no-enable-ip-alias \ --scopes cloud-platform
פלט הפקודה
Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s). Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done. Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2]. To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403 kubeconfig entry generated for otel-trace-codelab2. NAME: otel-trace-codelab2 LOCATION: us-central1-f MASTER_VERSION: 1.23.6-gke.1501 MASTER_IP: 104.154.76.89 MACHINE_TYPE: e2-medium NODE_VERSION: 1.23.6-gke.1501 NUM_NODES: 3 STATUS: RUNNING
הגדרת Artifact Registry ו-skaffold
עכשיו יש לנו אשכול Kubernetes מוכן לפריסה. בשלב הבא נתכונן לשימוש ב-Container Registry כדי לדחוף ולפרוס קונטיינרים. כדי לבצע את השלבים האלה, צריך להגדיר Artifact Registry (GAR) ו-skaffold כדי להשתמש בהם.
הגדרת Artifact Registry
עוברים לתפריט של Artifact Registry ומקישים על הלחצן ENABLE.
אחרי כמה רגעים יוצג דפדפן המאגר של GAR. לוחצים על הלחצן 'CREATE REPOSITORY' ומזינים את שם המאגר.
בקודלאב הזה, נתתי למאגר החדש את השם trace-codelab
. הפורמט של הארטיפקט הוא 'Docker' וסוג המיקום הוא 'אזור'. בוחרים את האזור הקרוב לאזור שהגדרתם כתחום ברירת המחדל של Google Compute Engine. לדוגמה, בדוגמה הזו בחרנו ב-'us-central1-f' למעלה, לכן כאן בוחרים ב-'us-central1 (Iowa)'. לאחר מכן לוחצים על הלחצן 'יצירה'.
עכשיו השם trace-codelab יופיע בדפדפן המאגר.
נגיע לכאן מאוחר יותר כדי לבדוק את נתיב הרישום.
הגדרה של Skaffold
Skaffold הוא כלי שימושי ליצירת מיקרו-שירותים שפועלים ב-Kubernetes. הוא מטפל בתהליך העבודה של יצירה, דחיפה ופריסה של קונטיינרים של אפליקציות באמצעות קבוצה קטנה של פקודות. כברירת מחדל, Skaffold משתמש ב-Docker Registry כמאגר קונטיינרים, לכן צריך להגדיר את Skaffold לזיהוי GAR כשדוחפים קונטיינרים.
פותחים שוב את Cloud Shell ומוודאים ש-skaffold מותקן. (Cloud Shell מתקין את skaffold בסביבה כברירת מחדל). מריצים את הפקודה הבאה כדי לראות את גרסת skaffold.
skaffold version
פלט הפקודה
v1.38.0
עכשיו אפשר לרשום את מאגר ברירת המחדל לשימוש של skaffold. כדי לקבל את נתיב הרישום, עוברים למרכז הבקרה של Artifact Registry ולוחצים על שם המאגר שהגדרתם בשלב הקודם.
לאחר מכן יופיעו נתיבי breadcrumbs בחלק העליון של הדף. לוחצים על הסמל כדי להעתיק את נתיב הרישום ללוח.
כשלוחצים על לחצן ההעתקה, תיבת הדו-שיח מופיעה בתחתית הדפדפן עם הודעה כמו:
הועתק "us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab"
חוזרים ל-Cloud Shell. מריצים את הפקודה skaffold config set default-repo
עם הערך שהעתקתם זה עתה מלוח הבקרה.
skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab
פלט הפקודה
set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox
בנוסף, צריך להגדיר את המרשם להגדרות של Docker. מריצים את הפקודה הבאה:
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
פלט הפקודה
{ "credHelpers": { "gcr.io": "gcloud", "us.gcr.io": "gcloud", "eu.gcr.io": "gcloud", "asia.gcr.io": "gcloud", "staging-k8s.gcr.io": "gcloud", "marketplace.gcr.io": "gcloud", "us-central1-docker.pkg.dev": "gcloud" } } Adding credentials for: us-central1-docker.pkg.dev
עכשיו אפשר להמשיך לשלב הבא כדי להגדיר מאגר Kubernetes ב-GKE.
סיכום
בשלב הזה מגדירים את סביבת ה-Codelab:
- הגדרת Cloud Shell
- יצרתם מאגר Artifact Registry למאגר הקונטיינרים
- הגדרת skaffold לשימוש במאגר הקונטיינרים
- יצירת אשכול Kubernetes שבו פועלים המיקרו-שירותים של הקודלמאב
השלב הבא
בשלב הבא נתאר איך יוצרים, מעבירים ומפרסים את המיקרו-שירותים באשכול
3. פיתוח, דחיפה ופריסה של המיקרו-שירותים
הורדת החומר של ה-Codelab
בשלב הקודם הגדרנו את כל הדרישות המוקדמות ל-codelab הזה. עכשיו אתם מוכנים להריץ מיקרו-שירותים שלמים על גביהם. חומרי הקודלמעבדה מתארחים ב-GitHub, לכן צריך להוריד אותם לסביבת Cloud Shell באמצעות פקודת git הבאה.
cd ~ git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git cd opentelemetry-trace-codelab-go
מבנה הספריות של הפרויקט הוא:
. ├── README.md ├── step0 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step1 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step2 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step3 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step4 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step5 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src └── step6 ├── manifests ├── proto ├── skaffold.yaml └── src
- manifests: קובצי מניפסט של Kubernetes
- proto: הגדרת proto לתקשורת בין הלקוח לשרת
- src: ספריות לקוד המקור של כל השירותים
- skaffold.yaml: קובץ תצורה של skaffold
בשיעור ה-Codelab הזה תעדכנו את קוד המקור שנמצא בתיקייה step0
. אפשר גם לעיין בקוד המקור בתיקיות step[1-6]
כדי למצוא את התשובות לשלבים הבאים. (חלק 1 מכסה את שלבים 0 עד 4, וחלק 2 מכסה את שלבים 5 ו-6)
הרצת הפקודה skaffold
עכשיו אתם מוכנים ליצור, לדחוף ולפרוס תוכן שלם באשכולות Kubernetes שיצרתם. נשמע כאילו יש כאן כמה שלבים, אבל בפועל skaffold עושה את כל העבודה בשבילכם. ננסה את זה באמצעות הפקודה הבאה:
cd step0 skaffold dev
מיד לאחר הפעלת הפקודה, תוצג פלט היציאה של היומן של docker build
ותוכלו לוודא שהם הועברו לרשומת הרישום בהצלחה.
פלט הפקודה
... ---> Running in c39b3ea8692b ---> 90932a583ab6 Successfully built 90932a583ab6 Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1 The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice] cc8f5a05df4a: Preparing 5bf719419ee2: Preparing 2901929ad341: Preparing 88d9943798ba: Preparing b0fdf826a39a: Preparing 3c9c1e0b1647: Preparing f3427ce9393d: Preparing 14a1ca976738: Preparing f3427ce9393d: Waiting 14a1ca976738: Waiting 3c9c1e0b1647: Waiting b0fdf826a39a: Layer already exists 88d9943798ba: Layer already exists f3427ce9393d: Layer already exists 3c9c1e0b1647: Layer already exists 14a1ca976738: Layer already exists 2901929ad341: Pushed 5bf719419ee2: Pushed cc8f5a05df4a: Pushed step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001
אחרי הדחיפה של כל קונטיינרי השירות, הפריסות ב-Kubernetes מתחילות באופן אוטומטי.
פלט הפקודה
sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997 Tags used in deployment: - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a Starting deploy... - deployment.apps/clientservice created - service/clientservice created - deployment.apps/loadgen created - deployment.apps/serverservice created - service/serverservice created
אחרי הפריסה, יוצגו יומני האפליקציה בפועל שמועברים ל-stdout בכל הקונטיינרים, באופן הבא:
פלט הפקודה
[client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:16 {"match_count":3040} [loadgen] 2022/07/14 06:33:16 query 'love': matched 3040 [client] 2022/07/14 06:33:19 {"match_count":463} [loadgen] 2022/07/14 06:33:19 query 'tear': matched 463 [loadgen] 2022/07/14 06:33:20 query 'world': matched 728 [client] 2022/07/14 06:33:20 {"match_count":728} [client] 2022/07/14 06:33:22 {"match_count":463} [loadgen] 2022/07/14 06:33:22 query 'tear': matched 463
חשוב לזכור שבשלב הזה, אתם רוצים לראות את כל ההודעות מהשרת. עכשיו אתם מוכנים להתחיל להטמיע את האפליקציה באמצעות OpenTelemetry למעקב מבוזבז אחרי השירותים.
לפני שמתחילים להטמיע את השירות, צריך לכבות את האשכולות באמצעות Ctrl-C.
פלט הפקודה
... [client] 2022/07/14 06:34:57 {"match_count":1} [loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1 ^CCleaning up... - W0714 06:34:58.464305 28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead. - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
סיכום
בשלב הזה, הכנתם את החומר של ה-codelab בסביבה שלכם וווידאתם ש-skaffold פועל כצפוי.
השלב הבא
בשלב הבא, תשנו את קוד המקור של שירות loadgen כדי להטמיע את פרטי המעקב.
4. אינסטרומנטציה ל-HTTP
המושג של כלי למעקב אחר נתונים והעברה שלהם
לפני שנערוך את קוד המקור, אסביר בקצרה איך פועלים עקבות מבוזרים באמצעות תרשים פשוט.
בדוגמה הזו, אנחנו משתמשים בקוד כדי לייצא את המידע של Trace ו-Span ל-Cloud Trace ולהפיץ את ההקשר של המעקב בבקשה משירות loadgen לשירות השרת.
כדי ש-Cloud Trace יוכל לאסוף את כל ה-spans שיש להם את אותו מזהה Trace ולקבץ אותם למעקב אחד, האפליקציות צריכות לשלוח מטא-נתונים של מעקב, כמו מזהה מעקב ומזהה span. בנוסף, האפליקציה צריכה להפיץ את הקשרי המעקב (השילוב של מזהה המעקב ומזהה ה-span של ה-span ההורה) בשירותים מצד הלקוח שמבקשים את המעקב, כדי שהם יוכלו לדעת באיזה הקשר מעקב הם מטפלים.
OpenTelemetry עוזר לכם:
- כדי ליצור מזהה Trace ID ומזהה Span ID ייחודיים
- כדי לייצא את מזהה המעקב ואת מזהה ה-span לקצה העורפי
- להעברת הקשרי המעקב לשירותים אחרים
- להטמיע מטא-נתונים נוספים שיעזרו לנתח את העקבות
רכיבים ב-OpenTelemetry Trace
התהליך להטמעת מעקב אחר אפליקציות באמצעות OpenTelemetry הוא:
- יצירת גורם ייצוא
- יוצרים TracerProvider שמקשר את ה-exporter בשלב 1 ומגדירים אותו כ-global.
- הגדרת TextMapPropagaror כדי להגדיר את שיטת ההפצה
- אחזור של Tracer מ-TracerProvider
- יצירת Span מה-Tracer
בשלב הזה, אין צורך להבין את המאפיינים המפורטים בכל רכיב, אבל חשוב לזכור את הדברים הבאים:
- הכלי לייצוא כאן ניתן לחיבור ל-TracerProvider
- ב-TracerProvider נשמרות כל ההגדרות לגבי דגימת נתוני המעקב וייצוא שלהם
- כל העקבות נארזים באובייקט Tracer
אחרי שהבנו את זה, נעבור לעבודה בפועל על הקוד.
הקטע הראשון של הכלי
שירות יצירת עומסי כלי
כדי לפתוח את Cloud Shell Editor, לוחצים על הלחצן בפינה הימנית העליונה של Cloud Shell. פותחים את step0/src/loadgen/main.go
מהחלונית השמאלית של ה-Explorer ומאתרים את הפונקציה הראשית.
step0/src/loadgen/main.go
func main() { ... for range t.C { log.Printf("simulating client requests, round %d", i) if err := run(numWorkers, numConcurrency); err != nil { log.Printf("aborted round with error: %v", err) } log.Printf("simulated %d requests", numWorkers) if numRounds != 0 && i > numRounds { break } i++ } }
בפונקציה הראשית, רואים את הלולאה שמפעילה את הפונקציה run
. בהטמעה הנוכחית, הקטע מכיל 2 שורות ביומן שמתעדות את ההתחלה והסיום של קריאת הפונקציה. עכשיו נוסיף למידע של Span כלי למעקב אחר זמן האחזור של קריאת הפונקציה.
קודם כול, כפי שצוין בקטע הקודם, נגדיר את כל ההגדרות של OpenTelemetry. מוסיפים את החבילות של OpenTelemetry באופן הבא:
step0/src/loadgen/main.go
import ( "context" // step1. add packages "encoding/json" "fmt" "io" "log" "math/rand" "net/http" "net/url" "time" // step1. add packages "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "go.opentelemetry.io/otel/trace" // step1. end add packages )
כדי לשפר את הקריאוּת, יוצרים פונקציית הגדרה בשם initTracer
ומפעילים אותה בפונקציה main
.
step0/src/loadgen/main.go
// step1. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // create a stdout exporter to show collected spans out to stdout. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil }
שימו לב שההליך להגדרת OpenTelemetry זהה לזה שמתואר בקטע הקודם. בהטמעה הזו אנחנו משתמשים בייצואן stdout
שמייצא את כל פרטי המעקב ל-stdout בפורמט מובנה.
לאחר מכן, קוראים לה מהפונקציה הראשית. קוראים ל-initTracer()
ומוודאים שקוראים ל-TracerProvider.Shutdown()
כשסוגרים את האפליקציה.
step0/src/loadgen/main.go
func main() { // step1. setup OpenTelemetry tp, err := initTracer() if err != nil { log.Fatalf("failed to initialize TracerProvider: %v", err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step1. end setup log.Printf("starting worder with %d workers in %d concurrency", numWorkers, numConcurrency) log.Printf("number of rounds: %d (0 is inifinite)", numRounds) ...
בסיום ההגדרה, צריך ליצור Span עם מזהה Trace ומזהה Span ייחודיים. OpenTelemetry מספק ספרייה שימושית לכך. הוספת חבילות חדשות ללקוח ה-HTTP המכשיר.
step0/src/loadgen/main.go
import ( "context" "encoding/json" "fmt" "io" "log" "math/rand" "net/http" "net/http/httptrace" // step1. add packages "net/url" "time" // step1. add packages "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" // step1. end add packages "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "go.opentelemetry.io/otel/trace" )
מאחר שיצרן העומסים קורא לשירות הלקוח ב-HTTP באמצעות net/http
בפונקציה runQuery
, אנחנו משתמשים בחבילת contrib עבור net/http
ומפעילים את המדידה באמצעות התוסף של httptrace
והחבילה otelhttp
.
קודם מוסיפים משתנה גלובלי לחבילה httpClient כדי לקרוא לבקשות HTTP דרך הלקוח המכשיר.
step0/src/loadgen/main.go
var httpClient = http.Client{ Transport: otelhttp.NewTransport(http.DefaultTransport) }
בשלב הבא מוסיפים מכשור בפונקציה runQuery
כדי ליצור את ה-span המותאם אישית באמצעות OpenTelemetry ואת ה-span שנוצר באופן אוטומטי מלקוח ה-HTTP המותאם אישית. עליכם לבצע את הפעולות הבאות:
- איך מקבלים דיווח על אירוע ב-
TracerProvider
ברמת האתר באמצעותotel.Tracer()
- יצירת span ברמה הבסיסית באמצעות השיטה
Tracer.Start()
- סיום התקופה של השורש במועד שרירותי (במקרה הזה, בסיום הפונקציה
runQuery
)
step0/src/loadgen/main.go
reqURL.RawQuery = v.Encode() // step1. replace http.Get() with custom client call // resp, err := http.Get(reqURL.String()) // step1. instrument trace ctx := context.Background() tr := otel.Tracer("loadgen") ctx, span := tr.Start(ctx, "query.request", trace.WithAttributes( semconv.TelemetrySDKLanguageGo, semconv.ServiceNameKey.String("loadgen.runQuery"), attribute.Key("query").String(s), )) defer span.End() ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx)) req, err := http.NewRequestWithContext(ctx, "GET", reqURL.String(), nil) if err != nil { return -1, fmt.Errorf("error creating HTTP request object: %v", err) } resp, err := httpClient.Do(req) // step1. end instrumentation if err != nil { return -1, fmt.Errorf("error sending request to %v: %v", reqURL.String(), err) }
עכשיו סיימתם את תהליך הכלי למדידת ביצועים ב-loadgen (אפליקציית לקוח HTTP). חשוב לעדכן את go.mod
ו-go.sum
באמצעות הפקודה go mod
.
go mod tidy
שירות לקוחות של כלי
בסעיף הקודם, הטמענו מכשירי מדידה בחלק שמוקף במלבן האדום בתרשים שבהמשך. הטמענו את פרטי ה-span בשירות ה-load generator. בדומה לשירות ליצירת עומסים, עכשיו צריך להטמיע את השירות של הלקוח. ההבדל ביחס לשירות ה-load generator הוא ששירות הלקוח צריך לחלץ את פרטי מזהה המעקב שהועברו משירות ה-load generator בכותרת ה-HTTP ולהשתמש במזהה כדי ליצור Spans.
פותחים את Cloud Shell Editor ומוסיפים את החבילות הנדרשות, כמו שעשינו לשירות ה-load generator.
step0/src/client/main.go
import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "os" "time" "opentelemetry-trace-codelab-go/client/shakesapp" // step1. add new import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" // step1. end new import )
שוב, אנחנו צריכים להגדיר את OpenTelemtry. פשוט מעתיקים את הפונקציה initTracer
מ-loadgen ומדביקים אותה גם בפונקציה main
של שירות הלקוח.
step0/src/client/main.go
// step1. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // create a stdout exporter to show collected spans out to stdout. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil }
עכשיו הגיע הזמן להוסיף רכיבי מעקב ל-spans. מכיוון ששירות הלקוח צריך לקבל בקשות HTTP משירות loadgen, צריך להטמיע את הכלי למעקב ב-handler. שרת ה-HTTP בשירות הלקוח מיושם באמצעות net/http, וניתן להשתמש בחבילה otelhttp
כמו שעשינו ב-loadgen.
קודם כול, מחליפים את רישום ה-handler ב-otelhttp
Handler. בפונקציה main
, מחפשים את השורות שבהן הטיפול ב-HTTP רשום ב-http.HandleFunc()
.
step0/src/client/main.go
// step1. change handler to intercept OpenTelemetry related headers // http.HandleFunc("/", svc.handler) otelHandler := otelhttp.NewHandler(http.HandlerFunc(svc.handler), "client.handler") http.Handle("/", otelHandler) // step1. end intercepter setting http.HandleFunc("/_genki", svc.health)
לאחר מכן אנחנו מתעדים את ה-span בפועל בתוך הטיפול. מחפשים את func (*clientService) handler(), ומוסיפים את הכלי למדידת זמן ביקורת (span) באמצעות trace.SpanFromContext()
.
step0/src/client/main.go
func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) { ... ctx := r.Context() ctx, cancel := context.WithCancel(ctx) defer cancel() // step1. instrument trace span := trace.SpanFromContext(ctx) defer span.End() // step1. end instrument ...
בעזרת המדידה הזו, מקבלים את הקטעים מתחילת השיטה handler
ועד לסופה. כדי שיהיה קל לנתח את הקטעים, מוסיפים לשאילתה מאפיין נוסף ששומר את מספר ההתאמות. מוסיפים את הקוד הבא ממש לפני שורת היומן.
func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) { ... // step1. add span specific attribute span.SetAttributes(attribute.Key("matched").Int64(resp.MatchCount)) // step1. end adding attribute log.Println(string(ret)) ...
אחרי שמוסיפים את כל האמצעים למדידת ביצועים שמפורטים למעלה, השלמת את המדידה של ה-trace בין loadgen לבין הלקוח. עכשיו נראה איך זה עובד. מריצים שוב את הקוד באמצעות skaffold.
skaffold dev
אחרי זמן מה של הפעלת השירותים באשכול GKE, תראו כמות עצומה של הודעות ביומן כמו זו:
פלט הפקודה
[loadgen] { [loadgen] "Name": "query.request", [loadgen] "SpanContext": { [loadgen] "TraceID": "cfa22247a542beeb55a3434392d46b89", [loadgen] "SpanID": "18b06404b10c418b", [loadgen] "TraceFlags": "01", [loadgen] "TraceState": "", [loadgen] "Remote": false [loadgen] }, [loadgen] "Parent": { [loadgen] "TraceID": "00000000000000000000000000000000", [loadgen] "SpanID": "0000000000000000", [loadgen] "TraceFlags": "00", [loadgen] "TraceState": "", [loadgen] "Remote": false [loadgen] }, [loadgen] "SpanKind": 1, [loadgen] "StartTime": "2022-07-14T13:13:36.686751087Z", [loadgen] "EndTime": "2022-07-14T13:14:31.849601964Z", [loadgen] "Attributes": [ [loadgen] { [loadgen] "Key": "telemetry.sdk.language", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "go" [loadgen] } [loadgen] }, [loadgen] { [loadgen] "Key": "service.name", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "loadgen.runQuery" [loadgen] } [loadgen] }, [loadgen] { [loadgen] "Key": "query", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "faith" [loadgen] } [loadgen] } [loadgen] ], [loadgen] "Events": null, [loadgen] "Links": null, [loadgen] "Status": { [loadgen] "Code": "Unset", [loadgen] "Description": "" [loadgen] }, [loadgen] "DroppedAttributes": 0, [loadgen] "DroppedEvents": 0, [loadgen] "DroppedLinks": 0, [loadgen] "ChildSpanCount": 5, [loadgen] "Resource": [ [loadgen] { [loadgen] "Key": "service.name", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "unknown_service:loadgen" ...
היצוא של stdout
פולט את ההודעות האלה. שימו לב שההורים של כל ה-spans של loadgen הם TraceID: 00000000000000000000000000000000
, כי זהו ה-span ברמה הבסיסית, כלומר ה-span הראשון במעקב. בנוסף, תוכלו לראות שמאפיין ההטמעה "query"
מכיל את מחרוזת השאילתה שמועברת לשירות הלקוח.
סיכום
בשלב הזה, הטמעתם את השירות ליצירת עומסים ואת שירות הלקוח שמתקשרים ב-HTTP, ואימתתם שאפשר להפיץ את ה-Trace Context בין השירותים ולייצא את פרטי ה-Span משני השירותים ל-stdout.
השלב הבא
בשלב הבא תוסיפו מכשירי מדידה לשירות הלקוח ולשירות השרת כדי לוודא איך להפיץ את ה-Trace Context דרך gRPC.
5. אינסטרומנטציה ל-gRPC
בשלב הקודם, הטמענו את המחצית הראשונה של הבקשה במיקרו-שירותים האלה. בשלב הזה אנחנו מנסים להשתמש בכלי למדידת התקשורת של gRPC בין שירות הלקוח לשירות השרת. (המלבן הירוק והסגול בתמונה שבהמשך)
הטמעת מכשירי מדידה לפני ה-build ללקוח gRPC
הסביבה העסקית של OpenTelemetry כוללת הרבה ספריות שימושיות שעוזרות למפתחים להתקין כלי למדידת ביצועים באפליקציות. בשלב הקודם השתמשנו במדידה מראש ל-build של החבילה net/http
. בשלב הזה, אנחנו מנסים להפיץ את Trace Context דרך gRPC, ולכן אנחנו משתמשים בספרייה.
קודם כול, מייבאים את חבילת ה-gRPC שנוצרה מראש שנקראת otelgrpc
.
step0/src/client/main.go
import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "os" "time" "opentelemetry-trace-codelab-go/client/shakesapp" // step2. add prebuilt gRPC package (otelgrpc) "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" )
הפעם, שירות הלקוח הוא לקוח gRPC מול שירות השרת, כך שצריך להטמיע את לקוח ה-gRPC. מחפשים את הפונקציה mustConnGRPC
ומוסיפים מנטרים של gRPC שמטמיעים קטעי קוד חדשים בכל פעם שהלקוח שולח בקשות לשרת.
step0/src/client/main.go
// Helper function for gRPC connections: Dial and create client once, reuse. func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { var err error // step2. add gRPC interceptor interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider()) *conn, err = grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(interceptorOpt)), grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(interceptorOpt)), grpc.WithTimeout(time.Second*3), ) // step2: end adding interceptor if err != nil { panic(fmt.Sprintf("Error %s grpc: failed to connect %s", err, addr)) } }
כבר הגדרתם את OpenTelemetry בקטע הקודם, ולכן אין צורך לעשות זאת.
מכשירי מדידה מוכנים מראש לשרת gRPC
בדומה למה שעשינו עבור לקוח ה-gRPC, אנחנו קוראים למכשיר שנוצר מראש בשרת ה-gRPC. מוסיפים חבילה חדשה לקטע הייבוא, למשל:
step0/src/server/main.go
import ( "context" "fmt" "io/ioutil" "log" "net" "os" "regexp" "strings" "opentelemetry-trace-codelab-go/server/shakesapp" "cloud.google.com/go/storage" // step2. add OpenTelemetry packages including otelgrpc "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "google.golang.org/api/iterator" "google.golang.org/api/option" "google.golang.org/grpc" healthpb "google.golang.org/grpc/health/grpc_health_v1" )
זו הפעם הראשונה שמטמיעים את השרת, לכן קודם צריך להגדיר את OpenTelemetry, בדומה למה שעשינו ל-loadgen ולשירותי הלקוח.
step0/src/server/main.go
// step2. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // create a stdout exporter to show collected spans out to stdout. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil } func main() { ... // step2. setup OpenTelemetry tp, err := initTracer() if err != nil { log.Fatalf("failed to initialize TracerProvider: %v", err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step2. end setup ...
בשלב הבא, צריך להוסיף מנטרים של שרתים. בפונקציה main
, מחפשים את המקום שבו grpc.NewServer()
נקראת ומוסיפים לה נתוני מעקב.
step0/src/server/main.go
func main() { ... svc := NewServerService() // step2: add interceptor interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider()) srv := grpc.NewServer( grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)), grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)), ) // step2: end adding interceptor shakesapp.RegisterShakespeareServiceServer(srv, svc) ...
הפעלת המיקרו-שירות ואימות המעקב
לאחר מכן מריצים את הקוד ששונה באמצעות הפקודה skaffold.
skaffold dev
שוב, יוצגו נתונים רבים על span ב-stdout.
פלט הפקודה
... [server] { [server] "Name": "shakesapp.ShakespeareService/GetMatchCount", [server] "SpanContext": { [server] "TraceID": "89b472f213a400cf975e0a0041649667", [server] "SpanID": "96030dbad0061b3f", [server] "TraceFlags": "01", [server] "TraceState": "", [server] "Remote": false [server] }, [server] "Parent": { [server] "TraceID": "89b472f213a400cf975e0a0041649667", [server] "SpanID": "cd90cc3859b73890", [server] "TraceFlags": "01", [server] "TraceState": "", [server] "Remote": true [server] }, [server] "SpanKind": 2, [server] "StartTime": "2022-07-14T14:05:55.74822525Z", [server] "EndTime": "2022-07-14T14:06:03.449258891Z", [server] "Attributes": [ ... [server] ], [server] "Events": [ [server] { [server] "Name": "message", [server] "Attributes": [ ... [server] ], [server] "DroppedAttributeCount": 0, [server] "Time": "2022-07-14T14:05:55.748235489Z" [server] }, [server] { [server] "Name": "message", [server] "Attributes": [ ... [server] ], [server] "DroppedAttributeCount": 0, [server] "Time": "2022-07-14T14:06:03.449255889Z" [server] } [server] ], [server] "Links": null, [server] "Status": { [server] "Code": "Unset", [server] "Description": "" [server] }, [server] "DroppedAttributes": 0, [server] "DroppedEvents": 0, [server] "DroppedLinks": 0, [server] "ChildSpanCount": 0, [server] "Resource": [ [server] { ... [server] ], [server] "InstrumentationLibrary": { [server] "Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", [server] "Version": "semver:0.33.0", [server] "SchemaURL": "" [server] } [server] } ...
שמתם לב שלא הטמעתם שמות של קטעי טקסט, ושיצרתם קטעי טקסט באופן ידני באמצעות trace.Start()
או span.SpanFromContext()
. עם זאת, עדיין מקבלים מספר גדול של קטעי קוד (spans) כי מנטעים של gRPC יצרו אותם.
סיכום
בשלב הזה, צירפתם לקוד רכיבים למדידת תקשורת מבוססת-gRPC עם תמיכה מספריות הסביבה של OpenTelemetry.
השלב הבא
בשלב הבא נציג את המעקב באופן חזותי באמצעות Cloud Trace, ונלמד איך לנתח את ה-spans שנאספו.
6. הצגה חזותית של נתוני המעקב באמצעות Cloud Trace
הוספת מכשירי מעקב במערכת כולה באמצעות OpenTelemetry. עד עכשיו למדתם איך להטמיע שירותי HTTP ו-gRPC. למדתם איך להטמיע את הנתונים, אבל עדיין לא למדתם איך לנתח אותם. בקטע הזה נלמד איך מחליפים את היצוא של stdout ביצוא של Cloud Trace, ואיך לנתח את הטרייסים.
שימוש בייצוא של Cloud Trace
אחד המאפיינים החזקים של OpenTelemetry הוא היכולת להוסיף רכיבים. כדי להציג באופן חזותי את כל ה-spans שנאספו על ידי הכלי למדידת ביצועים, צריך פשוט להחליף את ה-exporter של stdout ב-exporter של Cloud Trace.
פותחים את קובצי main.go
של כל שירות ומחפשים את הפונקציה initTracer()
. מוחקים את השורה ליצירת מעבד ייצוא של stdout ויוצרים מעבד ייצוא של Cloud Trace במקום זאת.
step0/src/loadgen/main.go
import ( ... // step3. add OpenTelemetry for Cloud Trace package cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace" ) // step1. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // step3. replace stdout exporter with Cloud Trace exporter // cloudtrace.New() finds the credentials to Cloud Trace automatically following the // rules defined by golang.org/x/oauth2/google.findDefaultCredentailsWithParams. // https://pkg.go.dev/golang.org/x/oauth2/google#FindDefaultCredentialsWithParams exporter, err := cloudtrace.New() // step3. end replacing exporter if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil }
צריך לערוך את אותה פונקציה גם בשירות הלקוח וגם בשירות השרת.
הפעלת המיקרו-שירות ואימות המעקב
אחרי העריכה, פשוט מריצים את האשכולות כרגיל באמצעות הפקודה skaffold.
skaffold dev
עכשיו לא מוצגים הרבה פרטי span בפורמט של יומנים מובְנים ב-stdout, כי החלפתם את הכלי לייצוא ב-Cloud Trace.
פלט הפקודה
[loadgen] 2022/07/14 15:01:07 simulated 20 requests [loadgen] 2022/07/14 15:01:07 simulating client requests, round 37 [loadgen] 2022/07/14 15:01:14 query 'sweet': matched 958 [client] 2022/07/14 15:01:14 {"match_count":958} [client] 2022/07/14 15:01:14 {"match_count":3040} [loadgen] 2022/07/14 15:01:14 query 'love': matched 3040 [client] 2022/07/14 15:01:15 {"match_count":349} [loadgen] 2022/07/14 15:01:15 query 'hello': matched 349 [client] 2022/07/14 15:01:15 {"match_count":484} [loadgen] 2022/07/14 15:01:15 query 'faith': matched 484 [loadgen] 2022/07/14 15:01:15 query 'insolence': matched 14 [client] 2022/07/14 15:01:15 {"match_count":14} [client] 2022/07/14 15:01:21 {"match_count":484} [loadgen] 2022/07/14 15:01:21 query 'faith': matched 484 [client] 2022/07/14 15:01:21 {"match_count":728} [loadgen] 2022/07/14 15:01:21 query 'world': matched 728 [client] 2022/07/14 15:01:22 {"match_count":484} [loadgen] 2022/07/14 15:01:22 query 'faith': matched 484 [loadgen] 2022/07/14 15:01:22 query 'hello': matched 349 [client] 2022/07/14 15:01:22 {"match_count":349} [client] 2022/07/14 15:01:23 {"match_count":1036} [loadgen] 2022/07/14 15:01:23 query 'friend': matched 1036 [loadgen] 2022/07/14 15:01:28 query 'tear': matched 463 ...
עכשיו נבדוק אם כל ה-spans נשלחים כראוי ל-Cloud Trace. נכנסים למסוף Cloud ועוברים אל Trace list. קל לגשת אליו מתיבת החיפוש. לחלופין, אפשר ללחוץ על התפריט בחלונית הימנית.
לאחר מכן יופיעו הרבה נקודות כחולות בתרשים זמן האחזור. כל נקודה מייצגת נתיב אחד.
לוחצים על אחת מהן כדי לראות את הפרטים בתוך המעקב.
כבר מהסקירה המהירה והפשוטה הזו, אתם יכולים לקבל הרבה תובנות. לדוגמה, בתרשים המפל אפשר לראות שהסיבה לזמן האחזור היא בעיקר ה-span בשם shakesapp.ShakespeareService/GetMatchCount
. (ראו 1 בתמונה שלמעלה). אפשר לאשר זאת בטבלת הסיכום. (העמודה השמאלית ביותר מציגה את משך הזמן של כל טווח). בנוסף, המעקב הזה היה לשאילתה 'friend'. (ראו 2 בתמונה שלמעלה)
אחרי הניתוח הקצר הזה, יכול להיות שתבחינו שאתם צריכים לדעת על מרחבים מפורטים יותר בתוך השיטה GetMatchCount
. בהשוואה למידע ב-stdout, התצוגה החזותית חזקה יותר. מידע נוסף על פרטי Cloud Trace זמין במסמכי התיעוד הרשמיים שלנו.
סיכום
בשלב הזה החלפתם את ה-exporter של stdout ב-Cloud Trace, והצגתם גרפיות של נתוני המעקב ב-Cloud Trace. בנוסף, למדתם איך להתחיל לנתח את הנתונים.
השלב הבא
בשלב הבא, תשנו את קוד המקור של שירות השרת כדי להוסיף span משני ב-GetMatchCount.
7. הוספת span משני לניתוח מדויק יותר
בשלב הקודם, גיליתם שהסיבה לזמן הנסיעה הלוך ושוב שנצפה ב-loadgen היא בעיקר התהליך בתוך השיטה GetMatchCount, הטיפול ב-gRPC, בשירות השרת. עם זאת, מכיוון שלא הטמענו כלים למעקב מלבד הטיפול, לא נוכל למצוא תובנות נוספות מהתרשים מסוג Waterfall. זהו מקרה נפוץ כשמתחילים להטמיע מיקרו-שירותים.
בקטע הזה נשתמש ב-span משני שבו השרת קורא ל-Google Cloud Storage, כי לפעמים תהליך הקלט/פלט של רשת חיצונית נמשך זמן רב, וחשוב לזהות אם הקריאה היא הסיבה לכך.
הוספת מכשיר למדידה של span משני בשרת
פותחים את main.go
בשרת ומאתרים את הפונקציה readFiles
. הפונקציה הזו שולחת בקשה ל-Google Cloud Storage כדי לאחזר את כל קובצי הטקסט של כתבי שייקספיר. בפונקציה הזו אפשר ליצור span משני, כמו שעשיתם לצורך המדידה של שרת ה-HTTP בשירות הלקוח.
step0/src/server/main.go
func readFiles(ctx context.Context, bucketName, prefix string) ([]string, error) { type resp struct { s string err error } // step4: add an extra span span := trace.SpanFromContext(ctx) span.SetName("server.readFiles") span.SetAttributes(attribute.Key("bucketname").String(bucketName)) defer span.End() // step4: end add span ...
זהו, סיימנו להוסיף span חדש. נבדוק את זה על ידי הפעלת האפליקציה.
הפעלת המיקרו-שירות ואימות המעקב
אחרי העריכה, פשוט מריצים את האשכולות כרגיל באמצעות הפקודה skaffold.
skaffold dev
ובוחרים מעקב אחד בשם query.request
מרשימת המעקבים. יוצג תרשים waterfall דומה של המעקב, מלבד span חדש בקטע shakesapp.ShakespeareService/GetMatchCount
. (הקטע שמוקף במלבן האדום בהמשך)
מהתרשים הזה אפשר לראות שהקריאה החיצונית ל-Google Cloud Storage תופסת חלק גדול מהזמן האחזור, אבל עדיין יש גורמים אחרים שתורמים לרוב זמן האחזור.
כבר צברתם הרבה תובנות רק מתוך כמה בדיקות בתרשים המפל של המעקב. איך מקבלים את פרטי הביצועים הנוספים באפליקציה? כאן נכנס לתמונה הכלי למעקב ביצועים, אבל בינתיים נציין שזהו סוף הקודלאב הזה ונשאיר את כל המדריכים בנושא כלי למעקב ביצועים לחלק 2.
סיכום
בשלב הזה, הוספת span נוסף לשירות השרת וקיבלתם תובנות נוספות לגבי זמן האחזור של המערכת.
8. מזל טוב
יצרתם ב-OpenTelemery מעקבים מבוזרים ואימתתם את זמני האחזור של הבקשות במיקרו-השירות ב-Google Cloud Trace.
כדי לתרגל את הנושאים האלה לעומק, תוכלו לנסות את הנושאים הבאים בעצמכם.
- ההטמעה הנוכחית שולחת את כל ה-spans שנוצרו על ידי בדיקת בריאות. (
grpc.health.v1.Health/Check
) איך מסננים את ה-spans האלה מ-Cloud Traces? הרמז מופיע כאן. - יצירת מתאם בין יומני אירועים לבין קטעים (spans) וצפייה באופן הפעולה ב-Google Cloud Trace וב-Google Cloud Logging. הרמז מופיע כאן.
- מחליפים שירות כלשהו בשירות בשפה אחרת ומנסים להטמיע אותו באמצעות OpenTelemetry בשפה הזו.
בנוסף, אם אתם רוצים לקבל מידע נוסף על הכלי לניתוח פרופיל, תוכלו לעבור לחלק 2. במקרה כזה, אפשר לדלג על הקטע 'ניקוי' שבהמשך.
ניקוי
בסיום הקודלאב, חשוב להפסיק את האשכול של Kubernetes ולוודא שמוחקים את הפרויקט כדי שלא תחויבו על חיובים לא צפויים ב-Google Kubernetes Engine, ב-Google Cloud Trace וב-Google Artifact Registry.
קודם צריך למחוק את האשכולות. אם אתם מפעילים את האשכולות באמצעות skaffold dev
, פשוט מקישים על Ctrl-C. אם אתם מריצים את האשכולות באמצעות skaffold run
, מריצים את הפקודה הבאה:
skaffold delete
פלט הפקודה
Cleaning up... - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
אחרי שמוחקים את האשכולות, בוחרים באפשרות 'IAM ואדמין' > 'הגדרות' בחלונית התפריט, ואז לוחצים על הלחצן 'השבתה'.
לאחר מכן מזינים את מזהה הפרויקט (ולא את שם הפרויקט) בטופס בתיבת הדו-שיח ומאשרים את ההשבתה.