次の方法で共有


Azure Kubernetes Service (AKS) 上の Valkey クラスターの回復性を検証する

この記事では、Azure Kubernetes Service (AKS) 上の Valkey クラスターの回復性を検証する方法について説明します。

この記事には、Microsoft が既に使用しなくなった "マスター" という用語への言及が含まれています。 Valkey ソフトウェアからこの用語が削除された時点で、この記事から削除します。

Valkey 用のサンプル クライアント アプリケーションをビルドして実行する

次の手順では、Valkey 用のサンプル クライアント アプリケーションをビルドし、アプリケーションの Docker イメージを Azure Container Registry (ACR) にプッシュする方法を示しています。

サンプル クライアント アプリケーションでは、Locust ロード テスト フレームワークを使用して、Valkey クラスターでワークロードをシミュレートします。

  1. Dockerfilerequirements.txt を作成し、次のコマンドを使用して新しいディレクトリに配置します。

    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. 次の内容を含む locustfile.py ファイルを作成します。

    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
    

    この Python コードは、Valkey クラスターに接続し、"設定と取得" 操作を実行する、Locust ユーザー クラスを実装します。 このクラスを拡張して、より複雑な操作を実装できます。

  3. Docker イメージをビルドし、az acr build コマンドを使用して ACR にアップロードします。

    az acr build --image valkey-client --registry ${MY_ACR_REGISTRY} .
    

Azure Kubernetes Service (AKS) 上の Valkey クラスターをテストする

  1. kubectl apply コマンドを使用して、前の手順でビルドした Valkey クライアント イメージを使用する Pod を作成します。 ポッド スペックには、クライアントが Valkey クラスターへの接続に使用する Valkey パスワードを含むシークレット ストア CSI ボリュームが含まれています。

    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. kubectl port-forward コマンドを使用して、ポート 8089 をポート転送して、ローカル コンピューター上の Locust Web インターフェイスにアクセスします。

     kubectl port-forward -n valkey valkey-client 8089:8089
    
  3. http://localhost:8089 の、Locust Web インターフェイスにアクセスしてテストを開始します。 ユーザーの数と生成速度を調整して、Valkey クラスターのワークロードをシミュレートできます。 次のグラフでは、ユーザーが 100 人で生成率は 10 を使用しています。

    Locust のテスト ダッシュボードを示す Web ページのスクリーンショット。

  4. --cascade=orphan フラグを指定した kubectl delete コマンドを使用して StatefulSet を削除することで、停止をシミュレートします。 目標は、StatefulSet で削除されたポッドが直ちに再作成されずに、1 つのポッドを削除できることです。

    kubectl delete statefulset valkey-masters --cascade=orphan
    
  5. kubectl delete pod コマンドを使用して valkey-masters-0 ポッドを削除します。

    kubectl delete pod valkey-masters-0
    
  6. kubectl get pods コマンドを使用して、ポッドの一覧を確認します。

    kubectl get pods
    

    出力は、ポッド valkey-masters-0 が削除されたことを示しているはずです。

    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
    
  7. kubectl logs valkey-replicas-0 コマンドを使用して、 valkey-replicas-0 ポッドのログを取得します。

    kubectl logs valkey-replicas-0
    

    出力で、完全なイベントが約 18 秒間続くのを確認します。

    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
    

    この 18 秒間に、削除されたポッドに属するシャードへの書き込みが失敗し、Valkey クラスターにより新しいプライマリが選択されていることが確認できます。 要求の待機時間は、この時間枠内で 60 ミリ秒に急増します。

    要求の待機時間の 95 パーセンタイルが 60 ミリ秒に急上昇したことを示すグラフのスクリーンショット。

    新しいプライマリが選択されると、Valkey クラスターは約 2 ミリ秒の待機時間で要求を処理し続けます。

次のステップ

この記事では、Locust を使用してテスト アプリケーションを構築する方法と、Valkey プライマリ ポッドの障害をシミュレートする方法について説明しました。 Valkey クラスターが障害から復旧し、待機時間に短期間の急増が見られたものの、要求を引き続き処理できることを確認しました。

Azure Kubernetes Service (AKS) を使用したステートフル ワークロードの詳細については、次の記事を参照してください。

共同作成者

Microsoft では、この記事を保持しています。 最初の寄稿者は次のとおりです。

  • Nelly Kiboi | サービス エンジニア
  • Saverio Proto | プリンシパル カスタマー エクスペリエンス エンジニア