Deployment di Memcached su GKE


In questo tutorial imparerai a eseguire il deployment di un cluster di server Memcached distribuiti su Google Kubernetes Engine (GKE) utilizzando Kubernetes, Helm e Mcrouter. Memcached è un popolare sistema di memorizzazione nella cache multiuso open source. In genere funge da spazio di archiviazione temporaneo per i dati di uso frequente per velocizzare le applicazioni web e alleggerire i carichi del database.

Caratteristiche di Memcached

Memcached ha due obiettivi di progettazione principali:

  • Semplicità: Memcached funziona come una grande tabella hash e offre un'API semplice per memorizzare e recuperare oggetti di forma arbitraria per chiave.
  • Velocità: Memcached memorizza i dati della cache esclusivamente nella memoria ad accesso casuale (RAM), rendendo l'accesso ai dati estremamente rapido.

Memcached è un sistema distribuito che consente di scalare la capacità della tabella hash in modo orizzontale in un pool di server. Ogni server Memcached opera in completo isolamento dagli altri server del pool. Pertanto, il routing e il bilanciamento del carico tra i server devono essere eseguiti a livello di client. I client Memcached applicano un schema di hashing coerente per selezionare in modo appropriato i server di destinazione. Questo schema garantisce le seguenti condizioni:

  • Per la stessa chiave viene sempre selezionato lo stesso server.
  • L'utilizzo della memoria è bilanciato uniformemente tra i server.
  • Un numero minimo di chiavi viene spostato quando il pool di server viene ridotto o ampliato.

Il seguente diagramma illustra a grandi linee l'interazione tra un client Memcached e un pool distribuito di server Memcached.

interazione tra memcached e un pool di server memcached
Figura 1: interazione di alto livello tra un client Memcached e un pool distribuito di server Memcached.

Obiettivi

  • Scopri alcune caratteristiche dell'architettura distribuita di Memcached.
  • Esegui il deployment di un servizio Memcached su GKE utilizzando Kubernetes e Helm.
  • Esegui il deployment di Mcrouter, un proxy Memcached open source, per migliorare le prestazioni del sistema.

Costi

In questo documento utilizzi i seguenti componenti fatturabili di Google Cloud:

  • Compute Engine

Per generare una stima dei costi in base all'utilizzo previsto, utilizza il Calcolatore prezzi. I nuovi utenti di Google Cloud potrebbero avere diritto a una prova gratuita.

Prima di iniziare

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Compute Engine and GKE APIs.

    Enable the APIs

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

    Go to project selector

  6. Make sure that billing is enabled for your Google Cloud project.

  7. Enable the Compute Engine and GKE APIs.

    Enable the APIs

  8. Avvia un'istanza Cloud Shell.
    Apri Cloud Shell

Deployment di un servizio Memcached

Un modo semplice per eseguire il deployment di un servizio Memcached in GKE è utilizzare un chart Helm. Per procedere con il deployment, segui questi passaggi in Cloud Shell:

  1. Crea un nuovo cluster GKE di tre nodi:

    gcloud container clusters create demo-cluster --num-nodes 3 --zone us-central1-f
    
  2. Scarica l'archivio binario helm:

    HELM_VERSION=3.7.1
    cd ~
    wget https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
    
  3. Decomprimi il file dell'archivio sul sistema locale:

    mkdir helm-v${HELM_VERSION}
    tar zxfv helm-v${HELM_VERSION}-linux-amd64.tar.gz -C helm-v${HELM_VERSION}
    
  4. Aggiungi la directory del file binario helm alla variabile di ambiente PATH:

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

    Questo comando rende il file binario helm rilevabile da qualsiasi directory durante la sessione Cloud Shell corrente. Per rendere permanente questa configurazione tra più sessioni, aggiungi il comando al file ~/.bashrc dell'utente Cloud Shell.

  5. Installa una nuova release del grafico Helm Memcached con l'architettura ad alta disponibilità:

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

    Il grafico Helm di Memcached utilizza un controller StatefulSet. Uno dei vantaggi dell'utilizzo di un controller StatefulSet è che i nomi dei pod sono ordinati e prevedibili. In questo caso, i nomi sono mycache-memcached-{0..2}. Questo ordinamento consente ai client Memcached di fare riferimento ai server più facilmente.

  6. Per visualizzare i pod in esecuzione, esegui il seguente comando:

    kubectl get pods
    

    L'output della console Google Cloud è il seguente:

    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

Rilevamento degli endpoint di servizio Memcached

Il grafico Helm Memcached utilizza un servizio headless. Un servizio senza interfaccia espone gli indirizzi IP di tutti i suoi pod in modo che possano essere rilevati singolarmente.

  1. Verifica che il servizio di cui è stato eseguito il deployment sia headless:

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

    L'output None conferma che il servizio non ha clusterIP e che è quindi headless.

    Il servizio crea un record DNS per un nome host del seguente tipo:

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

    In questo tutorial, il nome del servizio è mycache-memcached. Poiché non è stato definito esplicitamente uno spazio dei nomi, viene utilizzato lo spazio dei nomi predefinito e quindi l'intero nome host è mycache-memcached.default.svc.cluster.local. Questo nome host risolve in un insieme di indirizzi IP e domini per tutti e tre i pod esposti dal servizio. Se in futuro alcuni pod vengono aggiunti al pool o quelli vecchi vengono rimossi, kube-dns aggiornerà automaticamente il record DNS.

    È responsabilità del client rilevare gli endpoint del servizio Memcached, come descritto nei passaggi successivi.

  2. Recupera gli indirizzi IP degli endpoint:

    kubectl get endpoints mycache-memcached
    

    L'output è simile al seguente:

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

    Tieni presente che ogni pod Memcached ha un indirizzo IP separato, rispettivamente10.36.0.32, 10.36.0.33 e 10.36.1.25. Questi indirizzi IP potrebbero essere diversi per le tue istanze server. Ogni pod ascolta la porta 11211, che è la porta predefinita di Memcached.

  3. Per un'alternativa al passaggio 2, esegui un'ispezione DNS utilizzando un linguaggio di programmazione come Python:

    1. Avvia una console interattiva Python all'interno del cluster:

      kubectl run -it --rm python --image=python:3.10-alpine --restart=Never python
      
    2. Nella console Python, esegui questi comandi:

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

      L'output è simile al seguente:

      ('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. Testa il deployment aprendo una sessione telnet con uno dei server Memcached in esecuzione sulla porta 11211:

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

    Al prompt telnet, esegui questi comandi utilizzando il protocollo ASCII Memcached:

    set mykey 0 0 5
    hello
    get mykey
    quit

    L'output risultante è mostrato qui in grassetto:

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

Implementazione della logica di Service Discovery

Ora è tutto pronto per implementare la logica di Service Discovery di base mostrata nel diagramma seguente.

<img <="" alt="service discovery logic" img="" src="/static/architecture/images/memcached-fig-2.svg" />
Figura 2: logica di discovery dei servizi.

A livello generale, la logica di Service Discovery è costituita dai seguenti passaggi:

  1. L'applicazione esegue query su kube-dns per il record DNS di mycache-memcached.default.svc.cluster.local.
  2. L'applicazione recupera gli indirizzi IP associati a quel record.
  3. L'applicazione esegue l'inizializzazione di un nuovo client Memcached e fornisce gli indirizzi IP recuperati.
  4. Il bilanciatore del carico integrato del client Memcached si connette ai server Memcached agli indirizzi IP specificati.

Ora implementa questa logica di Service Discovery utilizzando Python:

  1. Esegui il deployment di un nuovo pod compatibile con Python nel cluster e avvia una sessione di shell all'interno del pod:

    kubectl run -it --rm python --image=python:3.10-alpine --restart=Never sh
    
  2. Installa la libreria pymemcache:

    pip install pymemcache
    
  3. Avvia una console interattiva Python eseguendo il comando python.

  4. Nella console Python, esegui questi comandi:

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

    L'output è il seguente:

    b'hello'

    Il prefisso b indica un letterale in byte, che è il formato in cui Memcached memorizza i dati.

  5. Esci dalla console Python:

    exit()
    
  6. Per uscire dalla sessione shell del pod, premi Control+D.

Attivazione del pool di connessioni

Man mano che le tue esigenze di memorizzazione nella cache aumentano e il pool viene scalato fino a decine, centinaia o persino migliaia di server Memcached, potresti riscontrare alcuni limiti. In particolare, il numero elevato di connessioni aperte dei client Memcached potrebbe comportare un carico elevato sui server, come mostrato nel seguente diagramma.

<img <="" alt="Numero elevato di connessioni aperte quando tutti i client Memcached accedono direttamente a tutti i server Memcached" img="" src="/static/architecture/images/memcached-fig-3.svg" />
Figura 3: numero elevato di connessioni aperte quando tutti i client Memcached accedono direttamente a tutti i server Memcached.

Per ridurre il numero di connessioni aperte, devi introdurre un proxy per abilitare il pooling delle connessioni, come nel seguente diagramma.

<img <="" alt="Proxy per abilitare il pooling delle connessioni." img="" src="/static/architecture/images/memcached-fig-4.svg" />
Figura 4: utilizzo di un proxy per ridurre il numero di connessioni aperte.

Mcrouter (pronunciato "mick router"), un potente proxy Memcached open source, consente il pooling delle connessioni. L'integrazione di Mcrouter è semplice, perché utilizza il protocollo ASCII Memcached standard. Per un client Memcached, Mcrouter si comporta come un normale server Memcached. Per un server Memcached, Mcrouter si comporta come un normale client Memcached.

Per eseguire il deployment di Mcrouter, esegui i seguenti comandi in Cloud Shell.

  1. Elimina la release del grafico Helm mycache installata in precedenza:

    helm delete mycache
    
  2. Esegui il deployment di nuovi pod Memcached e Mcrouter installando una nuova release del grafico Helm Mcrouter:

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

    I pod proxy sono ora pronti per accettare le richieste dalle applicazioni client.

  3. Testa questa configurazione connettendoti a uno dei pod proxy. Utilizza il comando telnet sulla porta 5000, che è la porta predefinita di 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
    

    Nel prompt telnet, esegui questi comandi:

    set anotherkey 0 0 15
    Mcrouter is fun
    get anotherkey
    quit

    I comandi impostano ed emettono il valore della chiave.

Ora hai implementato un proxy che abilita il pooling delle connessioni.

Riduzione della latenza

Per aumentare la resilienza, è prassi comune utilizzare un cluster con più nodi. Questo tutorial utilizza un cluster con tre nodi. Tuttavia, l'utilizzo di più nodi comporta anche il rischio di un aumento della latenza causato da un maggiore traffico di rete tra i nodi.

Colocazione in comune dei pod proxy

Puoi ridurre questo rischio collegando i pod delle applicazioni client solo a un pod proxy Memcached nello stesso nodo. Il seguente diagramma illustra questa configurazione.

<img <="" alt="topology for interactions between pods" img="" src="/static/architecture/images/memcached-fig-5.svg" />
Figura 5: topologia per le interazioni tra i pod di applicazioni, i pod Mcrouter e i pod Memcached in un cluster di tre nodi.

Esegui questa configurazione nel seguente modo:

  1. Assicurati che ogni nodo contenga un pod proxy in esecuzione. Un approccio comune è eseguire il deployment dei pod proxy con un controller DaemonSet. Man mano che i nodi vengono aggiunti al cluster, vengono aggiunti automaticamente nuovi pod proxy. Quando i nodi vengono rimossi dal cluster, i pod vengono sottoposti al garbage collection. In questo tutorial, il grafico Helm Mcrouter che hai eseguito in precedenza utilizza un controller DaemonSet per impostazione predefinita. Quindi, questo passaggio è già stato completato.
  2. Imposta un valore hostPort nei parametri Kubernetes del contenitore proxy per far sì che il nodo ascolti quella porta e reindirizzi il traffico al proxy. In questo tutorial, il grafico Helm di Mcrouter utilizza questo parametro per impostazione predefinita per la porta 5000. Quindi anche questo passaggio è già stato completato.
  3. Esponi il nome del nodo come variabile di ambiente all'interno dei pod dell'applicazione utilizzando la voce spec.env e selezionando il valore spec.nodeName fieldRef. Scopri di più su questo metodo nella documentazione di Kubernetes.

    1. Esegui il deployment di pod di applicazioni di esempio. Il seguente comando applica un deployment Kubernetes. Un deployment è un oggetto dell'API Kubernetes che ti consente di eseguire più repliche di pod distribuite tra i nodi di un cluster:

      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. Verifica che il nome del nodo sia esposto controllando uno dei pod di applicazioni di esempio:

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

    Questo comando restituisce il nome del nodo nel seguente formato:

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

Collegamento dei pod

I pod di applicazione di esempio sono ora pronti per connettersi al pod Mcrouter che viene eseguito sui rispettivi nodi comuni sulla porta 5000, che è la porta predefinita di Mcrouter.

  1. Avvia una connessione per uno dei pod aprendo una sessione 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. Nel prompt telnet, esegui questi comandi:

    get anotherkey
    quit
    

    Output risultante:

    Mcrouter is fun

Infine, come illustrazione, il seguente codice Python è un programma di esempio che esegue questa connessione recuperando la variabile NODE_NAME dall'ambiente e utilizzando la libreria 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')

Esegui la pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo tutorial, elimina il progetto che contiene le risorse oppure mantieni il progetto ed elimina le singole risorse.

  1. Esegui il seguente comando per eliminare il cluster GKE:

    gcloud container clusters delete demo-cluster --zone us-central1-f
    
  2. Se vuoi, elimina il file binario Helm:

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

Passaggi successivi

  • Scopri le molte altre funzionalità che Mcrouter offre oltre al semplice pooling delle connessioni, come le repliche di failover, gli stream di eliminazione affidabili, il riscaldamento della cache a freddo e la trasmissione in multi-cluster.
  • Esplora i file di origine del grafico Memcached e del grafico Mcrouter per ulteriori dettagli sulle rispettive configurazioni di Kubernetes.
  • Scopri le tecniche efficaci per utilizzare Memcached su App Engine. Alcune si applicano ad altre piattaforme, come GKE.