פריסת Memcached ב-GKE

במדריך הזה תלמדו איך לפרוס אשכול של שרתי Memcached מבוזרים ב-Google Kubernetes Engine ‏ (GKE) באמצעות Kubernetes,‏ Helm ו-Mcrouter. ‫Memcached היא מערכת פופולרית של קוד פתוח למטרות שונות של שמירת נתונים במטמון. הוא משמש בדרך כלל כמאגר זמני לנתונים שמשתמשים בהם בתדירות גבוהה, כדי להאיץ את פעולת אפליקציות האינטרנט ולהפחית את העומס על מסדי הנתונים.

המאפיינים של Memcached

ל-Memcached יש שתי מטרות עיצוב עיקריות:

  • פשטות: פונקציות Memcached פועלות כמו טבלת גיבוב גדולה, ומציעות API פשוט לאחסון ולאחזור של אובייקטים בכל צורה לפי מפתח.
  • מהירות: נתוני המטמון ב-Memcached נשמרים רק בזיכרון גישה אקראית (RAM), ולכן הגישה לנתונים מהירה מאוד.

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

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

התרשים הבא מציג ברמה גבוהה את האינטראקציה בין לקוח Memcached לבין מאגר מבוזר של שרתי Memcached.

אינטראקציה בין memcached לבין מאגר של שרתי memcached
איור 1: אינטראקציה ברמה גבוהה בין לקוח Memcached לבין מאגר מבוזר של שרתי Memcached.

מטרות

  • מידע על כמה מאפיינים של הארכיטקטורה המבוזרת של Memcached.
  • פריסת שירות Memcached ב-GKE באמצעות Kubernetes ו-Helm.
  • כדי לשפר את הביצועים של המערכת, אפשר לפרוס את Mcrouter, שרת proxy של Memcached בקוד פתוח.

עלויות

במסמך הזה משתמשים ברכיבים הבאים של Google Cloud, והשימוש בהם כרוך בתשלום:

  • Compute Engine

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

משתמשים חדשים של Google Cloud ? יכול להיות שאתם זכאים לתקופת ניסיון בחינם.

לפני שמתחילים

  1. נכנסים לחשבון Google Cloud . אם אתם משתמשים חדשים ב- Google Cloud, צרו חשבון כדי שתוכלו להעריך את הביצועים של המוצרים שלנו בתרחישים מהעולם האמיתי. לקוחות חדשים מקבלים בחינם גם קרדיט בשווי 300$ להרצה, לבדיקה ולפריסה של עומסי העבודה.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. Enable the Compute Engine and GKE APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  6. Verify that billing is enabled for your Google Cloud project.

  7. Enable the Compute Engine and GKE APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  8. מפעילים מופע של Cloud Shell.
    פתיחת Cloud Shell

פריסת שירות Memcached

דרך פשוטה לפרוס שירות Memcached ב-GKE היא באמצעות תרשים Helm. כדי להמשיך בהטמעה, פועלים לפי השלבים הבאים ב-Cloud Shell:

  1. יוצרים אשכול GKE חדש עם שלושה צמתים:

    gcloud container clusters create demo-cluster --num-nodes 3 --location us-central1-f
    
  2. מורידים את הארכיון הבינארי helm:

    HELM_VERSION=3.7.1
    cd ~
    wget https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
    
  3. מחלצים את קובץ הארכיון למערכת המקומית:

    mkdir helm-v${HELM_VERSION}
    tar zxfv helm-v${HELM_VERSION}-linux-amd64.tar.gz -C helm-v${HELM_VERSION}
    
  4. מוסיפים את ספריית הקובץ הבינארי helm למשתנה הסביבה PATH:

    export PATH="$(echo ~)/helm-v${HELM_VERSION}/linux-amd64:$PATH"
    

    הפקודה הזו מאפשרת לגלות את הקובץ הבינארי helm מכל ספרייה במהלך סשן Cloud Shell הנוכחי. כדי שההגדרה הזו תישמר בכמה סשנים, מוסיפים את הפקודה לקובץ ~/.bashrc של המשתמש ב-Cloud Shell.

  5. מטמיעים גרסה חדשה של תרשים Helm של Memcached עם ארכיטקטורה של זמינות גבוהה:

    helm repo add bitnami https://charts.bitnami.com/bitnami
    helm install mycache bitnami/memcached --set architecture="high-availability" --set autoscaling.enabled="true"
    

    תרשים ה-Helm של Memcached משתמש בבקר StatefulSet. אחד היתרונות בשימוש בבקר StatefulSet הוא ששמות הפודים מסודרים וניתנים לחיזוי. בדוגמה הזו, השמות הם mycache-memcached-{0..2}. הסדר הזה מקל על לקוחות Memcached להפנות לשרתים.

  6. כדי לראות את הפודים הפועלים, מריצים את הפקודה הבאה:

    kubectl get pods
    

    Google Cloud הפלט במסוף נראה כך:

    NAME                  READY     STATUS    RESTARTS   AGE
    mycache-memcached-0   1/1       Running   0          45s
    mycache-memcached-1   1/1       Running   0          35s
    mycache-memcached-2   1/1       Running   0          25s

גילוי נקודות קצה של שירות Memcached

תרשים ה-Helm של Memcached משתמש בשירות ללא כתובת IP. שירות ללא ראש חושף את כתובות ה-IP של כל הפודים שלו, כדי שאפשר יהיה לגלות אותם בנפרד.

  1. מוודאים שהשירות שנפרס הוא headless:

    kubectl get service mycache-memcached -o jsonpath="{.spec.clusterIP}"
    

    הפלט None מאשר שלשירות אין clusterIP ולכן הוא headless.

    השירות יוצר רשומת DNS לשם מארח בפורמט:

    [SERVICE_NAME].[NAMESPACE].svc.cluster.local
    

    במדריך הזה, שם השירות הוא mycache-memcached. מכיוון שלא הוגדר מרחב שמות באופן מפורש, נעשה שימוש במרחב השמות שמוגדר כברירת מחדל, ולכן שם המארח כולו הוא mycache-memcached.default.svc.cluster.local. שם המארח הזה מומר לקבוצה של כתובות IP ודומיינים עבור כל שלושת הפודים שנחשפים על ידי השירות. אם בעתיד יתווספו מאגרי פודים למאגר או יוסרו ממנו פודים ישנים, kube-dns יעדכן אוטומטית את רשומת ה-DNS.

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

  2. מאחזרים את כתובות ה-IP של נקודות הקצה:

    kubectl get endpoints mycache-memcached
    

    הפלט אמור להיראות כך:

    NAME                ENDPOINTS                                            AGE
    mycache-memcached   10.36.0.32:11211,10.36.0.33:11211,10.36.1.25:11211   3m
    

    שימו לב שלכל פוד של Memcached יש כתובת IP נפרדת, בהתאמה: 10.36.0.32, 10.36.0.33 ו-10.36.1.25. יכול להיות שכתובות ה-IP האלה יהיו שונות במופעים של השרת שלכם. כל פוד מאזין ליציאה 11211, שהיא יציאת ברירת המחדל של Memcached.

  3. כחלופה לשלב 2, אפשר לבצע בדיקת DNS באמצעות שפת תכנות כמו Python:

    1. מפעילים מסוף אינטראקטיבי של Python בתוך האשכול:

      kubectl run -it --rm python --image=python:3.10-alpine --restart=Never python
      
    2. במסוף Python, מריצים את הפקודות הבאות:

      import socket
      print(socket.gethostbyname_ex('mycache-memcached.default.svc.cluster.local'))
      exit()
      

      הפלט אמור להיראות כך:

      ('mycache-memcached.default.svc.cluster.local', ['mycache-memcached.default.svc.cluster.local'], ['10.36.0.32', '10.36.0.33', '10.36.1.25'])
  4. כדי לבדוק את הפריסה, פותחים סשן telnet עם אחד משרתי Memcached שפועלים ביציאה 11211:

    kubectl run -it --rm busybox --image=busybox:1.33 --restart=Never telnet mycache-memcached-0.mycache-memcached.default.svc.cluster.local 11211
    

    בשורת הפקודה telnet, מריצים את הפקודות הבאות באמצעות פרוטוקול ASCII של Memcached:

    set mykey 0 0 5
    hello
    get mykey
    quit

    הפלט שמתקבל מוצג כאן בטקסט מודגש:

    set mykey 0 0 5
    hello
    STORED
    get mykey
    VALUE mykey 0 5
    hello
    END
    quit

הטמעה של לוגיקה לגילוי שירותים

עכשיו אפשר להטמיע את הלוגיקה הבסיסית של גילוי שירותים שמוצגת בתרשים הבא.

<img <="" alt="service discovery logic" img="" src="/static/architecture/images/memcached-fig-2.svg" />
איור 2: לוגיקה של זיהוי שירותים.

באופן כללי, הלוגיקה של גילוי שירותים מורכבת מהשלבים הבאים:

  1. האפליקציה שולחת שאילתות אל kube-dns לגבי רשומת ה-DNS של mycache-memcached.default.svc.cluster.local.
  2. האפליקציה מאחזרת את כתובות ה-IP שמשויכות לרשומה הזו.
  3. האפליקציה יוצרת מופע של לקוח Memcached חדש ומספקת לו את כתובות ה-IP שאוחזרו.
  4. מאזן העומסים המשולב של לקוח Memcached מתחבר לשרתי Memcached בכתובות ה-IP שצוינו.

עכשיו מטמיעים את הלוגיקה של גילוי השירות באמצעות Python:

  1. פורסים פוד חדש עם Python באשכול ומתחילים סשן של מעטפת בתוך הפוד:

    kubectl run -it --rm python --image=python:3.10-alpine --restart=Never sh
    
  2. מתקינים את הספרייה pymemcache:

    pip install pymemcache
    
  3. מריצים את הפקודה python כדי להפעיל קונסולת Python אינטראקטיבית.

  4. במסוף Python, מריצים את הפקודות הבאות:

    import socket
    from pymemcache.client.hash import HashClient
    _, _, ips = socket.gethostbyname_ex('mycache-memcached.default.svc.cluster.local')
    servers = [(ip, 11211) for ip in ips]
    client = HashClient(servers, use_pooling=True)
    client.set('mykey', 'hello')
    client.get('mykey')
    

    הפלט אמור להיראות כך:

    b'hello'

    הקידומת b מציינת מחרוזת ליטרלית של בייטים, שהוא הפורמט שבו Memcached מאחסן נתונים.

  5. יוצאים ממסוף Python:

    exit()
    
  6. כדי לצאת מסשן המעטפת של ה-Pod, מקישים על Control+D.

הפעלת איגום חיבורים

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

<img <="" alt="High number of open connections when all Memcached clients access all Memcached servers directly" img="" src="/static/architecture/images/memcached-fig-3.svg" />
איור 3: מספר גבוה של חיבורים פתוחים כשכל לקוחות Memcached ניגשים ישירות לכל שרתי Memcached.

כדי לצמצם את מספר החיבורים הפתוחים, צריך להוסיף שרת proxy כדי להפעיל את התכונה connection pooling, כמו בתרשים הבא.

<img <="" alt="Proxy to enable connection pooling." img="" src="/static/architecture/images/memcached-fig-4.svg" />
איור 4: שימוש בשרת proxy כדי לצמצם את מספר החיבורים הפתוחים.

Mcrouter (מבוטא "מיק ראוטר"), פרוקסי Memcached חזק עם קוד פתוח, מאפשר איגום חיבורים. השילוב של Mcrouter הוא חלק, כי הוא משתמש בפרוטוקול Memcached ASCII הרגיל. ללקוח Memcached, ‏ Mcrouter מתנהג כמו שרת Memcached רגיל. לשרת Memcached, ‏ Mcrouter מתנהג כמו לקוח Memcached רגיל.

כדי לפרוס את Mcrouter, מריצים את הפקודות הבאות ב-Cloud Shell.

  1. מוחקים את הגרסה של תרשים Helm שהותקנה קודם:mycache

    helm delete mycache
    
  2. פורסים פודים חדשים של Memcached ופודים של Mcrouter על ידי התקנה של תרשים Helm חדש של Mcrouter:

    helm repo add stable https://charts.helm.sh/stable
    helm install mycache stable/mcrouter --set memcached.replicaCount=3
    

    תאי ה-proxy מוכנים עכשיו לקבל בקשות מאפליקציות לקוח.

  3. כדי לבדוק את ההגדרה הזו, מתחברים לאחד ממאגרי ה-proxy. משתמשים בפקודה telnet ביציאה 5000, שהיא יציאת ברירת המחדל של Mcrouter.

    MCROUTER_POD_IP=$(kubectl get pods -l app=mycache-mcrouter -o jsonpath="{.items[0].status.podIP}")
    
    kubectl run -it --rm busybox --image=busybox:1.33 --restart=Never telnet $MCROUTER_POD_IP 5000
    

    בשורת הפקודה של telnet, מריצים את הפקודות הבאות:

    set anotherkey 0 0 15
    Mcrouter is fun
    get anotherkey
    quit

    הפקודות מגדירות את הערך של המפתח ומציגות אותו.

הפריסה של ה-proxy שמאפשרת איגום חיבורים הושלמה.

הפחתת זמן האחזור

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

מיקום משותף של פודים של שרת proxy

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

<img <="" alt="topology for interactions between pods" img="" src="/static/architecture/images/memcached-fig-5.svg" />
איור 5: טופולוגיה של האינטראקציות בין פודים של אפליקציות, פודים של Mcrouter ופודים של Memcached באשכול של שלושה צמתים.

כדי לבצע את ההגדרה הזו:

  1. מוודאים שכל צומת מכיל פוד פרוקסי אחד שפועל. גישה נפוצה היא פריסת תרמילי ה-proxy באמצעות בקר DaemonSet. כשמוסיפים צמתים לאשכול, תרמילי proxy חדשים מתווספים אליהם באופן אוטומטי. כשצמתים מוסרים מהאשכול, הפודים האלה עוברים איסוף פסולת. במדריך הזה, תרשים ה-Helm של Mcrouter שפרסתם קודם משתמש בבקר DaemonSet כברירת מחדל. לכן, השלב הזה כבר הושלם.
  2. מגדירים ערך hostPort בפרמטרים של Kubernetes במאגר של ה-proxy כדי שהצומת יאזין ליציאה הזו ויפנה את התנועה אל ה-proxy. במדריך הזה, תרשים ה-Helm של Mcrouter משתמש בפרמטר הזה כברירת מחדל עבור יציאה 5000. לכן השלב הזה כבר הושלם.
  3. כדי לחשוף את שם הצומת כמשתנה סביבה בתוך תרמילי האפליקציה, משתמשים בערך spec.env ובוחרים באפשרות spec.nodeName fieldRef. מידע נוסף על השיטה הזו מופיע במאמרי העזרה בנושא Kubernetes.

    1. פריסת קבוצות Pod של אפליקציות לדוגמה. הפקודה הבאה מפעילה Kubernetes Deployment. פריסה היא אובייקט Kubernetes API שמאפשר להפעיל כמה רפליקות של Pods שמפוזרות בין הצמתים באשכול:

      cat <<EOF | kubectl create -f -
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: sample-application
      spec:
        selector:
          matchLabels:
            app: sample-application
        replicas: 9
        template:
          metadata:
            labels:
              app: sample-application
          spec:
            containers:
              - name: busybox
                image: busybox:1.33
                command: [ "sh", "-c"]
                args:
                - while true; do sleep 10; done;
                env:
                  - name: NODE_NAME
                    valueFrom:
                      fieldRef:
                        fieldPath: spec.nodeName
      EOF
      
  4. כדי לוודא ששם הצומת נחשף, בודקים באחד מפודי האפליקציה לדוגמה:

    POD=$(kubectl get pods -l app=sample-application -o jsonpath="{.items[0].metadata.name}")
    
    kubectl exec -it $POD -- sh -c 'echo $NODE_NAME'
    

    הפלט של הפקודה הזו הוא שם הצומת בפורמט הבא:

    gke-demo-cluster-default-pool-XXXXXXXX-XXXX

חיבור ה-Pods

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

  1. מתחילים חיבור לאחד מה-pods על ידי פתיחת סשן telnet:

    POD=$(kubectl get pods -l app=sample-application -o jsonpath="{.items[0].metadata.name}")
    
    kubectl exec -it $POD -- sh -c 'telnet $NODE_NAME 5000'
    
  2. בשורת הפקודה של telnet, מריצים את הפקודות הבאות:

    get anotherkey
    quit
    

    הפלט שמתקבל:

    Mcrouter is fun

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

import os
from pymemcache.client.base import Client

NODE_NAME = os.environ['NODE_NAME']
client = Client((NODE_NAME, 5000))
client.set('some_key', 'some_value')
result = client.get('some_key')

הסרת המשאבים

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

  1. מריצים את הפקודה הבאה כדי למחוק את אשכול GKE:

    gcloud container clusters delete demo-cluster --location us-central1-f
    
  2. אפשר גם למחוק את קובץ ה-Helm הבינארי:

    cd ~
    rm -rf helm-v3.7.1
    rm helm-v3.7.1-linux-amd64.tar.gz
    

המאמרים הבאים

  • כדאי לעיין בתכונות הרבות האחרות ש-Mcrouter מציע מעבר לאיגום חיבורים פשוט, כמו רפליקות של מעבר לגיבוי בעת כשל, זרמי מחיקה מהימנים, חימום מטמון קר ושידור מרובה אשכולות.
  • כדי לקבל פרטים נוספים על הגדרות Kubernetes, אפשר לעיין בקובצי המקור של תרשים Memcached ושל תרשים Mcrouter.
  • כדאי לקרוא על טכניקות יעילות לשימוש ב-Memcached ב-App Engine. חלק מההגדרות האלה חלות על פלטפורמות אחרות, כמו GKE.