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

1. היי!

תודה שהצטרפת אלינו ל-Codelab של Google בנושא Istio Multi Cloud Burst.כדי להשתתף ב-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 (מומלץ): מעטפת בדפדפן, עם כלים מותקנים
  • במחשב הנייד (פועלים לפי ההוראות הבאות)

תחילת העבודה עם 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 Cloud, לכן כדאי להגדיר משתנה סביבה לשימוש מהיר

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 באשכולות (הסטטוס שלהם אמור להיות 'מוכן'):

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. יצירת אשכול ל'פרץ פעילות'

הפקודה הבאה תיצור אשכול 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 באשכולות (הסטטוס שלהם אמור להיות 'מוכן'):

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 שמטרתו "לקשר, לאבטח, לשלוט ולנטר שירותים". הוא עושה זאת במגוון דרכים, אבל בעיקר על ידי הוספת קונטיינר proxy ( Envoy) לכל אחד מה-Pods של Kubernetes שנפרסו. מאגר ה-proxy שולט בכל תקשורת הרשת בין מיקרו-שירותים בשילוב עם מדיניות למטרות כלליות ועם מרכז טלמטריה ( Mixer).

a25613cd581825da.png

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

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

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

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

מונחי Istio שנעשה בהם שימוש

VirtualService

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

Gateway

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

DestinationRule

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

Istio Multicluster

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

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

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

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

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

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

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

Worker

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

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

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

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

חזית

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

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

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

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

e5e3b9cbede4cac4.png

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

7ae4bc22a58f80a6.png

תרשים פריסה

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

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

561db37c510944bd.png

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

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

8f6183bdfc3f813c.png

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

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

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

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

כתיבת frontend.yaml

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

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

הנקודה החשובה האחרונה בפריסה הזו היא התווית 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 Cache
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

אחרי שכל ה-pods יהיו בסטטוס '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

חזית האתר אמורה להופיע. אם מזינים מספר בתיבה 'תדירות', אמורים להופיע גיבוב (hash)

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. התוספים האלה מוסיפים למערכת Istio תמיכה ב-Prometheus וב-ServiceGraph. נשתמש בשירות Prometheus בהמשך הסדנה.

פריסה של Istio

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

kubectl create namespace istio-system

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

kubectl apply -f istio-primary.yaml

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

Istio פועל על ידי הזרקת שירות proxy של צדדים נלווים לכל אחד מהפריסות שלכם. האפשרות הזו זמינה רק אם בוחרים בה, לכן צריך לתייג את מרחב השמות default בתווית istio-injection=enabled כדי ש-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. החלת VirtualService של Redis
kubectl apply -f redis-virtualservice.yaml
  1. החלת VirtualService של Worker
kubectl apply -f worker-virtualservice.yaml

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

  1. חוזרים לספרייה kubernetes
cd ${proj}/kubernetes
  1. פריסה של Redis Cache
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

בדומה לפריסה של 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 בספרייה הנוכחית, שבו האשכולות primary ו-burst יוכלו להשתמש כדי לאמת ולנהל את האשכולות.

חזרה לאשכול הראשי

kubectx primary

החלת kubeconfig ל'פרץ', על ידי יצירת סוד וסימון שלו

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)

שינוי קובץ ה-kubeconfig ל-burst

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

החלת VirtualServices של Istio

שינוי kubeconfig ל-primary

kubectx primary

לאחר מכן, פריסה

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

איך מוודאים שהכל עובד

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

השאילתה הסופית שלנו ל-Prometheus

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

הגדרה

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

כל התנועה שמגיעה אל 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 והעברה שלו ל-Push

מריצים את הפקודה הבאה כדי לשלוח את הספרייה הנוכחית ל-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

פריסה

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

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

המשך הדרך