1. מבוא
עדכון אחרון:15 ביולי 2022
ניראות (observability) של האפליקציה
ניראות (observability) ו-OpenTelemetry
ניראות (observability) היא המונח שמשמש לתיאור תכונה של מערכת. מערכת עם ניראות (observability) מאפשרת לצוותים לנפות באגים במערכת באופן פעיל. בהקשר הזה, שלושה עמודי תווך של ניראות (observability); יומנים, מדדים ועקבות הם הכלים הבסיסיים שבאמצעותם המערכת יכולה להשיג ניראות (observability).
OpenTelemetry היא קבוצה של מפרטים, ספריות וסוכנים שמאיצים את העיבוד והייצוא של נתוני טלמטריה (יומנים, מדדים ועקבות) שנדרשים לצורך ניראות (observability). OpenTelemetry הוא פרויקט פתוח בתקן פתוח ומבוסס-קהילה במסגרת CNCF. על ידי שימוש בספריות שהפרויקט והסביבה העסקית שלו מספקים, המפתחים יכולים להטמיע את האפליקציות שלהם באופן ניטרלי של הספקים ומול מספר ארכיטקטורות.
בנוסף לשלושת עמודי התווך של הניראות (observability), הפרופיילינג הרציף הוא רכיב מרכזי נוסף בניראות, והוא מרחיב את בסיס המשתמשים בתחום. הכלי Cloud Profiler הוא אחד מהגורמים שיצרו, והוא מספק ממשק פשוט שבו אפשר להציג פירוט של מדדי הביצועים במקבצי הקריאות של האפליקציות.
ה-Codelab הזה הוא חלק ראשון בסדרה והוא עוסק ביצירת מעקבים מבוזרים במיקרו-שירותים (microservices) באמצעות OpenTelemetry ו-Cloud Trace. חלק 2 יעסוק ביצירת פרופילים רציפים באמצעות Cloud Profiler.
מעקב שהופץ
בין היומנים, המדדים והמעקבים, נתוני המעקב הם נתוני הטלמטריה שמציינת את זמן האחזור של חלק ספציפי בתהליך. במיוחד בעידן מיקרו-שירותים, מעקב מבוזר הוא הגורם המשמעותי ביותר למציאת צווארי בקבוק בזמן האחזור במערכת המבוזרת הכוללת.
כשמנתחים מעקבים מבוזרים, התצוגה החזותית של נתוני המעקב היא המפתח להבנת זמן האחזור הכולל של המערכת במבט מהיר. במעקב מבוזר, אנחנו מטפלים בקבוצה של קריאות כדי לעבד בקשה יחידה לנקודת הכניסה למערכת בצורה של נתוני מעקב, שמכילים כמה קריאות.
טווח מייצג יחידת עבודה בודדת המבוצעת במערכת מבוזרת, והקלטה של זמני התחלה וסיום. לעיתים קרובות יש יחסים היררכיים ביניהם – בתמונה שמתחת לכל ההיקפים הקטנים יותר יש אזורים צאצאים של טווח /הודעות גדול, והם מורכבים מ-Trace להצגת נתיב העבודה במערכת.
הכלי Google Cloud Trace הוא אחת מהאפשרויות לקצה העורפי של מעקב מבוזר, והוא משתלב היטב עם מוצרים אחרים ב-Google Cloud.
מה תפַתחו
ב-Codelab הזה, אתם תעקבו אחרי נתוני מעקב בשירותים שנקראים 'אפליקציית שייקספיר' (שנקראת גם Shakesapp) שרצה על אשכול של Google Kubernetes Engine. הארכיטקטורה של שייקסאפ מתוארת בהמשך:
- Loadgen שולח מחרוזת שאילתה ללקוח ב-HTTP
- הלקוחות מעבירים את השאילתה מיוצר העומסים לשרת ב-gRPC
- השרת מקבל את השאילתה מהלקוח, מאחזר את כל העבודות בפורמט טקסט מ-Google Cloud Storage, מחפש בשורות שמכילות את השאילתה ומחזיר את מספר השורה שהתאימה ללקוח
את פרטי המעקב של הבקשה תמלאו. לאחר מכן, תטמיעו סוכן Profiler בשרת ותבדקו את צוואר הבקבוק.
מה תלמדו
- איך מתחילים לעבוד עם ספריות OpenTelemetry Trace בפרויקט Go
- איך ליצור span באמצעות הספרייה
- איך להפיץ הקשרי טווח לאורך החוט בין רכיבי אפליקציה
- איך לשלוח נתוני מעקב ל-Cloud Trace
- איך לנתח את המעקב ב-Cloud Trace
בשיעור הזה תקבלו הסבר על האופן שבו ניתן להגדיר מיקרו-שירותים (microservices). כדי לפשט את ההסברים, הדוגמה הזו מכילה רק 3 רכיבים (מחולל עומסים, לקוח ושרת), אבל אפשר להחיל את אותו התהליך שמוסבר במאמר הזה גם על מערכות מורכבות וגדולות יותר.
מה צריך להכין
- ידע בסיסי ב-Go
- ידע בסיסי ב-Kubernetes
2. הגדרה ודרישות
הגדרת סביבה בקצב עצמאי
אם אין לכם עדיין חשבון Google (Gmail או Google Apps), עליכם ליצור חשבון. נכנסים למסוף Google Cloud Platform ( console.cloud.google.com) ויוצרים פרויקט חדש.
אם כבר יש לכם פרויקט, לוחצים על התפריט הנפתח לבחירת פרויקט בפינה השמאלית העליונה של המסוף:
ולוחצים על 'New project' (פרויקט חדש). בתיבת הדו-שיח שמתקבלת כדי ליצור פרויקט חדש:
אם עדיין אין לכם פרויקט, אמורה להופיע תיבת דו-שיח כזו כדי ליצור את הפרויקט הראשון:
בתיבת הדו-שיח הבאה ליצירת פרויקט תוכלו להזין את פרטי הפרויקט החדש:
חשוב לזכור את מזהה הפרויקט, שהוא שם ייחודי בכל הפרויקטים ב-Google Cloud (השם שלמעלה כבר תפוס ולא מתאים לכם, סליחה). בהמשך ב-Codelab הזה, המערכת תתייחס אליה בתור PROJECT_ID.
בשלב הבא, אם עדיין לא עשית זאת, יהיה עליך להפעיל חיוב ב-Developers Console כדי להשתמש במשאבים של Google Cloud ולהפעיל את Cloud Trace API.
ההרצה של Codelab הזה לא אמורה לעלות לך יותר מכמה דולרים, אבל זה יכול להיות גבוה יותר אם תחליטו להשתמש ביותר משאבים או אם תשאירו אותם פועלים (עיינו בקטע 'ניקוי' בסוף המסמך). המחירים של Google Cloud Trace, Google Kubernetes Engine ו-Google Artifact Registry מפורטים במסמכי התיעוד הרשמיים.
- תמחור חבילת התפעול של Google Cloud | חבילת התפעול
- תמחור | מסמכי תיעוד של Kubernetes Engine
- תמחור של Artifact Registry | חומרי עזר של 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 (ההקצאה וההתחברות של הסביבה אמורות להימשך כמה דקות).
אחרי ההתחברות ל-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
אפשר לבחור מגוון אזורים שונים. מידע נוסף זמין במאמר אזורים ו אזורים.
מעבר להגדרת שפה
ב-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 הזה הוא כך:
- הורדת פרויקט הבסיס אל Cloud Shell
- יצירת מיקרו-שירותים (microservices) בקונטיינרים
- העלאת קונטיינרים ל-Google Artifact Registry (GAR)
- פריסת קונטיינרים ב-GKE
- שינוי קוד המקור של השירותים עבור אינסטרומנטציה למעקב
- מעבר לשלב 2
הפעלת Kubernetes Engine
בשלב הראשון אנחנו מגדירים אשכול Kubernetes שבו Shakesapp פועל ב-GKE, ולכן עלינו להפעיל את GKE. עוברים לתפריט 'Kubernetes Engine'. ולוחצים על לחצן ההפעלה.
עכשיו אתם מוכנים ליצור אשכול 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 ולוחצים על לחצן ההפעלה.
אחרי כמה רגעים תראו את דפדפן המאגר של GAR. לוחצים על "CREATE REPOSITORY". ותזין את שם המאגר.
ב-Codelab הזה, אקרא למאגר החדש trace-codelab
. הפורמט של פריט המידע שנוצר בתהליך הפיתוח (Artifact) הוא 'Docker' וסוג המיקום הוא Region (אזור). בוחרים את האזור שקרוב לאזור שהגדרתם לאזור ברירת המחדל של Google Compute Engine. לדוגמה, בדוגמה הזו בחרו 'us-central1-f' למעלה, אז כאן אנחנו בוחרים את 'us-central1 (איווה)'. לאחר מכן לוחצים על 'יצירה' לחצן.
עכשיו מופיעה האפשרות 'trace-codelab' בדפדפן של המאגר.
נחזור לכאן מאוחר יותר כדי לבדוק את נתיב הרישום.
הגדרת Skaffold
Skaffold הוא כלי שימושי לפיתוח מיקרו-שירותים (microservices) שרצים ב-Kubernetes. הוא מטפל בתהליך העבודה של פיתוח, דחיפה ופריסה של קונטיינרים של אפליקציות עם קבוצת פקודות קטנה. כברירת מחדל, Skaffold משתמש ב-Docker Registry כרישום קונטיינרים, לכן צריך להגדיר את skaffold כך שיזהה GAR בדחיפת קונטיינרים אליהם.
פותחים שוב את Cloud Shell ובודקים אם skaffold מותקן. (Cloud Shell מתקינה את skaffold בסביבה כברירת מחדל). מריצים את הפקודה הבאה ובודקים את הגרסה של skaffold.
skaffold version
פלט הפקודה
v1.38.0
עכשיו אפשר לרשום את מאגר ברירת המחדל לשימוש ב-skaffold. כדי למצוא את נתיב הרישום, עוברים למרכז הבקרה של Artifact Registry ולוחצים על שם המאגר שהגדרתם בשלב הקודם.
לאחר מכן יופיעו נתיבי ניווט בחלק העליון של הדף. לוחצים על סמל כדי להעתיק את נתיב הרישום ללוח.
כשלוחצים על לחצן ההעתקה, תוצג תיבת דו-שיח בחלק התחתון של הדפדפן עם הודעה כמו:
"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 שבו רצים מיקרו-שירותים של Codelab
הנושא הבא
בשלב הבא תפתחו, תדחוףו ותפרסו את המיקרו-שירותים (microservices) שלכם באשכול
3. פיתוח, דחיפה ופריסה של מיקרו-שירותים (microservices)
הורדת החומר של ה-Codelab
בשלב הקודם, הגדרנו את כל הדרישות המוקדמות ל-Codelab הזה. עכשיו אתם מוכנים להריץ עליהם מיקרו-שירותים שלמים. חומרי ה-Codelab מתארחים ב-GitHub, לכן צריך להוריד אותם לסביבת Cloud Shell באמצעות פקודת ה-Git הבאה.
cd ~ git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git cd opentelemetry-trace-codelab-go
מבנה הספריות של הפרויקט הוא:
. ├── README.md ├── step0 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step1 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step2 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step3 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step4 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step5 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src └── step6 ├── manifests ├── proto ├── skaffold.yaml └── src
- מניפסטים: קובצי מניפסט של Kubernetes
- proto: הגדרת אב לתקשורת בין לקוח לשרת
- src: ספריות לקוד המקור של כל שירות
- skaffold.yaml: קובץ התצורה של skaffold
ב-Codelab הזה, צריך לעדכן את קוד המקור שנמצא בתיקייה step0
. אפשר גם לעיין בקוד המקור ב-step[1-6]
תיקיות כדי לקבל את התשובות בשלבים הבאים. (חלק 1 מכסה את שלב 0 עד שלב 4, וחלק 2 מכסה את שלבים 5 ו-6)
הרצת פקודת skaffold
בסוף אתם מוכנים לפתח, לדחוף ולפרוס תוכן שלם באשכול Kubernetes שיצרתם. זה נשמע כאילו הסרטון כולל כמה שלבים, אבל המודל עצמו עושה את כל העבודה בשבילך. ננסה לפעול באמצעות הפקודה הבאה:
cd step0 skaffold dev
מיד לאחר הרצת הפקודה, פלט היומן של docker build
יופיע ואפשר לאשר שהם נדחפו בהצלחה למרשם.
פלט הפקודה
... ---> Running in c39b3ea8692b ---> 90932a583ab6 Successfully built 90932a583ab6 Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1 The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice] cc8f5a05df4a: Preparing 5bf719419ee2: Preparing 2901929ad341: Preparing 88d9943798ba: Preparing b0fdf826a39a: Preparing 3c9c1e0b1647: Preparing f3427ce9393d: Preparing 14a1ca976738: Preparing f3427ce9393d: Waiting 14a1ca976738: Waiting 3c9c1e0b1647: Waiting b0fdf826a39a: Layer already exists 88d9943798ba: Layer already exists f3427ce9393d: Layer already exists 3c9c1e0b1647: Layer already exists 14a1ca976738: Layer already exists 2901929ad341: Pushed 5bf719419ee2: Pushed cc8f5a05df4a: Pushed step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001
אחרי דחיפת כל קונטיינרים של השירות, הפריסות של Kubernetes מתחילות באופן אוטומטי.
פלט הפקודה
sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997 Tags used in deployment: - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a Starting deploy... - deployment.apps/clientservice created - service/clientservice created - deployment.apps/loadgen created - deployment.apps/serverservice created - service/serverservice created
לאחר הפריסה, יומני האפליקציות שהתקבלו ל-stdout יופיעו בכל קונטיינרים בצורה הבאה:
פלט הפקודה
[client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:16 {"match_count":3040} [loadgen] 2022/07/14 06:33:16 query 'love': matched 3040 [client] 2022/07/14 06:33:19 {"match_count":463} [loadgen] 2022/07/14 06:33:19 query 'tear': matched 463 [loadgen] 2022/07/14 06:33:20 query 'world': matched 728 [client] 2022/07/14 06:33:20 {"match_count":728} [client] 2022/07/14 06:33:22 {"match_count":463} [loadgen] 2022/07/14 06:33:22 query 'tear': matched 463
שים לב שבשלב הזה רוצים לראות את כל ההודעות מהשרת. טוב, עכשיו הכול מוכן ואפשר להתחיל להוסיף לאפליקציה שלך אינסטרומנטציה באמצעות OpenTelemetry לצורך מעקב מבוזר של השירותים.
לפני התחלת האינסטרומנטציה בשירות, יש לכבות את האשכול באמצעות Ctrl-C.
פלט הפקודה
... [client] 2022/07/14 06:34:57 {"match_count":1} [loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1 ^CCleaning up... - W0714 06:34:58.464305 28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead. - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
סיכום
בשלב הזה הכנתם את חומר ה-Codelab בסביבה שלכם ואישרתם הפעלות של skaffold כמצופה.
הנושא הבא
בשלב הבא, תשנה את קוד המקור של שירות עומסים כדי להגדיר את פרטי המעקב.
4. אינסטרומנטציה ל-HTTP
מושג אינסטרומנטציה והפצה של מעקב
לפני עריכת קוד המקור, אסביר בקצרה איך פועלים מעקבים מבוזרים בתרשים פשוט.
בדוגמה הזו, אנחנו מגדירים את הקוד כדי לייצא מידע של Trace ו-Span ל-Cloud Trace, ולהפיץ את הקשר המעקב בין הבקשה משירות העומס אל שירות השרת.
האפליקציות צריכות לשלוח מטא-נתונים של Trace, כמו מזהה מעקב ו-Span ID, כדי ש-Cloud Trace ירכיב את כל החלקים עם אותו מזהה מעקב למעקב אחד. בנוסף, האפליקציה צריכה להפיץ הקשרי מעקב (השילוב של מזהה מעקב ומזהה Span של span ההורה) בבקשת שירותי downstream, כדי שהם יוכלו לדעת באיזה הקשר מעקב הם מטפלים.
OpenTelemetry עוזרת לכם:
- כדי ליצור מזהה מעקב ומזהה SPAN ייחודיים
- כדי לייצא מזהה Trace ו-Span ID לקצה העורפי
- להפיץ הקשרי מעקב לשירותים אחרים
- להטמיע מטא-נתונים נוספים שעוזרים לנתח נתוני מעקב
רכיבים ב-OpenTelemetry Trace
התהליך של מעקב אחר אפליקציות באמצעות OpenTelemetry הוא כך:
- יצירת יצואן
- יוצרים קישור ל-TracerProvider של המייצא ב-1 ומגדירים אותו גלובלי.
- מגדירים את TextMapPropagaror כדי להגדיר את שיטת ההפצה
- קבלו את ה-Tracer מ-TracerProvider
- יצירת טווח מה-Tracer
נכון לעכשיו, אין צורך להבין את המאפיינים המפורטים של כל רכיב, אבל הדבר שהכי חשוב לזכור הוא:
- אפשר לחבר את היצואן כאן ל-TracerProvider
- ב-TracerProvider יש את כל ההגדרות שקשורות לדגימה ולייצוא של מעקב
- כל נתוני המעקב מקובצים באובייקט Tracer
אחרי שנבין את זה, נעבור לעבודת התכנות בפועל.
היגוי הראשון של הכלי
שירות מחולל עומסים למכשירים
לוחצים על הלחצן בפינה הימנית העליונה של Cloud Shell כדי לפתוח את Cloud Shell Editor. פותחים את
step0/src/loadgen/main.go
דרך ה-Explorer בחלונית הימנית ומוצאים את הפונקציה הראשית.
step0/src/loadgen/main.go
func main() { ... for range t.C { log.Printf("simulating client requests, round %d", i) if err := run(numWorkers, numConcurrency); err != nil { log.Printf("aborted round with error: %v", err) } log.Printf("simulated %d requests", numWorkers) if numRounds != 0 && i > numRounds { break } i++ } }
בפונקציה הראשית, מופיעה הלולאה שקוראת לפונקציה run
בתוכה. בהטמעה הנוכחית, הקטע כולל 2 שורות יומן שמתעדות את ההתחלה והסיום של הקריאה לפונקציה. עכשיו נשתמש במידע על Span כדי לעקוב אחרי זמן האחזור של הפעלת הפונקציה.
קודם כל, כמו שצוין בקטע הקודם, נגדיר את כל התצורות של OpenTelemetry. מוסיפים חבילות OpenTelemetry באופן הבא:
step0/src/loadgen/main.go
import ( "context" // step1. add packages "encoding/json" "fmt" "io" "log" "math/rand" "net/http" "net/url" "time" // step1. add packages "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "go.opentelemetry.io/otel/trace" // step1. end add packages )
כדי להיות קריאים, אנחנו יוצרים פונקציית הגדרה שנקראת initTracer
ונותנים לה את הפונקציה main
.
step0/src/loadgen/main.go
// step1. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // create a stdout exporter to show collected spans out to stdout. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil }
יכול להיות שתבחינו שהתהליך להגדרת OpenTelemetry מתואר בחלק הקודם. בהטמעה הזו, אנחנו משתמשים ביצואן stdout
שמייצא את כל נתוני המעקב ל-stdout בפורמט מובנה.
ואז קוראים לו מהפונקציה הראשית. קוראים ל-initTracer()
ומקפידים לקרוא ל-TracerProvider.Shutdown()
לאחר סגירת האפליקציה.
step0/src/loadgen/main.go
func main() { // step1. setup OpenTelemetry tp, err := initTracer() if err != nil { log.Fatalf("failed to initialize TracerProvider: %v", err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step1. end setup log.Printf("starting worder with %d workers in %d concurrency", numWorkers, numConcurrency) log.Printf("number of rounds: %d (0 is inifinite)", numRounds) ...
בסיום ההגדרה, צריך ליצור Span עם מזהה Trace ו-Span ID ייחודיים. OpenTelemetry מספק ספרייה שימושית. להוסיף חבילות חדשות נוספות ללקוח ה-HTTP של האמצעי.
step0/src/loadgen/main.go
import ( "context" "encoding/json" "fmt" "io" "log" "math/rand" "net/http" "net/http/httptrace" // step1. add packages "net/url" "time" // step1. add packages "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" // step1. end add packages "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "go.opentelemetry.io/otel/trace" )
מכיוון שמחולל העומסים קורא לשירות לקוח ב-HTTP עם net/http
בפונקציה runQuery
, אנחנו משתמשים בחבילת התרומה של net/http
ומפעילים אינסטרומנטציה עם התוסף httptrace
ו-otelhttp
.
קודם כל, מוסיפים משתנה גלובלי של חבילה מסוג httpClient כדי לקרוא לבקשות HTTP דרך הלקוח עם האינסטרומנטציה.
step0/src/loadgen/main.go
var httpClient = http.Client{ Transport: otelhttp.NewTransport(http.DefaultTransport) }
בשלב הבא מוסיפים אינסטרומנטציה לפונקציה runQuery
כדי ליצור את ה-span המותאם אישית באמצעות OpenTelemetry, ואת ה-span שנוצר באופן אוטומטי מלקוח ה-HTTP בהתאמה אישית. מה עושים:
- רוצה לקבל Tracer מ-
TracerProvider
בכל העולם באמצעותotel.Tracer()
? - יצירת טווח ברמה הבסיסית (root) באמצעות השיטה
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) }
סיימתם עם האינסטרומנטציה ביוצר טעינה (אפליקציית לקוח HTTP). חשוב לעדכן את go.mod
ואת go.sum
בפקודה go mod
.
go mod tidy
שירות לקוח לכלים
בקטע הקודם, הטמענו את החלק שמוקף במלבן האדום בשרטוט למטה. השתמשנו בנתוני span בשירות מחולל עומסים. בדומה לשירות מחולל העומסים, עכשיו אנחנו צריכים לבצע אינסטלציה לשירות הלקוח. ההבדל משירות מחולל העומסים הוא ששירות הלקוח צריך לחלץ את פרטי מזהה המעקב המופצים משירות מחולל העומסים בכותרת ה-HTTP ולהשתמש במזהה כדי ליצור Spans.
פותחים את Cloud Shell Editor ומוסיפים את החבילות הנדרשות כמו שעשינו לשירות מחולל העומסים.
step0/src/client/main.go
import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "os" "time" "opentelemetry-trace-codelab-go/client/shakesapp" // step1. add new import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" // step1. end new import )
שוב, אנחנו צריכים להגדיר את OpenTelemtry. פשוט מעתיקים ומדביקים את הפונקציה initTracer
מכלי הטעינה ומפעילים אותה גם בפונקציה main
של שירות הלקוח.
step0/src/client/main.go
// step1. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // create a stdout exporter to show collected spans out to stdout. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil }
עכשיו הגיע הזמן לנגן על מקטעים. מכיוון ששירות הלקוח צריך לקבל בקשות HTTP משירות טעינה, הוא צריך להגדיר אינסטרומנטל ל-handler. שרת ה-HTTP בשירות הלקוח מיושם עם net/http, ותוכלו להשתמש בחבילת otelhttp
כמו שנעשה ב-Loadgen.
קודם כול, אנחנו מחליפים את הרישום של ה-handler ב-handler של otelhttp
. בפונקציה main
, מחפשים את הקווים שבהם ה-handler של HTTP רשום ב-http.HandleFunc()
.
step0/src/client/main.go
// step1. change handler to intercept OpenTelemetry related headers // http.HandleFunc("/", svc.handler) otelHandler := otelhttp.NewHandler(http.HandlerFunc(svc.handler), "client.handler") http.Handle("/", otelHandler) // step1. end intercepter setting http.HandleFunc("/_genki", svc.health)
לאחר מכן אנחנו מגדירים את הטווח בפועל בתוך ה-handler. מוצאים את handler() של Func (*clientService) ומוסיפים אינסטרומנטציה ל-span באמצעות trace.SpanFromContext()
.
step0/src/client/main.go
func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) { ... ctx := r.Context() ctx, cancel := context.WithCancel(ctx) defer cancel() // step1. instrument trace span := trace.SpanFromContext(ctx) defer span.End() // step1. end instrument ...
עם האינסטרומנטציה הזו, מקבלים את הטווחים מתחילת השיטה handler
ועד לסוף שלה. כדי שיהיה קל לנתח את המרווחים, מוסיפים עוד מאפיין שמאחסן את המספר התואם לשאילתה. ממש לפני שורת היומן, מוסיפים את הקוד הבא.
func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) { ... // step1. add span specific attribute span.SetAttributes(attribute.Key("matched").Int64(resp.MatchCount)) // step1. end adding attribute log.Println(string(ret)) ...
עם כל האינסטרומנטציות שלמעלה, השלמתם את אינסטרומנטציית המעקב בין כלי טעינה ולקוח. בואו נראה איך זה עובד. מריצים את הקוד שוב באמצעות skaffold.
skaffold dev
לאחר זמן מה הרצת השירותים באשכול GKE, תופיע כמות עצומה של הודעות יומן, כמו בדוגמה הבאה:
פלט הפקודה
[loadgen] { [loadgen] "Name": "query.request", [loadgen] "SpanContext": { [loadgen] "TraceID": "cfa22247a542beeb55a3434392d46b89", [loadgen] "SpanID": "18b06404b10c418b", [loadgen] "TraceFlags": "01", [loadgen] "TraceState": "", [loadgen] "Remote": false [loadgen] }, [loadgen] "Parent": { [loadgen] "TraceID": "00000000000000000000000000000000", [loadgen] "SpanID": "0000000000000000", [loadgen] "TraceFlags": "00", [loadgen] "TraceState": "", [loadgen] "Remote": false [loadgen] }, [loadgen] "SpanKind": 1, [loadgen] "StartTime": "2022-07-14T13:13:36.686751087Z", [loadgen] "EndTime": "2022-07-14T13:14:31.849601964Z", [loadgen] "Attributes": [ [loadgen] { [loadgen] "Key": "telemetry.sdk.language", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "go" [loadgen] } [loadgen] }, [loadgen] { [loadgen] "Key": "service.name", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "loadgen.runQuery" [loadgen] } [loadgen] }, [loadgen] { [loadgen] "Key": "query", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "faith" [loadgen] } [loadgen] } [loadgen] ], [loadgen] "Events": null, [loadgen] "Links": null, [loadgen] "Status": { [loadgen] "Code": "Unset", [loadgen] "Description": "" [loadgen] }, [loadgen] "DroppedAttributes": 0, [loadgen] "DroppedEvents": 0, [loadgen] "DroppedLinks": 0, [loadgen] "ChildSpanCount": 5, [loadgen] "Resource": [ [loadgen] { [loadgen] "Key": "service.name", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "unknown_service:loadgen" ...
היצואן stdout
פולט את ההודעות האלה. תוכלו לראות שלהורים של כל שכבות הטעינה יש TraceID: 00000000000000000000000000000000
, כי זהו ה-root [הקצה של הרמה], כלומר ה-span הראשון במעקב. אפשר גם לגלות שמאפיין ההטמעה "query"
כולל את מחרוזת השאילתה שמועברת לשירות הלקוח.
סיכום
בשלב הזה הגדרתם אינסטרומנטציה של שירות מחולל עומסים ושירות לקוח שמתקשרים ב-HTTP, ואישרתם שאתם יכולים להפיץ בהצלחה הקשר של מעקב בין השירותים ולייצא מידע מ-Span משני השירותים ל-stdout.
הנושא הבא
בשלב הבא, תגדירו לשירות הלקוח ולשירות השרת כדי לאשר את אופן ההפצה של Trace Context דרך gRPC.
5. אינסטרומנטציה ל-gRPC
בשלב הקודם, שייכנו את המחצית הראשונה של הבקשה במיקרו-שירותים (microservices) הזה. בשלב הזה, ננסה ליצור אלמנטים של תקשורת gRPC בין שירות הלקוח לבין שירות השרת. (מלבן ירוק וסגול בתמונה למטה)
אינסטרומנטציה לפני פיתוח (pre-build) עבור לקוח gRPC
בסביבה העסקית של OpenTelemetry יש הרבה ספריות שימושיות שעוזרות למפתחים לשלב אפליקציות במכשירים. בשלב הקודם, השתמשנו באינסטרומנטציה של בנייה מראש עבור חבילה net/http
. בשלב הזה, כשאנחנו מנסים להפיץ הקשר של מעקב דרך gRPC, אנחנו משתמשים בספרייה עבורו.
קודם כול צריך לייבא את החבילה המוכנה מראש של gRPC בשם otelgrpc
.
step0/src/client/main.go
import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "os" "time" "opentelemetry-trace-codelab-go/client/shakesapp" // step2. add prebuilt gRPC package (otelgrpc) "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" )
הפעם, שירות הלקוח הוא לקוח gRPC מול שירות השרת, ולכן צריך לבצע אינסטרומנטציית לקוח gRPC. צריך למצוא את הפונקציה mustConnGRPC
ולהוסיף מיירטים של gRPC שמחברים ל-spans חדשים בכל פעם שהלקוח שולח בקשות לשרת.
step0/src/client/main.go
// Helper function for gRPC connections: Dial and create client once, reuse. func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { var err error // step2. add gRPC interceptor interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider()) *conn, err = grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(interceptorOpt)), grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(interceptorOpt)), grpc.WithTimeout(time.Second*3), ) // step2: end adding interceptor if err != nil { panic(fmt.Sprintf("Error %s grpc: failed to connect %s", err, addr)) } }
מכיוון שכבר הגדרת את OpenTelemetry בקטע הקודם, אין צורך לעשות זאת.
אינסטרומנטציה מוגדרת מראש לשרת gRPC
בדומה למה שעשינו בשביל לקוח gRPC, אנחנו קוראים לאינסטרומנטציה מוכנה מראש של שרת gRPC. מוסיפים חבילה חדשה לקטע הייבוא, כמו:
step0/src/server/main.go
import ( "context" "fmt" "io/ioutil" "log" "net" "os" "regexp" "strings" "opentelemetry-trace-codelab-go/server/shakesapp" "cloud.google.com/go/storage" // step2. add OpenTelemetry packages including otelgrpc "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "google.golang.org/api/iterator" "google.golang.org/api/option" "google.golang.org/grpc" healthpb "google.golang.org/grpc/health/grpc_health_v1" )
זו הפעם הראשונה שזוהי הפעם הראשונה שאתם משתמשים בשרת, לכן קודם צריך להגדיר את OpenTelemetry, באופן דומה למה שעשינו בתחום של כלי טעינה ושירותי לקוח.
step0/src/server/main.go
// step2. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // create a stdout exporter to show collected spans out to stdout. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil } func main() { ... // step2. setup OpenTelemetry tp, err := initTracer() if err != nil { log.Fatalf("failed to initialize TracerProvider: %v", err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step2. end setup ...
בשלב הבא צריך להוסיף מיירטי שרתים. בפונקציה main
, מחפשים את המקום שבו קוראים לפונקציה grpc.NewServer()
ומוסיפים אליה מיירטים.
step0/src/server/main.go
func main() { ... svc := NewServerService() // step2: add interceptor interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider()) srv := grpc.NewServer( grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)), grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)), ) // step2: end adding interceptor shakesapp.RegisterShakespeareServiceServer(srv, svc) ...
מפעילים את המיקרו-שירות (microservice) ומאשרים את המעקב
לאחר מכן מריצים את הקוד שעבר שינוי באמצעות פקודת skaffold.
skaffold dev
עכשיו שוב מוצג מידע על span ב-stdout.
פלט הפקודה
... [server] { [server] "Name": "shakesapp.ShakespeareService/GetMatchCount", [server] "SpanContext": { [server] "TraceID": "89b472f213a400cf975e0a0041649667", [server] "SpanID": "96030dbad0061b3f", [server] "TraceFlags": "01", [server] "TraceState": "", [server] "Remote": false [server] }, [server] "Parent": { [server] "TraceID": "89b472f213a400cf975e0a0041649667", [server] "SpanID": "cd90cc3859b73890", [server] "TraceFlags": "01", [server] "TraceState": "", [server] "Remote": true [server] }, [server] "SpanKind": 2, [server] "StartTime": "2022-07-14T14:05:55.74822525Z", [server] "EndTime": "2022-07-14T14:06:03.449258891Z", [server] "Attributes": [ ... [server] ], [server] "Events": [ [server] { [server] "Name": "message", [server] "Attributes": [ ... [server] ], [server] "DroppedAttributeCount": 0, [server] "Time": "2022-07-14T14:05:55.748235489Z" [server] }, [server] { [server] "Name": "message", [server] "Attributes": [ ... [server] ], [server] "DroppedAttributeCount": 0, [server] "Time": "2022-07-14T14:06:03.449255889Z" [server] } [server] ], [server] "Links": null, [server] "Status": { [server] "Code": "Unset", [server] "Description": "" [server] }, [server] "DroppedAttributes": 0, [server] "DroppedEvents": 0, [server] "DroppedLinks": 0, [server] "ChildSpanCount": 0, [server] "Resource": [ [server] { ... [server] ], [server] "InstrumentationLibrary": { [server] "Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", [server] "Version": "semver:0.33.0", [server] "SchemaURL": "" [server] } [server] } ...
שמתם לב שלא הטמעתם שמות span ולכן יצרתם גבולות באופן ידני באמצעות trace.Start()
או span.SpanFromContext()
. בכל זאת, מקבלים מספר גדול של רכיבי span כי המיירטים של gRPC יצרו אותם.
סיכום
בשלב הזה בוצעה אינסטרומנטציה של תקשורת שמבוססת על gRPC עם התמיכה מספריות הסביבה של OpenTelemetry.
הנושא הבא
בשלב הבא, תוכלו לראות באופן חזותי את המעקב באמצעות Cloud Trace וללמוד איך לנתח את הטווחים שנאספו.
6. הצגה חזותית של מעקב באמצעות Cloud Trace
יש לכם מעקבים אינסטרומנטליים בכל המערכת באמצעות OpenTelemetry. עד עכשיו למדתם איך לבצע אינסטרומנטציה לשירותי HTTP ו-gRPC. למרות שלמדתם איך לנגן בהם, עדיין לא למדתם איך לנתח אותם. בקטע הזה מחליפים את יצואנים של stdout ביצואנים של Cloud Trace, ולומדים איך לנתח את העקבות.
שימוש ב-Cloud Trace Exporter
אחד המאפיינים העוצמתיים של OpenTelemetry הוא יכולת החיבור. כדי להמחיש את כל טווחי הנתונים שנאספו על ידי האינסטרומנט שלכם, צריך רק להחליף את יצואן stdout ביצואן Cloud Trace.
פותחים main.go
קבצים בכל אחד מהשירותים ומאתרים את הפונקציה initTracer()
. מוחקים את השורה כדי ליצור יצואן stdout וליצור במקום זאת יצואן Cloud Trace.
step0/src/loadgen/main.go
import ( ... // step3. add OpenTelemetry for Cloud Trace package cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace" ) // step1. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // step3. replace stdout exporter with Cloud Trace exporter // cloudtrace.New() finds the credentials to Cloud Trace automatically following the // rules defined by golang.org/x/oauth2/google.findDefaultCredentailsWithParams. // https://pkg.go.dev/golang.org/x/oauth2/google#FindDefaultCredentialsWithParams exporter, err := cloudtrace.New() // step3. end replacing exporter if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil }
צריך לערוך את אותה פונקציה גם בשירות הלקוח ובשירות השרת.
מפעילים את המיקרו-שירות (microservice) ומאשרים את המעקב
אחרי העריכה, מריצים את האשכול כרגיל באמצעות פקודת skaffold.
skaffold dev
עכשיו לא רואים הרבה מידע על טווח תאריכים בפורמט של יומנים מובנים ב-stdout, כי החלפתם את יצואן ב-Cloud Trace.
פלט הפקודה
[loadgen] 2022/07/14 15:01:07 simulated 20 requests [loadgen] 2022/07/14 15:01:07 simulating client requests, round 37 [loadgen] 2022/07/14 15:01:14 query 'sweet': matched 958 [client] 2022/07/14 15:01:14 {"match_count":958} [client] 2022/07/14 15:01:14 {"match_count":3040} [loadgen] 2022/07/14 15:01:14 query 'love': matched 3040 [client] 2022/07/14 15:01:15 {"match_count":349} [loadgen] 2022/07/14 15:01:15 query 'hello': matched 349 [client] 2022/07/14 15:01:15 {"match_count":484} [loadgen] 2022/07/14 15:01:15 query 'faith': matched 484 [loadgen] 2022/07/14 15:01:15 query 'insolence': matched 14 [client] 2022/07/14 15:01:15 {"match_count":14} [client] 2022/07/14 15:01:21 {"match_count":484} [loadgen] 2022/07/14 15:01:21 query 'faith': matched 484 [client] 2022/07/14 15:01:21 {"match_count":728} [loadgen] 2022/07/14 15:01:21 query 'world': matched 728 [client] 2022/07/14 15:01:22 {"match_count":484} [loadgen] 2022/07/14 15:01:22 query 'faith': matched 484 [loadgen] 2022/07/14 15:01:22 query 'hello': matched 349 [client] 2022/07/14 15:01:22 {"match_count":349} [client] 2022/07/14 15:01:23 {"match_count":1036} [loadgen] 2022/07/14 15:01:23 query 'friend': matched 1036 [loadgen] 2022/07/14 15:01:28 query 'tear': matched 463 ...
עכשיו נבדוק אם כל רכיבי ה-span נשלחו כראוי אל Cloud Trace. נכנסים למסוף Cloud ועוברים אל 'רשימת נתוני מעקב'. אפשר לגשת אליו בקלות מתיבת החיפוש. אם לא, אפשר ללחוץ על התפריט בחלונית הימנית.
לאחר מכן הרבה מקומות כחולים מפוזרים על פני תרשים זמן האחזור. כל נקודה מייצגת מעקב אחד.
לוחצים על אחת מהן כדי לראות את הפרטים במעקב.
אפילו מהמבט הקצר הזה אתם כבר יודעים הרבה תובנות. לדוגמה, בתרשים Waterfall, אפשר לראות שהסיבה לזמן האחזור היא בעיקר בגלל הטווח שנקרא shakesapp.ShakespeareService/GetMatchCount
. (ראו 1 בתמונה למעלה) אפשר לבדוק זאת בטבלת הסיכום. (בעמודה הימנית ביותר מוצג משך הזמן של כל טווח). בנוסף, המעקב הזה היה עבור השאילתה 'חבר'. (ראו 2 בתמונה שלמעלה)
בעקבות הניתוחים הקצרים האלה, יכול להיות שתבינו שצריך לדעת על טווחים מפורטים יותר של שיטת GetMatchCount
. בהשוואה למידע על stdout, התצוגה החזותית היא עוצמתית. מידע נוסף על הפרטים של Cloud Trace זמין במסמכי התיעוד הרשמיים.
סיכום
בשלב הזה החלפתם את יצואן stdout ב-Cloud Trace One ובעזרת נתוני מעקב חזותיים ב-Cloud Trace. למדתם גם איך להתחיל לנתח את המעקב.
הנושא הבא
בשלב הבא, תשנו את קוד המקור של שירות השרת כדי להוסיף תת-טווח ב-GetMatchCount.
7. כדאי להוסיף תת-טווח לניתוח טוב יותר
בשלב הקודם, גיליתם שהסיבה לזמן הלוך ושוב שנצפה מסביבת הטעינה היא בעיקר התהליך בתוך שיטת GetMatchCount, ה-handler של gRPC, בשירות השרת. עם זאת, מאחר שלא הטמענו שום דבר מלבד ה-handler, אין לנו אפשרות למצוא תובנות נוספות מתרשים ה-Waterfall. זה מצב נפוץ כשאנחנו מתחילים ליצור אינסטרומנטציה של מיקרו-שירותים (microservices).
בסעיף הזה, נשתמש בתת-טווח שבו השרת קורא ל-Google Cloud Storage, כי לעיתים קרובות התהליך של קלט/פלט ברשת חיצונית נמשך הרבה זמן וחשוב לזהות אם הקריאה היא הגורם לבעיה.
כך יוצרים תת-טווח בשרת
פותחים את main.go
בשרת ומוצאים את הפונקציה readFiles
. הפונקציה הזו שולחת בקשה ל-Google Cloud Storage לאחזור כל קובצי הטקסט של יצירות של שייקספיר. בפונקציה הזו אפשר ליצור תת-טווח, כמו מה שעשיתם לאינסטרומנטציה של שרת HTTP בשירות הלקוח.
step0/src/server/main.go
func readFiles(ctx context.Context, bucketName, prefix string) ([]string, error) { type resp struct { s string err error } // step4: add an extra span span := trace.SpanFromContext(ctx) span.SetName("server.readFiles") span.SetAttributes(attribute.Key("bucketname").String(bucketName)) defer span.End() // step4: end add span ...
והכל דרך הוספת span חדש. בואו נראה איך זה הולך, על ידי הפעלת האפליקציה.
מפעילים את המיקרו-שירות (microservice) ומאשרים את המעקב
אחרי העריכה, מריצים את האשכול כרגיל באמצעות פקודת skaffold.
skaffold dev
ובוחרים מעקב אחד בשם query.request
מרשימת המעקב. יוצג תרשים Waterfall דומה למעקב, מלבד טווח חדש מתחת ל-shakesapp.ShakespeareService/GetMatchCount
. (התחום מוקף במלבן אדום למטה)
מה שאפשר לראות מהתרשים הזה עכשיו הוא שלקריאה החיצונית ל-Google Cloud Storage יש זמן אחזור רב, אבל גורמים אחרים עדיין משאירים את רוב זמן האחזור.
כבר קיבלת הרבה תובנות מכמה לוקים מתרשים ה-Waterfall של המעקב. איך מקבלים את פרטי הביצועים הנוספים בבקשה? כאן נכנס הכלי ליצירת פרופילים, אבל בינתיים נסיים את ה-Codelab הזה ונעביר את כל מדריכי הפרופיל לחלק השני.
סיכום
בשלב הזה יצרתם רצף נוסף בשירות השרת וקיבלתם תובנות נוספות על זמן האחזור של המערכת.
8. מזל טוב
יצרת בהצלחה מעקבים מבוזרים באמצעות OpenTelemery ואישרת את זמני האחזור של הבקשות במיקרו-שירות (microservice) ב-Google Cloud Trace.
בתרגילים מורחבים, אתם יכולים לנסות את הנושאים הבאים בעצמכם.
- ההטמעה הנוכחית שולחת את כל החלקים שנוצרו על ידי בדיקת התקינות. (
grpc.health.v1.Health/Check
) איך מסננים את החלקים האלה מ-Cloud Traces? הרמז נמצא כאן. - קורלציה בין יומני אירועים ל-span – כדי לראות איך זה עובד ב-Google Cloud Trace וב-Google Cloud Logging. הרמז נמצא כאן.
- מחליפים חלק מהשירות בשירות בשפה אחרת ומנסים לבצע אינסטרומנטציית OpenTelemetry באותה שפה.
בנוסף, אם ברצונך לקבל מידע נוסף על כלי הפרופיל אחרי זה, עליך לעבור לחלק 2. במקרה כזה, אפשר לדלג על קטע הניקוי שלמטה.
פינוי מקום
אחרי שתעשו את ה-codelab הזה, צריך להפסיק את אשכול Kubernetes ולוודא למחוק את הפרויקט כדי שלא יתקבלו חיובים לא צפויים ב-Google Kubernetes Engine, ב-Google Cloud Trace ו-Google Artifact Registry.
קודם מוחקים את האשכול. אם אתם מפעילים את האשכול עם skaffold dev
, צריך רק להקיש על Ctrl-C. אם מריצים את האשכול עם skaffold run
, מריצים את הפקודה הבאה:
skaffold delete
פלט הפקודה
Cleaning up... - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
אחרי מחיקת האשכול, בחלונית התפריט, בוחרים באפשרות 'IAM & Admin" (אדמין) > 'הגדרות', ולאחר מכן ללחוץ על 'להורדה' לחצן.
אחר כך מזינים בטופס את מזהה הפרויקט (לא שם הפרויקט) בתיבת הדו-שיח ומאשרים את הכיבוי.