Memcached in GKE bereitstellen


In dieser Anleitung wird gezeigt, wie Sie einen Cluster verteilter Memcached-Server in Google Kubernetes Engine (GKE) mit Kubernetes, Helm und Mcrouter bereitstellen. Memcached zählt zu den beliebtesten Open-Source-Multifunktions-Caching-Systemen. Es dient in der Regel als Zwischenspeicher für häufig verwendete Daten, um Webanwendungen zu beschleunigen und Datenbanklasten zu reduzieren.

Eigenschaften von Memcached

Memcached hat zwei wesentliche Designziele:

  • Einfachheit: Memcached funktioniert wie eine große Hash-Tabelle. Beliebig gestaltete Objekte können mit einer einfachen API gespeichert und nach Schlüssel abgerufen werden.
  • Geschwindigkeit: Memcached speichert Daten ausschließlich in RAM und ermöglicht dadurch einen extrem schnellen Datenzugriff.

Memcached ist ein verteiltes System, das die horizontale Skalierung seiner Hash-Tabellen-Kapazität über einen Pool von Servern ermöglicht. Jeder Memcached-Server wird komplett von den anderen Servern im Pool isoliert betrieben. Das Routing und der Lastenausgleich zwischen den Servern muss daher auf der Clientebene erfolgen. Memcached-Clients wenden ein konsistentes Hash-Schema an, um die entsprechenden Zielserver auszuwählen. Dieses Schema garantiert die folgenden Bedingungen:

  • Es wird immer derselbe Server für denselben Schlüssel ausgewählt.
  • Die Speichernutzung wird gleichmäßig auf die Server verteilt.
  • Bei einer Reduzierung oder Erweiterung des Serverpools wird eine Mindestanzahl von Schlüsseln verlagert.

Im folgenden Diagramm erhalten Sie einen Überblick über die Interaktion zwischen einem Memcached-Client und einem verteilten Pool von Memcached-Servern.

Interaktion zwischen Memcached und einen Pool mit Memcache-Servern
Abbildung 1: Übersicht der Interaktion zwischen einem Memcached-Client und einem verteilten Pool von Memcached-Servern.

Ziele

  • Machen Sie sich mit den Eigenschaften der verteilten Memcached-Architektur vertraut.
  • Erstellen Sie mit Kubernetes und Helm einen Memcached-Dienst in GKE.
  • Erstellen Sie zur Verbesserung der Systemleistung den Open-Source-Memcached-Proxy Mcrouter.

Kosten

In diesem Dokument verwenden Sie die folgenden kostenpflichtigen Komponenten von Google Cloud:

  • Compute Engine

Mit dem Preisrechner können Sie eine Kostenschätzung für Ihre voraussichtliche Nutzung vornehmen. Neuen Google Cloud-Nutzern steht möglicherweise eine kostenlose Testversion zur Verfügung.

Hinweis

  1. Melden Sie sich bei Ihrem Google Cloud-Konto an. Wenn Sie mit Google Cloud noch nicht vertraut sind, erstellen Sie ein Konto, um die Leistungsfähigkeit unserer Produkte in der Praxis sehen und bewerten zu können. Neukunden erhalten außerdem ein Guthaben von 300 $, um Arbeitslasten auszuführen, zu testen und bereitzustellen.
  2. Wählen Sie in der Google Cloud Console auf der Seite der Projektauswahl ein Google Cloud-Projekt aus oder erstellen Sie eines.

    Zur Projektauswahl

  3. Die Abrechnung für das Google Cloud-Projekt muss aktiviert sein.

  4. Compute Engine and GKE APIs aktivieren.

    Aktivieren Sie die APIs

  5. Wählen Sie in der Google Cloud Console auf der Seite der Projektauswahl ein Google Cloud-Projekt aus oder erstellen Sie eines.

    Zur Projektauswahl

  6. Die Abrechnung für das Google Cloud-Projekt muss aktiviert sein.

  7. Compute Engine and GKE APIs aktivieren.

    Aktivieren Sie die APIs

  8. Starten Sie eine Cloud Shell-Instanz.
    Zu Cloud Shell

Memcached-Dienst bereitstellen

Eine einfache Möglichkeit, um einen Memcached-Dienst in GKE bereitzustellen, ist die Verwendung eines Helm-Diagramms. Führen Sie für das Deployment die folgenden Schritte in Cloud Shell aus:

  1. Erstellen Sie einen neuen GKE-Cluster mit drei Knoten:

    gcloud container clusters create demo-cluster --num-nodes 3 --zone us-central1-f
    
  2. Laden Sie das Binärarchiv helm herunter:

    HELM_VERSION=3.7.1
    cd ~
    wget https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
    
  3. Entpacken Sie die Archivdatei in Ihr lokales System:

    mkdir helm-v${HELM_VERSION}
    tar zxfv helm-v${HELM_VERSION}-linux-amd64.tar.gz -C helm-v${HELM_VERSION}
    
  4. Fügen Sie das Verzeichnis der helm-Binärdatei Ihrer PATH-Umgebungsvariablen hinzu:

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

    Dadurch wird die helm-Binärdatei während der aktuellen Cloud Shell-Sitzung von jedem beliebigen Verzeichnis aus sichtbar. Damit diese Konfiguration über mehrere Sitzungen hinweg bestehen bleibt, müssen Sie den Befehl in die Datei ~/.bashrc des Cloud Shell-Nutzers einfügen.

  5. Installieren Sie eine neue Memcached-Helm-Diagrammversion mit der Hochverfügbarkeitsarchitektur:

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

    Das Memcached-Helm-Diagramm verwendet einen StatefulSet-Controller. Ein Vorteil bei der Verwendung eines StatefulSet-Controllers ist, dass die Namen der Pods geordnet und vorhersagbar sind. In diesem Fall lauten die Namen mycache-memcached-{0..2}. Diese Reihenfolge erleichtert Memcached-Clients den Verweis auf die Server.

  6. Führen Sie den folgenden Befehl aus, um die aktiven Pods anzeigen zu lassen:

    kubectl get pods
    

    Die Ausgabe der Google Cloud Console sieht so aus:

    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-Dienstendpunkte ermitteln

Das Memcached-Helm-Diagramm verwendet einen monitorlosen Dienst. Ein monitorloser Dienst stellt IP-Adressen für alle seine Pods bereit, sodass diese einzeln ermittelt werden können.

  1. Überprüfen Sie, ob der bereitgestellte Dienst monitorlos ist:

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

    Durch die Ausgabe None wird bestätigt, dass der Dienst keine clusterIP hat und daher monitorlos ist.

    Der Dienst erstellt einen DNS-Eintrag für einen Hostnamen in folgendem Format:

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

    In dieser Anleitung wird als Dienstname mycache-memcached verwendet. Da kein Namespace explizit definiert wurde, wird der Standard-Namespace verwendet. Somit lautet der gesamte Hostname mycache-memcached.default.svc.cluster.local. Dieser Hostname wird in eine Gruppe von IP-Adressen und Domains für alle drei vom Dienst bereitgestellten Pods aufgelöst. Wenn später weitere Pods im Pool hinzugefügt oder entfernt werden, wird der DNS-Eintrag automatisch durch kube-dns aktualisiert.

    Für die Ermittlung der Memcached-Dienstendpunkte ist der Client zuständig. Die Vorgehensweise wird in den folgenden Schritten erläutert.

  2. Rufen Sie die IP-Adressen der Endpunkte ab:

    kubectl get endpoints mycache-memcached
    

    Die Ausgabe sieht etwa so aus:

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

    Beachten Sie, dass jeder Memcached-Pod eine separate IP-Adresse hat: 10.36.0.32, 10.36.0.33 bzw. 10.36.1.25. Diese IP-Adressen können bei Ihren eigenen Serverinstanzen anders sein. Alle Pods hören den Memcached-Standardport 11211 ab.

  3. Alternativ zu Schritt 2 können Sie eine DNS-Prüfung mithilfe einer Programmiersprache wie Python ausführen:

    1. Starten Sie in Ihrem Cluster eine interaktive Python-Konsole:

      kubectl run -it --rm python --image=python:3.10-alpine --restart=Never python
      
    2. Führen Sie in der Python-Konsole folgende Befehle aus:

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

      Die Ausgabe sieht etwa so aus:

      ('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. Starten Sie eine telnet-Sitzung mit einem der ausgeführten Memcached-Server an Port 11211, um das Deployment zu testen:

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

    Führen Sie an der telnet-Eingabeaufforderung folgende Befehle mit dem Memcached-ASCII-Protokoll aus:

    set mykey 0 0 5
    hello
    get mykey
    quit

    Die Ausgabe ist hier fett dargestellt:

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

Diensterkennungslogik implementieren

Sie können jetzt die im folgenden Diagramm gezeigte grundlegende Diensterkennungslogik implementieren.

Diensterkennungslogik
Abbildung 2: Diensterkennungslogik.

Auf übergeordneter Ebene besteht die Diensterkennungslogik aus den folgenden Schritten:

  1. Die Anwendung fragt den DNS-Eintrag von mycache-memcached.default.svc.cluster.local bei kube-dns ab.
  2. Sie ruft die damit verknüpften IP-Adressen ab.
  3. Die Anwendung instanziiert einen neuen Memcached-Client und stellt diesen mit den abgerufenen IP-Adressen zur Verfügung.
  4. Der in den Memcached-Client integrierte Load-Balancer stellt über die gegebenen IP-Adressen eine Verbindung zu den Memcached-Servern her.

Diese Diensterkennungslogik implementieren Sie jetzt mithilfe von Python:

  1. Erstellen Sie in Ihrem Cluster einen neuen Python-fähigen Pod und starten Sie eine Shell-Sitzung im Pod:

    kubectl run -it --rm python --image=python:3.10-alpine --restart=Never sh
    
  2. Installieren Sie die Bibliothek pymemcache:

    pip install pymemcache
    
  3. Starten Sie mit dem Befehl python eine interaktive Python-Konsole.

  4. Führen Sie in der Python-Konsole folgende Befehle aus:

    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')
    

    Die Ausgabe sieht so aus:

    b'hello'

    Das Präfix b kennzeichnet ein Byte-Literal. Dies ist das Format, in dem Daten in Memcached gespeichert werden.

  5. Beenden Sie die Python-Konsole:

    exit()
    
  6. Drücken Sie Control+D , um die Shell-Sitzung des Pods zu beenden.

Verbindungs-Pooling aktivieren

Mit steigenden Caching-Anforderungen und einem auf Dutzende, Hunderte oder Tausende Memcached-Server anwachsenden Pool stoßen Sie möglicherweise an gewisse Grenzen. Insbesondere die große Anzahl offener Verbindungen von Memcached-Clients kann für die Server zu einer starken Belastung werden, wie im folgenden Diagramm zu sehen ist.

Hohe Anzahl offener Verbindungen, wenn alle Memcached-Clients direkt auf alle Memcached-Server zugreifen
Abbildung 3: Hohe Anzahl offener Verbindungen, wenn alle Memcached-Clients direkt auf alle Memcached-Server zugreifen.

Führen Sie einen Proxy ein, der ein Verbindungs-Pooling wie im folgenden Diagramm ermöglicht, um die Anzahl der offenen Verbindungen zu reduzieren.

Proxy zur Aktivierung des Verbindungs-Poolings
Abbildung 4: Mit einem Proxy die Anzahl der offenen Verbindungen reduzieren.

Mcrouter (gesprochen "Mick Router"), ein leistungsfähiger Open Source Memcached-Proxy, ermöglicht das Verbindungs-Pooling. Die Integration von Mcrouter erfolgt aufgrund des Memcached-ASCII-Standardprotokolls nahtlos. Aus Sicht eines Memcached-Clients verhält sich Mcrouter wie ein normaler Memcached-Server. Aus Sicht eines Memcached-Servers verhält sich Mcrouter wie ein normaler Memcached-Client.

Führen Sie die folgenden Befehle in Cloud Shell aus, um Mcrouter bereitzustellen.

  1. Löschen Sie die zuvor installierte mycache-Helm-Diagrammversion:

    helm delete mycache
    
  2. Erstellen Sie neue Memcached-Pods und Mcrouter-Pods, indem Sie eine neue Mcrouter-Helm-Diagrammversion installieren:

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

    Die Proxy-Pods können jetzt Anfragen von Clientanwendungen annehmen.

  3. Testen Sie diese Einrichtung, indem Sie eine Verbindung zu einem der Proxy-Pods herstellen. Führen Sie den Befehl telnet am Mcrouter-Standardport 5000 aus.

    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
    

    Geben Sie folgende Befehle in die telnet-Eingabeaufforderung ein:

    set anotherkey 0 0 15
    Mcrouter is fun
    get anotherkey
    quit

    Mit den Befehlen wird der Wert Ihres Schlüssels festgelegt und zurückgegeben.

Sie haben jetzt einen Proxy bereitgestellt, der ein Verbindungs-Pooling ermöglicht.

Latenz reduzieren

Die Robustheit wird in der Regel durch einen Cluster mit mehreren Knoten gesteigert. In dieser Anleitung verwenden wir einen Cluster mit drei Knoten. Werden mehrere Knoten verwendet, kann sich jedoch aufgrund des erhöhten Netzwerkverkehrs zwischen den Knoten auch die Latenz erhöhen.

Proxy-Pods am selben Standort platzieren

Wenn Sie Clientanwendungs-Pods nur mit einem Memcached-Proxy-Pod auf demselben Knoten verbinden, können Sie die Latenz verringern. Diese Konfiguration wird im folgenden Diagramm dargestellt.

Topologie für Interaktionen zwischen Pods
Abbildung 5: Topologie der Interaktionen zwischen Anwendungs-Pods, Mcrouter-Pods und Memcached-Pods in einem Cluster mit drei Knoten.

So erstellen Sie diese Konfiguration:

  1. Wichtig ist, dass jeder Knoten einen aktiven Proxy-Pod enthält. Ein gängiger Ansatz besteht darin, die Proxy-Pods mit einem DaemonSet-Controller zu erstellen. Mit weiteren Knoten werden dem Cluster auch automatisch neue Proxy-Pods hinzugefügt. Diese Pods werden beim Entfernen von Knoten aus dem Cluster bereinigt. In dieser Anleitung wird für das zuvor erstellte Mcrouter-Helm-Diagramm standardmäßig ein DaemonSet-Controller verwendet. Dieser Schritt ist somit bereits erledigt.
  2. Legen Sie in den Kubernetes-Parametern des Proxycontainers den Wert hostPort fest, damit der Knoten diesen Port überwacht und Traffic an den Proxy weiterleitet. In dieser Anleitung wird der Parameter im Mcrouter-Helm-Diagramm standardmäßig für Port 5000 verwendet. Dieser Schritt ist somit ebenfalls bereits erledigt.
  3. Stellen Sie den Knotennamen innerhalb der Anwendungs-Pods als Umgebungsvariable bereit. Verwenden Sie hierfür den Eintrag spec.env und wählen Sie den Wert spec.nodeName fieldRef aus. Weitere Informationen zu dieser Methode finden Sie in der Kubernetes-Dokumentation.

    1. Stellen Sie einige Anwendungs-Pod-Beispiele bereit:

      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. Überprüfen Sie durch einen Blick in eines der Anwendungs-Pod-Beispiele, ob der Knotenname bereitgestellt wurde:

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

    Mit diesem Befehl wird der Knotenname in folgendem Format ausgegeben:

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

Pods verbinden

Die Anwendungs-Pod-Beispiele können jetzt mit dem Mcrouter-Pod verbunden werden, der auf den jeweils gegenseitigen Knoten am Mcrouter-Standardport 5000 ausgeführt wird.

  1. Starten Sie eine telnet-Sitzung, um für einen der Pods eine Verbindung herzustellen:

    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. Geben Sie folgende Befehle in die telnet-Eingabeaufforderung ein:

    get anotherkey
    quit
    

    Ausgabe:

    Mcrouter is fun

Der folgende Python-Code ist ein Beispielprogramm, das diese Verbindung durch Abrufen der Variablen NODE_NAME aus der Umgebung und mithilfe der Bibliothek pymemcache ausführt:

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')

Bereinigen

Damit Ihrem Google Cloud-Konto die in dieser Anleitung verwendeten Ressourcen nicht in Rechnung gestellt werden, löschen Sie entweder das Projekt, das die Ressourcen enthält, oder Sie behalten das Projekt und löschen die einzelnen Ressourcen.

  1. Führen Sie diesen Befehl aus, um den GKE-Cluster zu löschen:

    gcloud container clusters delete demo-cluster --zone us-central1-f
    
  2. Löschen Sie optional die Helm-Binärdatei:

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

Weitere Informationen

  • Weitere Informationen zu den zahlreichen Mcrouter-Funktionen, die über das einfache Verbindungs-Pooling hinaus gehen, wie Failover-Replikate, zuverlässige Lösch-Streams, Cold-Cache-Aufwärmphase und Multi-Cluster-Übertragung
  • Weitere Details zu den jeweiligen Kubernetes-Konfigurationen in den Quelldateien des Memcached-Diagramms und des Mcrouter-Diagramms
  • Weitere Informationen zu effektiven Techniken für die Verwendung von Memcached in App Engine Manche davon gelten auch für andere Plattformen wie GKE.