Déployer Memcached sur GKE


Dans ce tutoriel, vous allez apprendre à déployer un cluster de serveurs Memcached distribués sur Google Kubernetes Engine (GKE) à l'aide de Kubernetes, Helm et Mcrouter. Memcached est un système de mise en cache Open Source populaire et polyvalent. Il est généralement utilisé pour stocker à titre temporaire les données fréquemment consultées de manière à accélérer les applications Web et à alléger les charges des bases de données.

Caractéristiques de Memcached

Memcached a deux objectifs de conception principaux :

  • Simplicité : Memcached fonctionne comme une grande table de hachage. Il propose une API simple pour stocker et récupérer des objets de forme arbitraire par clé.
  • Rapidité : Memcached conserve les données en cache dans une mémoire vive (RAM) exclusivement. Vous pouvez ainsi accéder aux données beaucoup plus rapidement.

Memcached est un système distribué qui permet d'effectuer un scaling horizontal de la capacité de sa table de hachage sur un pool de serveurs. Chaque serveur Memcached fonctionne de manière totalement isolée par rapport aux autres serveurs du pool. Par conséquent, vous devez effectuer le routage et l'équilibrage de charge entre les serveurs au niveau du client. Les clients Memcached appliquent un schéma de hachage cohérent pour sélectionner les serveurs cibles de manière appropriée. Ce schéma garantit les conditions suivantes :

  • Le même serveur est toujours sélectionné pour la même clé.
  • L'utilisation de la mémoire est répartie uniformément entre les serveurs.
  • Un nombre minimal de clés est déplacé lorsque le pool de serveurs est réduit ou développé.

Le diagramme suivant illustre les interactions générales entre un client Memcached et un pool distribué de serveurs Memcached.

Interactions entre Memcached et un pool de serveurs Memcached
Figure 1 : Interactions générales entre un client Memcached et un pool distribué de serveurs Memcached

Objectifs

  • Découvrir certaines caractéristiques de l'architecture distribuée de Memcached
  • Déployer un service Memcached sur GKE à l'aide de Kubernetes et Helm
  • Déployer Mcrouter, un proxy Memcached Open Source, pour améliorer les performances du système

Coûts

Dans ce document, vous utilisez les composants facturables suivants de Google Cloud :

  • Compute Engine

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût. Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai gratuit.

Avant de commencer

  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. Démarrez une instance Cloud Shell.
    Ouvrir Cloud Shell

Déployer un service Memcached

Vous pouvez facilement déployer un service Memcached sur GKE à l'aide d'un chart Helm. Pour procéder au déploiement, suivez ces étapes dans Cloud Shell :

  1. Créez un cluster GKE avec trois nœuds :

    gcloud container clusters create demo-cluster --num-nodes 3 --zone us-central1-f
    
  2. Téléchargez l'archive binaire helm :

    HELM_VERSION=3.7.1
    cd ~
    wget https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
    
  3. Décompressez le fichier d'archive sur votre système local :

    mkdir helm-v${HELM_VERSION}
    tar zxfv helm-v${HELM_VERSION}-linux-amd64.tar.gz -C helm-v${HELM_VERSION}
    
  4. Ajoutez le répertoire du binaire helm à votre variable d'environnement PATH :

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

    Cette commande rend le binaire helm visible à partir de n'importe quel répertoire au cours de la session Cloud Shell actuelle. Pour conserver cette configuration sur plusieurs sessions, ajoutez la commande au fichier ~/.bashrc de l'utilisateur Cloud Shell.

  5. Installez une nouvelle version du chart Helm Memcached avec l'architecture haute disponibilité :

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

    Le graphique Memcached Helm utilise un contrôleur StatefulSet. L'avantage avec ce type de contrôleur est que les noms des pods sont triés et prévisibles. Dans le cas présent, les noms sont mycache-memcached-{0..2}. Cet ordre facilite le référencement des serveurs par les clients Memcached.

  6. Pour afficher les pods en cours, exécutez la commande suivante :

    kubectl get pods
    

    Le résultat de la console Google Cloud ressemble à ceci :

    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

Découvrir les points de terminaison du service Memcached

Le graphique Memcached Helm utilise un service sans adresse IP de cluster. Celui-ci expose les adresses IP de tous ses pods afin qu'ils puissent être découverts individuellement.

  1. Vérifiez que le service déployé ne contient pas d'adresse IP de cluster :

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

    Le résultat None confirme que le service n'a pas de clusterIP et qu'il n'a donc pas d'adresse IP de cluster.

    Le service crée un enregistrement DNS pour le nom d'hôte sous la forme suivante :

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

    Dans ce tutoriel, le nom du service est mycache-memcached. Étant donné qu'aucun espace de noms n'a été explicitement défini, l'espace de noms par défaut est utilisé. Le nom d'hôte complet est donc mycache-memcached.default.svc.cluster.local. Ce nom d'hôte résout un ensemble d'adresses IP et de domaines pour les trois pods exposés par le service. Si vous ajoutez ultérieurement certains pods au pool ou si vous en supprimez, kube-dns met automatiquement à jour l'enregistrement DNS.

    Il incombe au client de découvrir les points de terminaison du service Memcached, comme décrit dans les étapes suivantes.

  2. Récupérez les adresses IP des points de terminaison :

    kubectl get endpoints mycache-memcached
    

    Le résultat ressemble à ce qui suit :

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

    Notez que chaque pod Memcached a une adresse IP distincte, respectivement 10.36.0.32, 10.36.0.33 et 10.36.1.25. Ces adresses IP ne sont pas nécessairement les mêmes pour vos propres instances de serveur. Chaque pod écoute le port 11211, qui est le port par défaut de Memcached.

  3. Comme alternative à l'étape 2, vous pouvez effectuer une inspection DNS à l'aide d'un langage de programmation tel que Python :

    1. Démarrez une console interactive Python dans votre cluster :

      kubectl run -it --rm python --image=python:3.10-alpine --restart=Never python
      
    2. Dans la console Python, exécutez les commandes suivantes :

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

      Le résultat ressemble à ce qui suit :

      ('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. Testez le déploiement en ouvrant une session telnet avec l'un des serveurs Memcached en cours d'exécution sur le port 11211 :

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

    Dans l'invite de telnet, exécutez les commandes suivantes à l'aide du protocole ASCII Memcached :

    set mykey 0 0 5
    hello
    get mykey
    quit

    Le résultat obtenu est ici indiqué en gras :

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

Mettre en œuvre la logique de découverte de services

Vous êtes maintenant prêt à mettre en œuvre la logique de découverte de services fondamentale, illustrée dans le diagramme suivant.

<img <="" alt="service discovery logic" img="" src="/static/architecture/images/memcached-fig-2.svg" />
Figure 2 : Logique de découverte de services.

La logique de découverte de services comprend les étapes générales suivantes :

  1. L'application interroge kube-dns pour obtenir l'enregistrement DNS de mycache-memcached.default.svc.cluster.local.
  2. L'application récupère les adresses IP associées à cet enregistrement.
  3. L'application instancie un nouveau client Memcached et lui fournit les adresses IP récupérées.
  4. L'équilibreur de charge intégré au client Memcached se connecte aux serveurs Memcached aux adresses IP indiquées.

Vous allez maintenant mettre en œuvre cette logique de découverte de services à l'aide de Python :

  1. Déployez un nouveau pod compatible avec Python dans votre cluster, puis démarrez une session d'interface système à l'intérieur du pod :

    kubectl run -it --rm python --image=python:3.10-alpine --restart=Never sh
    
  2. Installez la bibliothèque pymemcache :

    pip install pymemcache
    
  3. Démarrez une console interactive Python à l'aide de la commande python.

  4. Dans la console Python, exécutez les commandes suivantes :

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

    Voici le résultat :

    b'hello'

    Le préfixe b correspond à un littéral d'octets. Il s'agit du format dans lequel Memcached stocke les données.

  5. Quittez la console Python :

    exit()
    
  6. Appuyez sur Control+D pour quitter la session d'interface système du pod.

Activer le regroupement de connexions

Vous pouvez rencontrer certaines limites à mesure que vos besoins en mise en cache augmentent et que des dizaines, des centaines voire des milliers de serveurs Memcached sont ajoutés au pool. Par exemple, un grand nombre de connexions ouvertes provenant de clients Memcached peut entraîner une charge importante sur les serveurs, comme le montre le diagramme suivant.

<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" />
Figure 3 : Nombre élevé de connexions ouvertes lorsque l'ensemble des clients Memcached accède directement à tous les serveurs Memcached.

Pour réduire le nombre de connexions ouvertes, vous devez utiliser un proxy afin d'activer le regroupement de connexions, comme indiqué dans le diagramme suivant.

<img <="" alt="Proxy to enable connection pooling." img="" src="/static/architecture/images/memcached-fig-4.svg" />
Figure 4 : Utilisation d'un proxy pour réduire le nombre de connexions ouvertes.

Mcrouter (prononcé "mick router") est un puissant proxy Memcached Open Source qui permet de regrouper les connexions. L'intégration de Mcrouter est transparente, car ce proxy utilise le protocole standard ASCII Memcached. Pour un client Memcached, Mcrouter se comporte comme un serveur Memcached normal. Pour un serveur Memcached, Mcrouter se comporte comme un client Memcached normal.

Pour déployer Mcrouter, exécutez les commandes suivantes dans Cloud Shell.

  1. Supprimez la version du chart Helm mycache précédemment installée :

    helm delete mycache
    
  2. Installez une nouvelle version du chart Helm Mcrouter pour déployer les nouveaux pods Memcached et Mcrouter :

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

    Les pods du proxy sont maintenant prêts à accepter les requêtes des applications clientes.

  3. Testez cette configuration en vous connectant à l'un des pods du proxy. Exécutez la commande telnet sur le port 5000, qui est le port par défaut de 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
    

    À l'invite de telnet, exécutez les commandes suivantes :

    set anotherkey 0 0 15
    Mcrouter is fun
    get anotherkey
    quit

    Les commandes définissent et rappellent la valeur de votre clé.

Vous venez de déployer un proxy permettant de regrouper des connexions.

Réduire la latence

Pour augmenter la résilience, il est courant d'utiliser un cluster avec plusieurs nœuds. Dans ce tutoriel, nous utilisons un cluster avec trois nœuds. Toutefois, l'utilisation de plusieurs nœuds entraîne un risque d'augmentation de la latence en raison du trafic réseau plus important entre les nœuds.

Héberger des pods de proxy en colocation

Vous pouvez réduire ce risque en ne connectant les pods des applications clientes qu'à un pod de proxy Memcached qui se trouve sur le même nœud. Le diagramme suivant illustre cette configuration.

<img <="" alt="topology for interactions between pods" img="" src="/static/architecture/images/memcached-fig-5.svg" />
Figure 5 : Topologie des interactions entre les pods d'applications, les pods Mcrouter et les pods Memcached sur un cluster à trois nœuds.

Effectuez cette configuration comme suit :

  1. Assurez-vous que chaque nœud contient un pod de proxy en cours d'exécution. Une méthode courante consiste à déployer les pods de proxy avec un contrôleur DaemonSet. À mesure que les nœuds sont ajoutés au cluster, de nouveaux pods de proxy leur sont automatiquement ajoutés. Lorsque les nœuds sont supprimés du cluster, ces pods sont récupérés. Le chart Helm Mcrouter que vous avez déployé précédemment dans ce tutoriel utilise un contrôleur DaemonSet par défaut. Cette tâche est donc déjà effectuée.
  2. Définissez une valeur hostPort dans les paramètres Kubernetes du conteneur du proxy pour que le nœud écoute ce port et redirige le trafic vers le proxy. Dans ce tutoriel, le chart Helm Mcrouter utilise ce paramètre par défaut pour le port 5000. Cette tâche est donc également effectuée.
  3. Exposez le nom du nœud en tant que variable d'environnement à l'intérieur des pods de l'application en utilisant l'entrée spec.env et en sélectionnant la valeur fieldRef spec.nodeName. Pour en savoir plus sur cette méthode, consultez la documentation Kubernetes.

    1. Déployez des exemples de pods d'application :

      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. Vérifiez que le nom du nœud est exposé en le recherchant dans l'un des exemples de pods d'application :

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

    Cette commande affiche le nom du nœud au format suivant :

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

Connecter les pods

Les exemples de pods d'application sont maintenant prêts à se connecter au pod Mcrouter qui s'exécute sur leurs nœuds communs respectifs sur le port 5000 (port par défaut de Mcrouter).

  1. Établissez une connexion pour l'un des pods en ouvrant une session 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. À l'invite de telnet, exécutez les commandes suivantes :

    get anotherkey
    quit
    

    Résultat :

    Mcrouter is fun

Enfin, à titre d'illustration, le code Python suivant est un exemple de programme qui établit cette connexion. Pour ce faire, il récupère la variable NODE_NAME de l'environnement et utilise la bibliothèque 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')

Nettoyer

Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet et supprimez les ressources individuelles.

  1. Exécutez la commande suivante pour supprimer le cluster GKE :

    gcloud container clusters delete demo-cluster --zone us-central1-f
    
  2. Supprimez éventuellement le binaire Helm :

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

Étape suivante

  • Découvrez les nombreuses autres fonctionnalités offertes par Mcrouter au-delà du simple regroupement de connexions, telles que les instances de basculement dupliquées, les flux de suppression fiables, le préchauffage du cache à froid et la diffusion multicluster.
  • Examinez les fichiers sources du chart Memcached et du chart Mcrouter pour en savoir plus sur les configurations Kubernetes respectives.
  • Découvrez des techniques efficaces pour utiliser Memcached sur App Engine. Certaines d'entre elles s'appliquent à d'autres plates-formes, telles que GKE.