Vérifier la résilience du cluster Valkey sur Azure Kubernetes Service (AKS)

Ce guide montre comment valider la résilience d’un cluster Valkey déployé sur Azure Kubernetes Service (AKS) à l’aide de l’infrastructure de test de charge locust. Il décrit la création d’un client de test, son déploiement sur AKS, la simulation d’échecs et l’analyse du comportement du cluster.

Remarque

Cet article contient des références au terme master (principal), qui est un terme que Microsoft n’utilise plus. Lorsque le terme sera supprimé du logiciel Valkey, nous le supprimerons de cet article.

Créer un exemple d’application cliente pour Valkey

Les étapes suivantes montrent comment générer un exemple d’application cliente pour Valkey.

L’exemple d’application cliente utilise l’infrastructure de test de charge Locust pour simuler une charge de travail sur le cluster Valkey que vous avez configuré et déployé. Le code Python implémente une classe Locust User qui se connecte au cluster Valkey et effectue une opération set et get . Vous pouvez développer cette classe pour implémenter des opérations plus complexes.

Remarque

Nous vous recommandons d’utiliser le flux d’authentification le plus sécurisé disponible. Le flux d’authentification décrit dans cette procédure nécessite un niveau de confiance très élevé dans l’application et comporte des risques qui ne sont pas présents dans d’autres flux. Utilisez ce flux seulement si d’autres flux plus sécurisés, tels que les identités managées, ne sont pas viables.

  1. Créez le fichier Dockerfile et requirements.txt placez-les dans un nouveau répertoire à l’aide des commandes suivantes :

    mkdir valkey-client
    cd valkey-client
    
    cat > Dockerfile <<EOF
    FROM python:3.10-slim-bullseye
    COPY requirements.txt .
    COPY locustfile.py .
    RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
    EOF
    
    cat > requirements.txt <<EOF
    valkey
    locust
    EOF
    
  2. Créez le locustfile.py fichier qui contient le code de l’application cliente Valkey :

    cat > locustfile.py <<EOF
    import time
    from locust import between, task, User, events,tag, constant_throughput
    from valkey import ValkeyCluster
    from random import randint
    
    class ValkeyLocust(User):
        wait_time = constant_throughput(50)
        host = "valkey-cluster.valkey.svc.cluster.local"
        def __init__(self, *args, **kwargs):
            super(ValkeyLocust, self).__init__(*args, **kwargs)
            self.client = ValkeyClient(host=self.host)
        def on_stop(self):
            self.client.close()
        @task
        @tag("set")
        def set_value(self):
            self.client.set_value("set_value")
        @task
        @tag("get")
        def get_value(self):
            self.client.get_value("get_value")
    
    class ValkeyClient(object):
        def __init__(self, host, *args, **kwargs):
            super().__init__(*args, **kwargs)
            with open("/etc/valkey-password/valkey-password-file.conf", "r") as f:
                self.password = f.readlines()[0].split(" ")[1].strip()
            self.host = host
            self.vc = ValkeyCluster(host=self.host,
                                    port=6379,
                                    password=self.password,
                                    username="default",
                                    cluster_error_retry_attempts=0,
                                    socket_timeout=2,
                                    keepalive=1
                                    )
    
        def set_value(self, key, command='SET'):
            start_time = time.perf_counter()
            try:
                result = self.vc.set(randint(0, 1000), randint(0, 1000))
                if not result:
                    result = ''
                length = len(str(result))
                total_time = (time.perf_counter()- start_time) * 1000
                events.request.fire(
                    request_type=command,
                    name=key,
                    response_time=total_time,
                    response_length=length,
                )
            except Exception as e:
                total_time = (time.perf_counter()- start_time) * 1000
                events.request.fire(
                    request_type=command,
                    name=key,
                    response_time=total_time,
                    response_length=0,
                    exception=e
                )
                result = ''
            return result
        def get_value(self, key, command='GET'):
            start_time = time.perf_counter()
            try:
                result = self.vc.get(randint(0, 1000))
                if not result:
                    result = ''
                length = len(str(result))
                total_time = (time.perf_counter()- start_time) * 1000
                events.request.fire(
                    request_type=command,
                    name=key,
                    response_time=total_time,
                    response_length=length,
                )
            except Exception as e:
                total_time = (time.perf_counter()- start_time) * 1000
                events.request.fire(
                    request_type=command,
                    name=key,
                    response_time=total_time,
                    response_length=0,
                    exception=e
                )
                result = ''
            return result
    EOF
    

Générer l’image Docker et l’envoyer à ACR

  1. Générez l’image Docker et chargez-la dans Azure Container Registry (ACR) à l’aide de la az acr build commande.

    az acr build --image valkey-client --registry ${MY_ACR_REGISTRY} .
    
  2. Vérifiez que l’image a été envoyée (push) avec succès à l’aide de la az acr repository list commande.

    az acr repository list --name ${MY_ACR_REGISTRY} --output table
    

    La sortie doit afficher l’image valkey-client , comme dans l’exemple suivant :

    Result
    ----------------
    valkey-client
    

Déployer l’exemple de pod client sur AKS

  1. Créez un Pod qui utilise l’image client Valkey générée à l’étape précédente à l’aide de la commande kubectl apply. La spécification du pod comporte le volume CSI du magasin des secrets avec le mot de passe Valkey permettant au client de se connecter au cluster Valkey.

    kubectl apply -f - <<EOF
    ---
    kind: Pod
    apiVersion: v1
    metadata:
      name: valkey-client
      namespace: valkey
    spec:
        affinity:
          nodeAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: agentpool
                  operator: In
                  values:
                  - nodepool1
        containers:
        - name: valkey-client
          image: ${MY_ACR_REGISTRY}.azurecr.io/valkey-client
          command: ["locust", "--processes", "4"]
          volumeMounts:
            - name: valkey-password
              mountPath: "/etc/valkey-password"
        volumes:
        - name: valkey-password
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: "valkey-password"
    EOF
    
  2. Réacheminez le port 8089 pour accéder à l’interface web Locust sur votre ordinateur local à l’aide de la commande kubectl port-forward.

    kubectl port-forward -n valkey valkey-client 8089:8089
    
  3. Accédez à l’interface web Locust à l’adresse http://localhost:8089 et démarrez le test. Vous pouvez ajuster le nombre d’utilisateurs et la vitesse de génération pour simuler une charge de travail sur le cluster Valkey. Le graphique suivant utilise 100 utilisateurs et une vitesse de génération de 10 :

    Capture d’écran d’une page web montrant le tableau de bord de test Locust.

Simuler une défaillance et observer le comportement du cluster Valkey

  1. Simulez une panne en supprimant le StatefulSet à l’aide de la commande kubectl delete avec l’indicateur --cascade=orphan. L’objectif est de pouvoir supprimer un seul pod sans que le StatefulSet recrée immédiatement le pod supprimé.

    kubectl delete statefulset valkey-masters --cascade=orphan
    
  2. Supprimez le valkey-masters-0 pod à l’aide de la kubectl delete pod commande.

    kubectl delete pod valkey-masters-0
    
  3. Vérifiez la liste des pods à l’aide de la kubectl get pods commande.

    kubectl get pods
    

    La sortie doit indiquer que le pod valkey-masters-0 a été supprimé. Les autres pods doivent être dans l’état Running , comme illustré dans l’exemple suivant :

    NAME                READY   STATUS    RESTARTS   AGE
    valkey-client       1/1     Running   0          6m34s
    valkey-masters-1    1/1     Running   0          16m
    valkey-masters-2    1/1     Running   0          16m
    valkey-replicas-0   1/1     Running   0          16m
    valkey-replicas-1   1/1     Running   0          16m
    valkey-replicas-2   1/1     Running   0          16m
    
  4. Obtenez les journaux du pod valkey-replicas-0 à l’aide de la commande kubectl logs valkey-replicas-0.

    kubectl logs valkey-replicas-0
    

    Dans la sortie, nous observons que l’événement complet dure environ 18 secondes :

    1:S 05 Nov 2024 12:18:53.961 * Connection with primary lost.
    1:S 05 Nov 2024 12:18:53.961 * Caching the disconnected primary state.
    1:S 05 Nov 2024 12:18:53.961 * Reconnecting to PRIMARY 10.224.0.250:6379
    1:S 05 Nov 2024 12:18:53.961 * PRIMARY <-> REPLICA sync started
    1:S 05 Nov 2024 12:18:53.964 # Error condition on socket for SYNC: Connection refused
    1:S 05 Nov 2024 12:18:54.910 * Connecting to PRIMARY 10.224.0.250:6379
    1:S 05 Nov 2024 12:18:54.910 * PRIMARY <-> REPLICA sync started
    1:S 05 Nov 2024 12:18:54.912 # Error condition on socket for SYNC: Connection refused
    1:S 05 Nov 2024 12:18:55.920 * Connecting to PRIMARY 10.224.0.250:6379
    [..CUT..]
    1:S 05 Nov 2024 12:19:10.056 * Connecting to PRIMARY 10.224.0.250:6379
    1:S 05 Nov 2024 12:19:10.057 * PRIMARY <-> REPLICA sync started
    1:S 05 Nov 2024 12:19:10.058 # Error condition on socket for SYNC: Connection refused
    1:S 05 Nov 2024 12:19:10.709 * Node c44d4b682b6fb9b37033d3e30574873545266d67 () reported node 9e7c43890613cc3ad4006a9cdc0b5e5fc5b6d44e     () as not reachable.
    1:S 05 Nov 2024 12:19:10.864 * NODE 9e7c43890613cc3ad4006a9cdc0b5e5fc5b6d44e () possibly failing.
    1:S 05 Nov 2024 12:19:11.066 * 10000 changes in 60 seconds. Saving...
    1:S 05 Nov 2024 12:19:11.068 * Background saving started by pid 29
    1:S 05 Nov 2024 12:19:11.068 * Connecting to PRIMARY 10.224.0.250:6379
    1:S 05 Nov 2024 12:19:11.068 * PRIMARY <-> REPLICA sync started
    1:S 05 Nov 2024 12:19:11.069 # Error condition on socket for SYNC: Connection refused
    29:C 05 Nov 2024 12:19:11.090 * DB saved on disk
    29:C 05 Nov 2024 12:19:11.090 * Fork CoW for RDB: current 0 MB, peak 0 MB, average 0 MB
    1:S 05 Nov 2024 12:19:11.169 * Background saving terminated with success
    1:S 05 Nov 2024 12:19:11.884 * FAIL message received from ba36d5167ee6016c01296a4a0127716f8edf8290 () about     9e7c43890613cc3ad4006a9cdc0b5e5fc5b6d44e ()
    1:S 05 Nov 2024 12:19:11.884 # Cluster state changed: fail
    1:S 05 Nov 2024 12:19:11.974 * Start of election delayed for 510 milliseconds (rank #0, offset 7225807).
    1:S 05 Nov 2024 12:19:11.976 * Node d43f370a417d299b78bd1983792469fe5c39dcdf () reported node 9e7c43890613cc3ad4006a9cdc0b5e5fc5b6d44e     () as not reachable.
    1:S 05 Nov 2024 12:19:12.076 * Connecting to PRIMARY 10.224.0.250:6379
    1:S 05 Nov 2024 12:19:12.076 * PRIMARY <-> REPLICA sync started
    1:S 05 Nov 2024 12:19:12.076 * Currently unable to failover: Waiting the delay before I can start a new failover.
    1:S 05 Nov 2024 12:19:12.078 # Error condition on socket for SYNC: Connection refused
    1:S 05 Nov 2024 12:19:12.581 * Starting a failover election for epoch 15.
    1:S 05 Nov 2024 12:19:12.616 * Currently unable to failover: Waiting for votes, but majority still not reached.
    1:S 05 Nov 2024 12:19:12.616 * Needed quorum: 2. Number of votes received so far: 1
    1:S 05 Nov 2024 12:19:12.616 * Failover election won: I'm the new primary.
    1:S 05 Nov 2024 12:19:12.616 * configEpoch set to 15 after successful failover
    1:M 05 Nov 2024 12:19:12.616 * Discarding previously cached primary state.
    1:M 05 Nov 2024 12:19:12.616 * Setting secondary replication ID to c0b5b2df8a43b19a4d43d8f8b272a07139e0ca34, valid up to offset:     7225808. New replication ID is 029fcfbae0e3e4a1dccd73066043deba6140c699
    1:M 05 Nov 2024 12:19:12.616 * Cluster state changed: ok
    

    Pendant cette fenêtre de temps de 18 secondes, nous observons que les écritures dans la partition qui appartient au pod supprimé échouent et que le cluster Valkey choisit un nouveau serveur principal. La latence des requêtes atteint 60 ms pendant cette fenêtre de temps.

    Capture d’écran d’un graphique montrant le 95e centile des latences de requête avec un pic à 60 ms.

    Une fois le nouveau principal élu, le cluster Valkey continue de traiter les requêtes avec une latence d’environ 2 ms.

Étape suivante

Contributeurs

Microsoft gère cet article. Les contributeurs suivants ont rédigé sa version d’origine :

  • Nelly Kiboi | Ingénieure de service
  • Saverio Proto | Ingénieur principal de l’expérience client