שימוש ב-Istio Multicluster כדי "לפרוק" עומסי עבודה בין אשכולות

1. היי!

תודה שהצטרפתם אלינו ל-codelab בנושא Istio Multi Cloud Burst של Google.כדי להשתתף ב-codelab הזה, נדרש ניסיון מעשי ברמת מתחילים ב-Kubernetes,‏ Node ו-Go.

מה צריך

  • חשבון ב-Google Cloud Platform (אפשר להשתמש בחשבון קיים, או שנספק חשבונות בחינם)
  • המחשב הנייד (צריך להתקין את kubectl,‏ gcloud וכו') או שאתם יכולים להשתמש ב-Google Cloud Shell.

מה תלמדו

  • איך יוצרים אשכול Kubernetes ב-GKE
  • איך מתקינים את Istio באשכול Kubernetes באמצעות Helm
  • איך מתקינים Istio Multicluster באמצעות Helm
  • פריסת אפליקציית אינטרנט מקוד מקור ב-Kubernetes
  • כתיבה והחלה של כללי ניתוב תנועה ב-Istio
  • מדדים של Prometheus
  • איך יוצרים תמונות של קונטיינרים ומעבירים אותן לתוך אשכול Kubernetes

2. תהליך ההגדרה

אפשר לנסות את ה-Codelab הזה באחת מהדרכים הבאות:

  • ‫Google Cloud Shell (מומלץ): מעטפת (shell) בדפדפן, כולל כלים מותקנים
  • במחשב הנייד (פועלים לפי ההוראות שבהמשך)

צעדים ראשונים עם Google Cloud Platform

  1. אם אין לכם חשבון GCP, תוכלו לקבל כרטיס עם פרטי חשבון משתמש בחינם מהמדריך.
  2. נכנסים אל מסוף Google Cloud ולוחצים על 'בחירת פרויקט': 5c2d9bf74c78f7e4.png
  3. רושמים בצד את המזהה של הפרויקט, ואז לוחצים על הפרויקט כדי לבחור אותו: ecc5e8e97bfa6559.png

‫Cloud Shell מספקת מעטפת של שורת פקודה בתוך הדפדפן, עם הכלים שאתם צריכים מותקנים ומאומתים אוטומטית בחשבון שלכם ב-Google Cloud Platform. (אם אתם לא רוצים להריץ את התרגיל הזה ב-Cloud Shell, אתם יכולים לדלג לקטע הבא).

עוברים אל Cloud Console ולוחצים על 'הפעלת Cloud Shell' בסרגל הכלים שבפינה הימנית העליונה:

68a17b036ce24ccb.png

הוספת כלים ל-Cloud Shell

  1. התקנה kubectx****: מורידים את סקריפטים של bash מכאן למיקום ב-‎ $PATH.
  2. התקנה של helm****: פועלים לפי ההוראות האלה.

אפשר גם להריץ את הפקודות האלה כדי להתקין את שניהם ב-~/.bin ולהוסיף אותו ל-$PATH:

mkdir -p ~/.bin && \
cd ~/.bin && \
curl -LO https://raw.githubusercontent.com/ahmetb/kubectx/master/kubectx && \
chmod +x kubectx && \
curl -LO https://raw.githubusercontent.com/ahmetb/kubectx/master/kubens && \
chmod +x kubens && \
curl -LO  https://storage.googleapis.com/kubernetes-helm/helm-v2.12.0-linux-amd64.tar.gz && \
tar xzf helm-v2.12.0-linux-amd64.tar.gz && \
rm helm-v2.12.0-linux-amd64.tar.gz && \
mv linux-amd64/helm ./helm && \
rm -r linux-amd64 && \
export PATH=${HOME}/.bin:${PATH}

כמה טיפים מהירים שיכולים להקל על השימוש ב-Cloud Shell:

1. מנתקים את המעטפת לחלון חדש:

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

3. לפתוח כרטיסיות חדשות: אם אתם צריכים יותר מהנחיות טרמינל אחת.

4. הגדלת הטקסט: גודל הגופן שמוגדר כברירת מחדל ב-Cloud Shell יכול להיות קטן מדי לקריאה.

Ctrl-+‎ ב-Linux/Windows או ‎⌘-+‎ ב-macOS.

אם אתם מעדיפים להשתמש בסביבת תחנת העבודה שלכם במקום ב-Cloud Shell, אתם צריכים להגדיר את הכלים הבאים:

  1. מתקינים את gcloud: (האפליקציה מותקנת מראש ב-Cloud Shell). פועלים לפי ההוראות כדי להתקין את gcloud בפלטפורמה. נשתמש בזה כדי ליצור אשכול Kubernetes.
  2. התקנה של kubectl:(מותקן מראש ב-Cloud Shell). מריצים את הפקודה הבאה כדי להתקין:
gcloud components install kubectl

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

gcloud init
  1. התקנה curl: מותקן מראש ברוב מערכות Linux/macOS. סביר להניח שכבר יש לכם אותו. אחרת, חפשו באינטרנט הוראות להתקנה.
  2. התקנה של kubectx****: מורידים את סקריפטים של Bash מכאן למיקום ב-$PATH
  3. התקנה של helm****: פועלים לפי ההוראות האלה.

3. הגדרת פרויקט GCP

מפעילים את ממשקי ה-API של GKE‏ (Google Kubernetes Engine),‏ GCR‏ (Google Container Registry) ו-GCB‏ (Google Cloud Build) בפרויקט:

gcloud services enable \
  cloudapis.googleapis.com \
  container.googleapis.com \
  containerregistry.googleapis.com \
  cloudbuild.googleapis.com

הגדרה של משתני סביבה

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

export GCLOUD_PROJECT=$(gcloud config get-value project)

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

mkdir -p src/istio-burst && \
cd src/istio-burst && \
export proj=$(pwd)

4. יצירת אשכול Kubernetes 'ראשי'

אפשר ליצור בקלות אשכול Kubernetes מנוהל באמצעות Google Kubernetes Engine‏ (GKE).

הפקודה הבאה תיצור אשכול Kubernetes:

  • בשם 'primary',
  • באזור us-west1-a,
  • הגרסה האחרונה של Kubernetes שזמינה,
  • עם 4 צמתים ראשוניים
export cluster=primary
export zone=us-west1-a
gcloud container clusters create $cluster --zone $zone --username "admin" \
--cluster-version latest --machine-type "n1-standard-2" \
--image-type "COS" --disk-size "100" \
--scopes "https://www.googleapis.com/auth/compute",\
"https://www.googleapis.com/auth/devstorage.read_only",\
"https://www.googleapis.com/auth/logging.write",\
"https://www.googleapis.com/auth/monitoring",\
"https://www.googleapis.com/auth/servicecontrol",\
"https://www.googleapis.com/auth/service.management.readonly",\
"https://www.googleapis.com/auth/trace.append" \
--num-nodes "4" --network "default" \
--enable-cloud-logging --enable-cloud-monitoring --enable-ip-alias

(התהליך עשוי להימשך כ-5 דקות. אפשר לצפות בתהליך יצירת האשכול במסוף Cloud.)

אחרי שיוצרים את אשכול Kubernetes, ‏ gcloud מגדיר את kubectl עם פרטי הכניסה שמפנים לאשכול.

gcloud container clusters get-credentials $cluster --zone=$zone

עכשיו אמורה להיות לך אפשרות להשתמש ב-kubectl עם האשכול החדש.

מריצים את הפקודה הבאה כדי להציג את צמתי Kubernetes של האשכול (הסטטוס שלהם צריך להיות Ready):

kubectl get nodes

שינוי שמות של Kubeconfig לשימוש קל

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

הפקודה הזו תשנה את השם של רשומת ה-kubeconfig שיצרתם ל-primary

kubectx ${cluster}=gke_${GCLOUD_PROJECT}_${zone}_${cluster}

הגדרת הרשאות:

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

kubectl create clusterrolebinding cluster-admin-binding \
    --clusterrole=cluster-admin \
    --user=$(gcloud config get-value core/account)

5. יצירת אשכול 'burst'

הפקודה הבאה תיצור אשכול Kubernetes:

  • בשם burst,
  • באזור us-west1-a,
  • הגרסה האחרונה של Kubernetes שזמינה,
  • עם צומת התחלתי אחד
  • התאמה אוטומטית לעומס מופעלת עד 5 צמתים
export cluster=burst
export zone=us-west1-a
gcloud container clusters create $cluster --zone $zone --username "admin" \
--cluster-version latest --machine-type "n1-standard-2" \
--image-type "COS" --disk-size "100" \
--scopes "https://www.googleapis.com/auth/compute",\
"https://www.googleapis.com/auth/devstorage.read_only",\
"https://www.googleapis.com/auth/logging.write",\
"https://www.googleapis.com/auth/monitoring",\
"https://www.googleapis.com/auth/servicecontrol",\
"https://www.googleapis.com/auth/service.management.readonly",\
"https://www.googleapis.com/auth/trace.append" \
--num-nodes "1" --enable-autoscaling --min-nodes=1 --max-nodes=5 \
--network "default" \
--enable-cloud-logging --enable-cloud-monitoring --enable-ip-alias

(התהליך עשוי להימשך כ-5 דקות. אפשר לצפות בתהליך יצירת האשכול במסוף Cloud.)

אחרי שיוצרים את אשכול Kubernetes, ‏ gcloud מגדיר את kubectl עם פרטי הכניסה שמפנים לאשכול.

gcloud container clusters get-credentials $cluster --zone=$zone

עכשיו אמורה להיות לך אפשרות להשתמש ב-kubectl עם האשכול החדש.

מריצים את הפקודה הבאה כדי להציג את צמתי Kubernetes של האשכול (הסטטוס שלהם צריך להיות Ready):

kubectl get nodes

שינוי שמות של Kubeconfig לשימוש קל

הפקודה הזו תשנה את רשומת ה-kubeconfig שיצרתם ל-burst

kubectx ${cluster}=gke_${GCLOUD_PROJECT}_${zone}_${cluster}

הגדרת הרשאות:

כדי לפרוס את Istio Remote, צריך להיות אדמין של האשכול. הפקודה הזו תגדיר את כתובת האימייל שמשויכת לחשבון Google Cloud שלכם כאדמין של האשכול

kubectl create clusterrolebinding cluster-admin-binding \
    --clusterrole=cluster-admin \
    --user=$(gcloud config get-value core/account)

6. החלת כללים לחומת האש

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

מריצים את הפקודות הבאות כדי ליצור כלל בחומת האש ב-Google Cloud Platform שיאפשר תקשורת בין האשכולות שלנו

function join_by { local IFS="$1"; shift; echo "$*"; }
ALL_CLUSTER_CIDRS=$(gcloud container clusters list \
--filter="(name=burst OR name=primary) AND zone=$zone" \
--format='value(clusterIpv4Cidr)' | sort | uniq)
ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}"))
ALL_CLUSTER_NETTAGS=$(gcloud compute instances list \
--filter="(metadata.cluster-name=burst OR metadata.cluster-name=primary) AND metadata.cluster-location=us-west1-a" \
--format='value(tags.items.[0])' | sort | uniq)
ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
gcloud compute firewall-rules create istio-multicluster-test-pods \
  --allow=tcp,udp,icmp,esp,ah,sctp \
  --direction=INGRESS \
  --priority=900 \
  --source-ranges="${ALL_CLUSTER_CIDRS}" \
  --target-tags="${ALL_CLUSTER_NETTAGS}" --quiet

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

7. מבוא ל-Istio

מה זה Istio?

‫Istio הוא רמת בקרה של Service mesh שמטרתה 'חיבור, אבטחה, בקרה ומעקב אחרי שירותים'. היא עושה זאת במגוון דרכים, אבל בעיקר על ידי הוספת קונטיינר פרוקסי ( Envoy) לכל אחד מה-Pods של Kubernetes שנפרסו. קונטיינר ה-proxy שולט בכל התקשורת ברשת בין המיקרו-שירותים, בשילוב עם מרכז מדיניות וטלמטריה לשימוש כללי ( Mixer).

a25613cd581825da.png

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

חלק מהתכונות של ניהול התנועה ש-Istio תומך בהן:

  • מפסקים חשמליים
  • חלוקת תנועה על סמך אחוזים
  • שכתוב כתובות URL
  • סיום TLS
  • בדיקות תקינות
  • איזון עומסים

בסדנה הזו נתמקד בפיצול תנועה על בסיס אחוזים.

מונחים של Istio שבהם נשתמש

VirtualService

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

Gateway

שער הוא מאזן עומסים שפועל בקצה של רשת ה-mesh ומקבל חיבורי HTTP/TCP נכנסים או יוצאים. שערי VPN יכולים לציין יציאות, הגדרות SNI וכו'.

DestinationRule

DestinationRule מגדיר מדיניות שחלה על תנועה שמיועדת לשירות אחרי הניתוב. הם מציינים את ההגדרה לאיזון עומסים, את הגודל של מאגר החיבורים מ-sidecar ואת ההגדרות של זיהוי חריגים.

Istio Multicluster

יכול להיות ששמתם לב כשיצרנו את שני האשכולות, שאשכול primary היה 4 צמתים ללא התאמה אוטומטית לעומס, ואשכול burst היה צומת אחד עם התאמה אוטומטית לעומס עד 5 צמתים.

יש שתי סיבות להגדרה הזו.

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

שנית, הגדרה של 4 צמתים (כפי שמוגדר למעלה) היא דרישת המינימום להרצת Istio. מכאן עולה השאלה: אם Istio דורש מינימום של 4 צמתים, איך יכול אשכול burst שלנו להריץ את Istio עם צומת אחד? התשובה היא ש-Istio Multicluster מתקין קבוצה קטנה בהרבה של שירותי Istio, ומתקשר עם התקנת Istio באשכול הראשי כדי לאחזר את כללי המדיניות ולפרסם מידע טלמטרי.

8. סקירה כללית על ארכיטקטורת אפליקציות

סקירה כללית של הרכיבים

נפרוס אפליקציה תלת-שכבתית באמצעות NodeJS ו-Redis.

Worker

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

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

לעיון: אלה החבילות שבהן האפליקציה משתמשת.

  • body-parser: מאפשר לנו לנתח את בקשות ה-HTTP שלנו
  • cors: מאפשר שימוש בשיתוף משאבים בין מקורות
  • dotenv: ניתוח קל של משתני סביבה
  • express: אירוח אתרים פשוט
  • ioredis: ספריית לקוח לתקשורת עם מסדי נתונים של Redis
  • morgan: מספק יומן מובנה ונוח

Frontend

הקצה הקדמי שלנו הוא גם אפליקציית NodeJS שמארחת דף אינטרנט באמצעות express. הכלי מקבל תדירות שהמשתמש מזין ושולח בקשות לאפליקציית worker שלנו בתדירות הזו. האפליקציה הזו גם נרשמת לקבלת הודעות בערוץ Redis בשם calculation ומציגה את התוצאות בדף אינטרנט.

האפליקציה משתמשת ביחסי התלות הבאים.

  • body-parser: מאפשר לנו לנתח את בקשות ה-HTTP שלנו
  • dotenv: ניתוח קל של משתני סביבה
  • express: אירוח אתרים פשוט
  • ioredis: ספריית לקוח לתקשורת עם מסדי נתונים של Redis
  • morgan: יומנים מובְנים ונוחים
  • request: הרשאה ליצירת בקשות HTTP
  • socket.io: מאפשר תקשורת דו-כיוונית מדף האינטרנט לשרת

דף האינטרנט הזה משתמש ב-Bootstrap לעיצוב, וכשהוא מופעל הוא נראה כך:

e5e3b9cbede4cac4.png

תרשים ארכיטקטורה

7ae4bc22a58f80a6.png

Deployment Diagram

נפרוס את האפליקציה הסופית בשני האשכולות שיצרנו. כל הרכיבים (frontend,‏ worker ו-Redis) יפרסו באשכול primary, אבל רק האפליקציה worker תפרוס באשכול burst.

התרשים הבא מתאר את שני האשכולות. התיבות שמסומנות בקו אדום הן שירותי Kubernetes, והתיבות שמסומנות בקו כחול הן פריסות Kubernetes. התיבות הצהובות מסמלות את ההתקנה של Istio.

561db37c510944bd.png

שימו לב שלקלאסטר burst עדיין יש שירות ל-Redis שפרוס בו, למרות שאין פריסה של Redis בקלאסטר. אנחנו צריכים את השירות הזה באשכול כדי ש-Kubernetes DNS יוכל לפתור את הבקשה, אבל כשהבקשה מוגשת בפועל, Istio Proxy ינתב מחדש את הבקשה לפריסת Redis באשכול primary.

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

8f6183bdfc3f813c.png

9. יצירת קובצי פריסה של אפליקציות

אנחנו צריכים ליצור קבוצה של מניפסטים של Kubernetes כדי לפרוס את האפליקציה שלנו

עוברים לספריית הבסיס של הפרויקט ויוצרים תיקייה חדשה בשם kubernetes

mkdir ${proj}/kubernetes && cd ${proj}/kubernetes

כתיבת הקובץ frontend.yaml

הפעולה הזו תיצור גם Kubernetes Deployment וגם Service כדי לגשת לקובץ האימג' של קצה קדמי.

מכניסים את הקוד הבא ל-frontend.yaml.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: frontend-deployment
  labels:
    app: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: gcr.io/istio-burst-workshop/frontend
        ports:
        - containerPort: 8080
        readinessProbe:
            initialDelaySeconds: 10
            httpGet:
              path: "/_healthz"
              port: 8080
              httpHeaders:
              - name: "Cookie"
                value: "istio_session-id=x-readiness-probe"
        livenessProbe:
          initialDelaySeconds: 10
          httpGet:
            path: "/"
            port: 8080
            httpHeaders:
            - name: "Cookie"
              value: "istio_session-id=x-liveness-probe"
        env:
        - name: PORT
          value: "8080"
        - name: PROCESSOR_URL
          value: "http://worker-service"
        - name: REDIS_URL
          value: "redis-cache-service:6379"
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  type: ClusterIP
  selector:
    app: frontend
  ports:
  - name: http
    port: 80
    targetPort: 8080

דברים חשובים שכדאי לשים לב אליהם בDeployment

  • ציינו שהיציאה שבה האפליקציה תפעל היא 8080
  • הגדרנו את הכתובת של העובד כ-http://worker-service ונשתמש בתכונת ה-DNS המובנית של Kubernetes כדי לפתור את השירות שמתקבל
  • הגדרנו את הכתובת של REDIS_URL להיות redis-cache-service:6379 ונשתמש בתכונת ה-DNS המובנית של Kubernetes כדי לפענח את כתובות ה-IP שמתקבלות.
  • הגדרנו גם בדיקות liveness ו-readiness לקונטיינר כדי לעדכן את Kubernetes כשהקונטיינר פועל.

כתיבה של worker-service.yaml

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

מזינים את הפרטים הבאים ב-worker-service.yaml

apiVersion: v1
kind: Service
metadata:
 name: worker-service
spec:
 type: ClusterIP
 selector:
   app: worker
 ports:
 - name: http
   port: 80
   targetPort: 8081

כתיבת הקובץ worker-primary.yaml

זו תהיה הפריסה של worker שנדחוף לאשכול הראשי.

מכניסים את הקוד הבא ל-worker-primary.yaml.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: worker-deployment
 labels:
   app: worker
spec:
 replicas: 1
 selector:
   matchLabels:
     app: worker
 template:
   metadata:
     labels:
       app: worker
       cluster-type: primary-cluster
   spec:
     containers:
     - name: worker
       image: gcr.io/istio-burst-workshop/worker
       imagePullPolicy: Always
       ports:
       - containerPort: 8081
       readinessProbe:
           initialDelaySeconds: 10
           httpGet:
             path: "/_healthz"
             port: 8081
             httpHeaders:
             - name: "Cookie"
               value: "istio_session-id=x-readiness-probe"
       livenessProbe:
         initialDelaySeconds: 10
         httpGet:
           path: "/"
           port: 8081
           httpHeaders:
           - name: "Cookie"
             value: "istio_session-id=x-liveness-probe"
       env:
       - name: PORT
         value: "8081"
       - name: REDIS_URL
         value: "redis-cache-service:6379"

שימו לב שאנחנו פועלים לפי אותו דפוס של אספקת בדיקות liveness ו-readiness, וגם מציינים את משתני הסביבה PORT ו-REDIS_URL לשימוש באפליקציה שלנו.

עוד דבר שחשוב לשים לב אליו בפריסה הזו הוא שמשתנה הסביבה PREFIX לא מוגדר. המשמעות היא שתוצאות החישוב שלנו יהיו גיבובים גולמיים (ללא קידומת).

נקודת המפתח האחרונה בפריסה הזו היא התווית cluster-type: primary-cluster. נשתמש בזה בהמשך כשנעשה ניתוב תנועה ב-Istio Multicluster

כתיבת redis.yaml

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

הוספת הקוד הבא אל redis.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: redis-cache
spec:
 template:
   metadata:
     labels:
       app: redis-cache
   spec:
     containers:
     - name: redis
       image: redis:alpine
       ports:
       - containerPort: 6379
       readinessProbe:
         periodSeconds: 5
         tcpSocket:
           port: 6379
       livenessProbe:
         periodSeconds: 5
         tcpSocket:
           port: 6379
       volumeMounts:
       - mountPath: /data
         name: redis-data
       resources:
         limits:
           memory: 256Mi
           cpu: 125m
         requests:
           cpu: 70m
           memory: 200Mi
     volumes:
     - name: redis-data
       emptyDir: {}

זהו פריסה חצי סטנדרטית של אפליקציית Redis. הוא יוצר קונטיינר שמבוסס על תמונת redis:alpine, חושף את הפורטים המתאימים ומגדיר מגבלות משאבים הגיוניות.

כתיבת redis-service.yaml

אנחנו צריכים שירות Kubernetes כדי לתקשר עם אפליקציית Redis

הוספת הקוד הבא אל redis-service.yaml

apiVersion: v1
kind: Service
metadata:
 name: redis-cache-service
spec:
 type: ClusterIP
 selector:
   app: redis-cache
 ports:
 - port: 6379
   targetPort: 6379

השירות הזה נקרא redis-cache-service ויש לו גישה לפריסת Redis שלנו.

10. פריסת האפליקציה

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

מריצים את הפקודות הבאות כדי לפרוס את האפליקציה

  1. מוודאים שאנחנו באשכול הנכון
kubectx primary
  1. פריסת מטמון Redis
kubectl apply -f redis.yaml
  1. פריסת שירות Redis
kubectl apply -f redis-service.yaml
  1. פריסת הקצה הקדמי
kubectl apply -f frontend.yaml
  1. פריסת העובד
kubectl apply -f worker-primary.yaml
  1. פריסת שירות העובד
kubectl apply -f worker-service.yaml

פרסנו את האפליקציה שלנו ב-GKE. מזל טוב!

בדיקה

מחכים עד שהפודים יעלו לאונליין

kubectl get pods -w

אחרי שכל הפודים במצב Running, לוחצים על Ctrl + C.

NAME                                   READY     STATUS    RESTARTS   AGE
frontend-deployment-695d95fbf7-76sd8   1/1       Running   0          2m
redis-cache-7475999bf5-nxj8x           1/1       Running   0          2m
worker-deployment-5b9cf9956d-g975p     1/1       Running   0          2m

אפשר לראות שלא חשפנו את קצה קדמי שלנו דרך LoadBalancer. הסיבה לכך היא שבהמשך ניגש לאפליקציה דרך Istio. כדי לבדוק שהכול פועל, נשתמש ב-kubectl port-forward.. מריצים את הפקודה הבאה כדי להעביר את היציאה 8080 במחשב המקומי (או ב-Cloud Shell) ליציאה 8080 שבה פועל הפריסה של frontend.

kubectl port-forward \
$(kubectl get pods -l app=frontend -o jsonpath='{.items[0].metadata.name}') \
8080:8080

אם מריצים באופן מקומי: פותחים דפדפן אינטרנט ועוברים אל http://localhost:8080

אם אתם מריצים את הפקודה ב-Cloud Shell: לוחצים על הלחצן 'תצוגה מקדימה באינטרנט' ובוחרים באפשרות 'תצוגה מקדימה ביציאה 8080'.

bdb5dc75f415be11.png

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

1caafaffab26897a.png

מזל טוב, הכול מוכן!

לוחצים על Ctrl+C כדי להפסיק את העברת המספר.

11. ניקוי אפליקציה שנפרסה

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

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

  1. מחיקת redis-cache-service
kubectl delete -f redis-service.yaml
  1. מחיקת redis
kubectl delete -f redis.yaml
  1. מחיקת frontend
kubectl delete -f frontend.yaml
  1. מחיקת worker
kubectl delete -f worker-primary.yaml
  1. מחיקת worker-service
kubectl delete -f worker-service.yaml

12. התקנת Istio באשכול הראשי

קבלת Istio

הגרסאות של Istio מתארחות ב-GitHub. הפקודות הבאות יורידו את גרסה 1.0.0 של istio ויחלצו אותה.

  1. מעבר לשורש של הפרויקט
cd ${proj}
  1. הורדת הארכיון
curl -LO https://github.com/istio/istio/releases/download/1.0.0/istio-1.0.0-linux.tar.gz
  1. חילוץ והסרה של הארכיון
tar xzf istio-1.0.0-linux.tar.gz && rm istio-1.0.0-linux.tar.gz

יצירת תבנית Istio

הפעלת פקודת Helm הבאה תיצור את התבנית להתקנת Istio באשכול.

helm template istio-1.0.0/install/kubernetes/helm/istio \
--name istio --namespace istio-system \
--set prometheus.enabled=true \
--set servicegraph.enabled=true  > istio-primary.yaml

פעולה זו יוצרת קובץ בשם istio-primary.yaml בתיקייה הנוכחית, שמכיל את כל ההגדרות והמפרטים שנדרשים לפריסה ולהרצה של Istio.

שימו לב לשני הפרמטרים --set. התוספים האלה מוסיפים תמיכה ב-Prometheus וב-ServiceGraph למערכת Istio. נשתמש בשירות Prometheus בהמשך שיעור ה-Lab.

פריסת Istio

כדי לפרוס את Istio, צריך קודם ליצור מרחב שמות בשם istio-system שבו אפשר להריץ את הפריסות והשירותים של Istio.

kubectl create namespace istio-system

ולבסוף, מפעילים את קובץ istio-primary.yaml שיצרנו באמצעות Helm.

kubectl apply -f istio-primary.yaml

מרחב שמות של תוויות ברירת מחדל

‫Istio פועל על ידי הזרקת שירות פרוקסי של קובץ עזר חיצוני לכל אחד מהפריסות שלכם. הפעולה הזו מתבצעת על בסיס הסכמה, ולכן אנחנו צריכים להוסיף את התווית istio-injection=enabled למרחב השמות default כדי ש-Istio יוכל להוסיף אוטומטית את ה-sidecar בשבילנו.

kubectl label namespace default istio-injection=enabled

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

13. פריסת האפליקציה שלנו באמצעות ניהול תנועה ב-Istio

יצירת קובצי הגדרות לניהול תעבורה ב-Istio

‫Istio פועל באופן דומה ל-Kubernetes, כי הוא משתמש בקובצי yaml להגדרה. לכן, אנחנו צריכים ליצור קבוצה של קבצים שמסבירים ל-Istio איך לחשוף את התנועה שלנו ולנתב אותה.

יוצרים ספרייה בשם istio-manifests ועוברים אליה

mkdir ${proj}/istio-manifests && cd ${proj}/istio-manifests

כתיבת הקובץ frontend-gateway.yaml

הקובץ הזה יחשוף את אשכול Kubernetes שלנו באופן דומה ל-GKE LoadBalancer, וינתב את כל התעבורה הנכנסת לשירות הקצה הקדמי שלנו.

יוצרים קובץ בשם frontend-gateway.yaml ומזינים את התוכן הבא.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
 name: frontend-gateway
spec:
 selector:
   istio: ingressgateway # use Istio default gateway implementation
 servers:
 - port:
     number: 80
     name: http
     protocol: HTTP
   hosts:
   - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: frontend-ingress-virtual-service
spec:
 hosts:
 - "*"
 gateways:
 - frontend-gateway
 http:
 - route:
   - destination:
       host: frontend-service
       port:
         number: 80

כתיבת הקובץ redis-virtualservice.yaml

יוצרים קובץ בשם redis-virtualservice.yaml ומזינים בו את התוכן הבא

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: redis-virtual-service
spec:
 hosts:
 - redis-cache-service
 gateways:
 - mesh
 tcp:
 - route:
   - destination:
       host: redis-cache-service.default.svc.cluster.local

כתיבת הקובץ worker-virtualservice.yaml

יוצרים קובץ בשם worker-virtualservice.yaml ומזינים בו את התוכן הבא

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: worker-virtual-service
spec:
 hosts:
 - worker-service
 gateways:
 - mesh
 http:
 - route:
   - destination:
       host: worker-service.default.svc.cluster.local   
       port:
         number: 80

פריסת מדיניות לניהול תעבורה ב-Istio

פריסת כללי המדיניות של Istio מתבצעת באותו אופן כמו פריסה של משאבי Kubernetes אחרים, באמצעות kubectl apply

  1. החלת השער שלנו
kubectl apply -f frontend-gateway.yaml
  1. החלת Redis VirtualService
kubectl apply -f redis-virtualservice.yaml
  1. החלת Worker VirtualService
kubectl apply -f worker-virtualservice.yaml

פריסת האפליקציה

  1. חזרה לספרייה kubernetes שלנו
cd ${proj}/kubernetes
  1. פריסת מטמון Redis
kubectl apply -f redis.yaml
  1. פריסת שירות Redis
kubectl apply -f redis-service.yaml
  1. פריסת הקצה הקדמי
kubectl apply -f frontend.yaml
  1. פריסת העובד
kubectl apply -f worker-primary.yaml
  1. פריסת שירות העובד
kubectl apply -f worker-service.yaml

אימות

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

צריך לחכות עד שכל עומסי העבודה יהיו אונליין

אחרי שכולם מחוברים, מקבלים את IngressGateway שהגדרנו ב-frontend-ingressgateway.yaml

$ kubectl -n istio-system get svc istio-ingressgateway
NAME                   TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)                                                                                                     AGE
istio-ingressgateway   LoadBalancer   10.36.3.112   35.199.158.10   80:31380/TCP,

אפשר לעבור לכתובת <EXTERNAL-IP> או להשתמש בפקודה curl כדי לראות את ממשק הקצה.

$ curl 35.199.158.10
<!doctype html>
<html>

<head>
    <title>String Hashr</title>
    <!-- Bootstrap -->
...

14. התקנת Istio באשכול 'burst'

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

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

יצירת מניפסט Istio Remote

בדומה לפריסת Istio באשכול primary, נשתמש ב-Helm כדי ליצור תבנית לפריסת Istio מרחוק באשכול burst. לפני שנוכל לעשות זאת, אנחנו צריכים לקבל מידע על אשכול primary שלנו

איסוף מידע על האשכול הראשי

שינוי לאשכול primary

kubectx primary

הפקודות הבאות מאחזרות את כתובות ה-IP של פודים שונים באשכול הראשי. הם משמשים את Istio Remote כדי לתקשר עם האשכול הראשי.

export PILOT_POD_IP=$(kubectl -n istio-system get pod -l istio=pilot -o jsonpath='{.items[0].status.podIP}')
export POLICY_POD_IP=$(kubectl -n istio-system get pod -l istio-mixer-type=policy -o jsonpath='{.items[0].status.podIP}')
export STATSD_POD_IP=$(kubectl -n istio-system get pod -l istio=statsd-prom-bridge -o jsonpath='{.items[0].status.podIP}')
export TELEMETRY_POD_IP=$(kubectl -n istio-system get pod -l istio-mixer-type=telemetry -o jsonpath='{.items[0].status.podIP}')
export ZIPKIN_POD_IP=$(kubectl -n istio-system get pod -l app=jaeger -o jsonpath='{range .items[*]}{.status.podIP}{end}')

יצירת תבנית מרחוק

עכשיו נשתמש ב-helm כדי ליצור קובץ בשם istio-remote-burst.yaml, שאותו נוכל לפרוס באשכול burst.

שינוי לשורש הפרויקט

cd $proj
helm template istio-1.0.0/install/kubernetes/helm/istio-remote --namespace istio-system \
--name istio-remote \
--set global.remotePilotAddress=${PILOT_POD_IP} \
--set global.remotePolicyAddress=${POLICY_POD_IP} \
--set global.remoteTelemetryAddress=${TELEMETRY_POD_IP} \
--set global.proxy.envoyStatsd.enabled=true \
--set global.proxy.envoyStatsd.host=${STATSD_POD_IP} \
--set global.remoteZipkinAddress=${ZIPKIN_POD_IP} > istio-remote-burst.yaml

התקנת Istio Remote באשכול Burst

כדי להתקין את Istio באשכול burst, צריך לפעול לפי אותם השלבים כמו בהתקנה באשכול primary, אבל צריך להשתמש בקובץ istio-remote-burst.yaml.

שינוי kubecontext ל-burst

kubectx burst

יצירת מרחב השמות istio-system

kubectl create ns istio-system

החלת istio-burst.yaml

kubectl apply -f istio-remote-burst.yaml

מרחב השמות שמוגדר כברירת מחדל לתווית

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

kubectl label namespace default istio-injection=enabled

מזל טוב! בשלב הזה הגדרנו את Istio Remote באשכול burst. אבל בשלב הזה, עדיין אין אפשרות לתקשר בין האשכולות. אנחנו צריכים ליצור קובץ kubeconfig עבור אשכול burst שאפשר לפרוס לאשכול primary כדי לקשר ביניהם.

יצירת kubeconfig עבור אשכול 'burst'

מעבר לאשכול עם ניהול תעבורה זמני

kubectx burst

הגדרת הסביבה

כדי ליצור קובץ kubeconfig עבור האשכול, אנחנו צריכים לאסוף מידע מסוים לגבי האשכול.

  1. אחזור שם האשכול
CLUSTER_NAME=$(kubectl config view --minify=true -o "jsonpath={.clusters[].name}")
  1. אחזור שם השרת של האשכול
SERVER=$(kubectl config view --minify=true -o "jsonpath={.clusters[].cluster.server}")
  1. קבלת השם של הסוד עבור רשות האישורים של חשבון השירות istio-multi
SECRET_NAME=$(kubectl get sa istio-multi -n istio-system -o jsonpath='{.secrets[].name}')
  1. קבלת נתונים של רשות אישורים שמאוחסנים בסוד הקודם
CA_DATA=$(kubectl get secret ${SECRET_NAME} -n istio-system -o "jsonpath={.data['ca\.crt']}")
  1. קבלת האסימון שמאוחסן בסוד הקודם
TOKEN=$(kubectl get secret ${SECRET_NAME} -n istio-system -o "jsonpath={.data['token']}" | base64 --decode)

יצירת קובץ kubeconfig

אחרי שמגדירים את כל משתני הסביבה האלה, צריך ליצור את קובץ ה-kubeconfig.

cat <<EOF > burst-kubeconfig
apiVersion: v1
clusters:
   - cluster:
       certificate-authority-data: ${CA_DATA}
       server: ${SERVER}
     name: ${CLUSTER_NAME}
contexts:
   - context:
       cluster: ${CLUSTER_NAME}
       user: ${CLUSTER_NAME}
     name: ${CLUSTER_NAME}
current-context: ${CLUSTER_NAME}
kind: Config
preferences: {}
users:
   - name: ${CLUSTER_NAME}
     user:
       token: ${TOKEN}
EOF

ייווצר קובץ חדש בשם burst-kubeconfig בספרייה הנוכחית, שאפשר להשתמש בו כדי לאמת את אשכול burst ולנהל אותו באמצעות אשכול primary.

חזרה לקלאסטר הראשי

kubectx primary

החלת kubeconfig ל-burst על ידי יצירת סוד ותיוגו

kubectl create secret generic burst-kubeconfig --from-file burst-kubeconfig -n istio-system

מתן תווית לסוד כדי ש-Istio יידע להשתמש בו לאימות בין כמה אשכולות

kubectl label secret burst-kubeconfig istio/multiCluster=true -n istio-system

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

15. פריסת אפליקציה בין אשכולות

יצירת פריסות

עוברים לספרייה kubernetes

cd ${proj}/kubernetes

יצירת פריסת עובדים עבור אשכול 'burst': worker-burst.yaml

יוצרים קובץ בשם worker-burst.yaml ומזינים בו את התוכן הבא:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: worker-deployment
  labels:
    app: worker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: worker
  template:
    metadata:
      labels:
        app: worker
        cluster-type: burst-cluster
    spec:
      containers:
      - name: worker
        image: gcr.io/istio-burst-workshop/worker
        imagePullPolicy: Always
        ports:
        - containerPort: 8081
        readinessProbe:
            initialDelaySeconds: 10
            httpGet:
              path: "/_healthz"
              port: 8081
              httpHeaders:
              - name: "Cookie"
                value: "istio_session-id=x-readiness-probe"
        livenessProbe:
          initialDelaySeconds: 10
          httpGet:
            path: "/"
            port: 8081
            httpHeaders:
            - name: "Cookie"
              value: "istio_session-id=x-liveness-probe"
        env:
        - name: PORT
          value: "8081"
        - name: REDIS_URL
          value: "redis-cache-service:6379"
        - name: PREFIX
          value: "bursty-"

שימו לב שהקובץ הזה כמעט זהה לקובץ worker-primary.yaml שיצרנו קודם. יש שני הבדלים עיקריים.

ההבדל העיקרי הראשון הוא שהוספנו את משתנה הסביבה PREFIX עם הערך bursty-

env:
- name: PORT
  value: "8081"
- name: REDIS_URL
  value: "redis-cache-service:6379"
- name: PREFIX
  value: "bursty-"

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

ההבדל השני הוא ששינינו את התווית cluster-type בפריסה הזו מ-primary-cluster ל-burst-cluster

labels:
  app: worker
  cluster-type: burst-cluster

נשתמש בתווית הזו בהמשך כשנעדכן את VirtualService.

שינוי שירותי Istio

נכון לעכשיו, שירותי Istio שלנו לא מנצלים את שני הפריסות. ‫100% מהתנועה שלנו מנותבת לאשכול 'הראשי'. אני רוצה לשנות.

עוברים לספרייה istio-manifests

cd ${proj}/istio-manifests

עריכת הקובץ worker-virtualservice.yaml כדי לכלול בו DestinationRules

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: worker-virtual-service
spec:
  hosts:
  - worker-service
  gateways:
  - mesh
  http:
  - route:
    - destination:
        host: worker-service.default.svc.cluster.local    
        subset: primary
        port:
          number: 80        
      weight: 50
    - destination:
        host: worker-service.default.svc.cluster.local     
        subset: burst  
        port:
          number: 80        
      weight: 50
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: worker-destination-rule
spec:
  host: worker-service
  trafficPolicy:
    loadBalancer:
      simple: RANDOM
  subsets:
  - name: primary
    labels:
      cluster-type: primary-cluster
  - name: burst
    labels:
     cluster-type: burst-cluster

אפשר לראות שהוספנו יעד שני ל-VirtualService. הוא עדיין מפנה לאותו מארח (worker-service.default.svc.cluster.local)), אבל 50% מהתנועה מנותבים לקבוצת המשנה primary ו-50% מהתנועה מנותבים לקבוצת המשנה burst.

הגדרנו את קבוצת המשנה primary כפריסות עם התווית cluster-type: primary-cluster ואת קבוצת המשנה burst כפריסות עם התווית cluster-type: burst-cluster.

בפועל, התנועה מחולקת באופן שווה בין שני האשכולות.

פריסה באשכול

פריסת redis-service.yaml באשכול burst

שינוי ל-burst kubeconfig

kubectx burst

שינוי לשורש הפרויקט

cd ${proj}

ואז פורסים

פריסת redis-service.yaml באשכול burst

kubectl apply -f kubernetes/redis-service.yaml

פריסת worker-burst.yaml לאשכול burst

kubectl apply -f kubernetes/worker-burst.yaml

פריסת worker-service.yaml לאשכול burst

kubectl apply -f kubernetes/worker-service.yaml

החלת Istio VirtualServices

שינוי ל-primary kubeconfig

kubectx primary

ואז פורסים

kubectl apply -f istio-manifests/worker-virtualservice.yaml

אימות הפעולה

כדי לוודא שהיא פועלת, עוברים לנקודת הכניסה של Istio ורואים שכ-50% מהגיבובים מתחילים ב-burst-.

78fb6e235e9f4a07.png

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

16. שימוש במדדים של Prometheus

מבוא ל-Prometheus

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

לעיון, הנה תרשים הארכיטקטורה של Prometheus:

601e1155a825e0c2.png

כשפורסים את Istio עם Prometheus, המערכת מדווחת אוטומטית על מדדים שונים לשרת Prometheus. אנחנו יכולים להשתמש במדדים האלה כדי לנהל את האשכולות שלנו תוך כדי תנועה.

הסבר על מדדי Prometheus

כדי להתחיל, צריך לחשוף את הפריסה של Prometheus.

עוברים לכרטיסייה Workloads (עומסי עבודה) ב-GKE, ומתעמקים בעומס העבודה prometheus.

b4a7a3cd67db05b3.png

אחרי שצופים בפרטי הפריסה, עוברים אל Actions (פעולות) -> Expose (חשיפה).

c04a482e55bdfd41.png

בוחרים להעביר לפורט 9090 ומקלידים "מאזן עומסים"

d5af3ba22a7a6ebb.png

בוחרים באפשרות 'חשיפה'.

פעולה זו תיצור שירות בכתובת IP שנגישה לציבור, שנוכל להשתמש בה כדי לבדוק את מדדי Prometheus

מחכים עד שהנקודה תהיה פעילה, ואז לוחצים על כתובת ה-IP לצד 'נקודות קצה חיצוניות' b1e40ad90851da29.png

עכשיו אמור להופיע ממשק המשתמש של Prometheus.

ed273552270337ec.png

‫Prometheus מספק מספיק מדדים כדי להוות סדנה בפני עצמה. אבל בינתיים, נתחיל בבדיקה של המדד istio_requests_total.

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

בקשות שבהן שירות היעד הוא worker-service.default.svc.cluster.local, והמקור הוא frontend-deployment, מוגבלות ל-15 השניות האחרונות

השאילתה תיראה כך:

istio_requests_total{reporter="destination",
destination_service="worker-service.default.svc.cluster.local",
source_workload="frontend-deployment"}[15s]

ומקבלים מערך נתונים הרבה יותר נוח לעבודה

19d551fd5eac3785.png

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

כדי לקבל את זה, אפשר להשתמש בפונקציה המובנית rate

rate(istio_requests_total{reporter="destination",
destination_service="worker-service.default.svc.cluster.local",
source_workload="frontend-deployment"}[15s])

dbb9dc063a18da9b.png

אנחנו מתקרבים, אבל צריך לצמצם עוד קצת את המדדים האלה לקבוצה הגיונית.

כדי לעשות את זה, אפשר להשתמש במילות המפתח sum ו-by כדי לקבץ ולסכם את התוצאות

sum(rate(istio_requests_total{reporter="destination",
destination_service="worker-service.default.svc.cluster.local",
source_workload="frontend-deployment"}[15s])) by (source_workload,
source_app, destination_service)

898519966930ec56.png

יופי! אנחנו יכולים לקבל מ-Prometheus את המדדים המדויקים שאנחנו צריכים.

Our final Prometheus Query

אחרי שלמדנו את כל מה שצריך, השאילתה הסופית שצריך להריץ ב-Prometheus היא

sum(rate(istio_requests_total{reporter="destination",
destination_service="worker-service.default.svc.cluster.local",
source_workload="frontend-deployment"}[15s])) by (source_workload,
source_app, destination_service)

עכשיו אפשר להשתמש ב-HTTP API שלהם כדי לקבל את המדד.

אנחנו יכולים לשלוח שאילתה ל-API שלהם באמצעות בקשת GET, כמו בדוגמה הבאה. צריך להחליף את <prometheus-ip-here>

curl http://<prometheus-ip-here>/api/v1/query?query=sum\(rate\(istio_requests_total%7Breporter%3D%22destination%22%2C%0Adestination_service%3D%22worker-service.default.svc.cluster.local%22%2C%0Asource_workload%3D%22frontend-deployment%22%7D%5B15s%5D\)\)%20by%20\(source_workload%2C%0Asource_app%2C%20destination_service\)

זוהי דוגמה לתשובה:

{
    "status": "success",
    "data": {
        "resultType": "vector",
        "result": [
            {
                "metric": {
                    "destination_service": "worker-service.default.svc.cluster.local",
                    "source_app": "frontend",
                    "source_workload": "frontend-deployment"
                },
                "value": [
                    1544404907.503,
                    "18.892886390062788"
                ]
            }
        ]
    }
}

עכשיו אפשר לחלץ את ערך המדד מ-JSON

ניקוי נתונים

צריך למחוק את השירות שבו השתמשנו כדי לחשוף את Prometheus. במסוף Google Cloud, עוברים לחלק העליון של השירות שיצרנו ולוחצים על Delete (מחיקה).

d58cb51b4c922751.png

השלבים הבאים:

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

17. יצירת התפרצות תעבורה בין אשכולות

הגדרה

הגדרת כל התנועה לשירות העובד באשכול הראשי

אנחנו נחשיב את כל התנועה שמיועדת ל-worker-service ומנותבת לאשכול primary כסטטוס ברירת המחדל של האפליקציה שלנו

עריכת $proj/istio-manifests/worker-virtualservice.yaml כך שייראה כמו הדוגמה הבאה

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: worker-virtual-service
spec:
  hosts:
  - worker-service
  gateways:
  - mesh
  http:
  - route:
    - destination:
        host: worker-service.default.svc.cluster.local    
        subset: primary
        port:
          number: 80        
      weight: 100
    - destination:
        host: worker-service.default.svc.cluster.local     
        subset: burst  
        port:
          number: 80        
      weight: 0
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: worker-destination-rule
spec:
  host: worker-service
  trafficPolicy:
    loadBalancer:
      simple: RANDOM
  subsets:
  - name: primary
    labels:
      cluster-type: primary-cluster
  - name: burst
    labels:
     cluster-type: burst-cluster

מוודאים שאתם מחוברים לאשכול primary

kubectx primary

החלת istio-manifests/worker-virtualservice.yaml

kubectl apply -f istio-manifests/worker-virtualservice.yaml

כתיבת דמון istiowatcher

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

יוצרים ספרייה חדשה בשם istiowatcher ב-src

mkdir -p ${proj}/src/istiowatcher && cd ${proj}/src/istiowatcher

נתקשר אל istioctl מתוך הקונטיינר שלנו כדי לבצע מניפולציה במישור הבקרה של Istio מתוך האשכול.

כתיבת הקובץ istiowatcher.go

יוצרים קובץ בשם istiowatcher.go בתיקייה הזו ומכניסים לתוכו את התוכן הבא

package main

import (
        "github.com/tidwall/gjson"
        "io/ioutil"
        "log"
        "net/http"
        "os/exec"
        "time"
)

func main() {
        //These are in requests per second
        var targetLow float64 = 10
        var targetHigh float64 = 15
        // This is for the ticker in milliseconds
        ticker := time.NewTicker(1000 * time.Millisecond)

        isBurst := false

        // Our prometheus query
        reqQuery := `/api/v1/query?query=sum(rate(istio_requests_total{reporter="destination",destination_service="worker-service.default.svc.cluster.local",source_workload="frontend-deployment"}[15s]))by(source_workload,source_app,destination_service)`

        for t := range ticker.C {
                log.Printf("Checking Prometheus at %v", t)

                // Check prometheus
                // Note that b/c we are querying over the past 5 minutes, we are getting a very SLOW ramp of our reqs/second
                // If we wanted this to be a little "snappier" we can scale it down to say 30s
                resp, err := http.Get("http://prometheus.istio-system.svc.cluster.local:9090" + reqQuery)
                if err != nil {
                        log.Printf("Error: %v", err)
                        continue
                }
                defer resp.Body.Close()
                body, _ := ioutil.ReadAll(resp.Body)

                val := gjson.Get(string(body), "data.result.0.value.1")
                log.Printf("Value: %v", val)

                currentReqPerSecond := val.Float()
                log.Printf("Reqs per second %f", currentReqPerSecond)

                if currentReqPerSecond > targetHigh && !isBurst {
                        applyIstio("burst.yaml")
                        log.Println("Entering burst mode")
                        isBurst = true
                } else if currentReqPerSecond < targetLow && isBurst {
                        applyIstio("natural.yaml")
                        log.Println("Returning to natural state.")
                        isBurst = false
                }
        }
}

func applyIstio(filename string) {
        cmd := exec.Command("istioctl", "replace", "-f", filename)
        if err := cmd.Run(); err != nil {
                log.Printf("Error hit applying istio manifests: %v", err)
        }
}

כתיבת קובץ Dockerfile

יוצרים קובץ חדש בשם Dockerfile ומכניסים לתוכו את התוכן הבא.

FROM golang:1.11.2-stretch as base

FROM base as builder

WORKDIR /workdir
RUN curl -LO https://github.com/istio/istio/releases/download/1.0.0/istio-1.0.0-linux.tar.gz
RUN tar xzf istio-1.0.0-linux.tar.gz
RUN cp istio-1.0.0/bin/istioctl ./istioctl

FROM base 

WORKDIR /go/src/istiowatcher
COPY . .
COPY --from=builder /workdir/istioctl /usr/local/bin/istioctl

RUN go get -d -v ./...
RUN go install -v ./...

CMD ["istiowatcher"]

קובץ ה-Dockerfile הרב-שלבי הזה מוריד ומחלץ את גרסה 1.0.0 של Istio בשלב הראשון. בשלב השני, כל מה שיש בספרייה שלנו מועתק לתמונה, ואז istioctl מועתק משלב ה-build אל /usr/local/bin (כדי שאפשר יהיה לקרוא לו מהאפליקציה שלנו), יחסי התלות מתקבלים, הקוד עובר קומפילציה והערך של CMD מוגדר כ-istiowatcher.

כתיבת הקובץ burst.yaml

זהו הקובץ istiowatcher שיחול כשהבקשות לשנייה אל worker מ-frontend יעלו על 15.

יוצרים קובץ חדש בשם burst.yaml ומכניסים לתוכו את התוכן הבא.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: worker-virtual-service
spec:
 hosts:
 - worker-service
 gateways:
 - mesh
 http:
 - route:
   - destination:
       host: worker-service.default.svc.cluster.local   
       subset: primary
       port:
         number: 80       
     weight: 0
   - destination:
       host: worker-service.default.svc.cluster.local    
       subset: burst 
       port:
         number: 80       
     weight:  100

כתיבת הקובץ natural.yaml

זה יהיה המצב ה "טבעי" שאליו נחזור כשהערך של בקשות/שנייה מ-frontend אל worker יירד מתחת ל-10. במצב הזה, 100% מהתנועה מנותבת לאשכול primary.

יוצרים קובץ חדש בשם natural.yaml ומכניסים לתוכו את התוכן הבא

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: worker-virtual-service
spec:
 hosts:
 - worker-service
 gateways:
 - mesh
 http:
 - route:
   - destination:
       host: worker-service.default.svc.cluster.local   
       subset: primary
       port:
         number: 80       
     weight: 100
   - destination:
       host: worker-service.default.svc.cluster.local    
       subset: burst 
       port:
         number: 80       
     weight: 0

בנייה ודחיפה של istiowatcher

מריצים את הפקודה הבאה כדי לשלוח את הספרייה הנוכחית אל Google Cloud Build‏ (GCB), שיבנה את התמונה ויסמן אותה ב-GCR.

gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/istiowatcher

פריסת istiowatcher

עוברים לספרייה kubernetes

cd ${proj}/kubernetes/

כתיבת קובץ פריסה: istiowatcher.yaml

יוצרים קובץ בשם istiowatcher.yaml ומזינים את התוכן הבא (צריך להחליף את <your-project-id>).

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: istiowatcher-deployment
  labels:
    app: istiowatcher
spec:
  replicas: 1
  selector:
    matchLabels:
      app: istiowatcher
  template:
    metadata:
      labels:
        app: istiowatcher
    spec:
      serviceAccountName: istio-pilot-service-account
      automountServiceAccountToken: true
      containers:
      - name: istiowatcher
        image: gcr.io/<your-project-id>/istiowatcher
        imagePullPolicy: Always

Deploy

מוודאים שהאפליקציה פועלת באשכול הראשי

kubectx primary

פריסה של istiowatcher.yaml במרחב השמות istio-system

kubectl apply -n istio-system -f istiowatcher.yaml

חשוב לשים לב להוראות serviceAccountName ו-automountServiceAccountToken בקובץ ה-YAML. כך אנחנו מקבלים את פרטי הכניסה שנדרשים להפעלת istioctl מתוך האשכול.

אנחנו צריכים גם לפרוס את זה במרחב השמות istio-system כדי לוודא שיש לנו את פרטי הכניסה ל-istio-pilot-service-account. (הוא לא קיים במרחב השמות default).

רואים את התנועה עוברת אוטומטית!

ועכשיו לרגע הקסום! נעבור לקצה הקדמי שלנו וננסה להגדיל את מספר הבקשות לשנייה ל-20

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

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

18. מה השלב הבא?

ניקוי נתונים

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

אפשר למחוק את אשכולות Kubernetes, את כלל חומת האש ואת התמונות ב-GCR

gcloud container clusters delete primary --zone=us-west1-a
gcloud container clusters delete burst --zone=us-west1-a
gcloud compute firewall-rules delete istio-multicluster-test-pods 
gcloud container images delete gcr.io/$GCLOUD_PROJECT/istiowatcher

המשך הדרך