ステートフル ワークロードを実行する GKE クラスタのアップグレード

このチュートリアルでは、ステートフル アプリケーションを作成し、アプリケーションを実行する Google Kubernetes Engine(GKE)クラスタをアップグレードする際のおすすめの方法を説明します。このチュートリアルでは、Redis アプリケーションをデプロイする例を紹介しますが、GKE にデプロイされた他のタイプのステートフル アプリケーションにも同じコンセプトを適用できます。

用語集

このチュートリアルで使用する用語:

  • Redis はオープンソース(BSD ライセンス)のインメモリ データ構造のストアで、データベース、キャッシュ、メッセージ ブローカーとして使用されます。
  • Redis クラスタは、高可用性を提供する Redis の分散実装です。Redis クラスタは、リーダーノードとフォロワー ノードで構成されています。
  • ステートフル アプリケーションは、実行されるたびにその状態情報を保存します。Redis はステートフル アプリケーション向けの一般的なインメモリ データベースです。

目標

このチュートリアルでは、次の手順について説明します。

  • GKE で、3 つの GKE ノードの上に 3 つのリーダーと 3 つのフォロワーからなる Redis クラスタを作成します。
  • Redis クライアント アプリケーションをデプロイします。このアプリケーションは、ウェブサイトに対するリクエストの数をカウントします。
  • サージ アップグレードを使用してクラスタをアップグレードします。
  • アプリケーションのワークロードの中断とステータスの中断をテストします。

次の図は、これらの目標を達成して作成したクラスタ アーキテクチャの概要を示しています。

アーキテクチャの図

費用

このチュートリアルでは、Google Cloud の課金対象となる以下のコンポーネントを使用します。

  • GKE

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを作成できます。新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

始める前に

次の手順で Kubernetes Engine API を有効にします。
  1. Google Cloud Console で Kubernetes Engine ページにアクセスします。
  2. プロジェクトを作成または選択します。
  3. API と関連サービスが有効になるのを待ちます。 これには数分かかることがあります。
  4. Cloud プロジェクトに対して課金が有効になっていることを確認します。プロジェクトに対して課金が有効になっていることを確認する方法を学習する

このチュートリアルで使用されている以下のコマンドライン ツールをインストールします。

  • gcloud は、Kubernetes Engine クラスタの作成と削除に使用されます。gcloud は、Google Cloud SDK に含まれています。
  • kubectl は、Kubernetes Engine で使用されるクラスタ オーケストレーション システムである Kubernetes の管理に使用されます。gcloud を使用して kubectl をインストールできます。
    gcloud components install kubectl

GKE クラスタの作成

このセクションでは、GKE に 3 つのノードからなるクラスタを作成し、そのクラスタの動作確認を行います。

gcloud コマンドライン ツールのデフォルトの設定

次のデフォルトを設定すると、gcloud コマンドライン ツールでプロジェクト IDCompute Engine ゾーンのオプションを入力する時間が節約されます。

gcloud config set project PROJECT-ID
gcloud config set compute/zone COMPUTE-ZONE

GKE クラスタの作成

GKE クラスタを作成するには、次の手順を行います。

  1. 3 つのノードを持つクラスタを redis-test という名前で作成します。

    gcloud container clusters create redis-test \
        --num-nodes=3
    

    クラスタが作成されると、次のような出力が表示されます。

    NAME        LOCATION      MASTER_VERSION  MASTER_IP     MACHINE_TYPE   NODE_VERSION    NUM_NODES  STATUS
    redis-test  COMPUTE-ZONE  1.15.12-gke.20  35.232.77.38  n1-standard-1  1.15.12-gke.20  3          RUNNING
    
  2. クラスタと通信を行うように kubectl を構成します。

    gcloud container clusters get-credentials redis-test
    
  3. クラスタが実行中であることを確認します。

    kubectl get nodes
    

    出力は次の例のようになります。

    NAME                                        STATUS   ROLES    AGE     VERSION
    gke-redis-test-default-pool-c4e4225c-mw3w   Ready    <none>   2m1s    v1.15.12-gke.20
    gke-redis-test-default-pool-c4e4225c-pv51   Ready    <none>   2m1s    v1.15.12-gke.20
    gke-redis-test-default-pool-c4e4225c-whl5   Ready    <none>   2m1s    v1.15.12-gke.20
    

GKE での Redis クラスタの作成

このセクションでは、ConfigMapStatefulSetヘッドレス Service を作成し、前のセクションで作成した GKE クラスタの上に Redis クラスタを作成します。

  1. サンプル マニフェストのクローンを作成します。

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    cd kubernetes-engine-samples/hello-app-redis/manifests
    
  2. redis-configmap.yaml という名前の ConfigMap には Redis 構成が格納されます。

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: redis-cluster
    data:
      redis.conf:  |+
        cluster-enabled yes
        cluster-node-timeout 15000
        cluster-config-file /data/nodes.conf
        appendonly yes
        protected-mode no
        dir /data
        port 6379
    
    

    この ConfigMap の Redis パラメータの詳細については、Redis クラスタ チュートリアルで Redis クラスタ構成パラメータのセクションをご覧ください。

  3. 次のコマンドを実行して ConfigMap をデプロイします。

    kubectl apply -f redis-configmap.yaml
    
  4. Statefulset redis-cluster.yaml には次のキーフィールドがあります。

    • replicas フィールドは 6 に設定されています。それぞれの GKE ノードに 2 つの Pod があり、3 つの Redis リーダーと 3 つの Redis フォロワーに使用します。
    • volumeClaimTemplates フィールドにより、PersistentVolume を使用して安定したストレージを提供します。
    • affinity フィールドにより、Pod のアンチアフィニティ ルールを作成して、Kubernetes ノード間で Pod を分散させます。
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: redis
    spec:
      serviceName: "redis-service"
      replicas: 6
      selector:
        matchLabels:
          app: redis
      template:
        metadata:
          labels:
            app: redis
            appCluster: redis-cluster
        spec:
          terminationGracePeriodSeconds: 20
          affinity:
            podAntiAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
              - weight: 100
                podAffinityTerm:
                  labelSelector:
                    matchExpressions:
                    - key: app
                      operator: In
                      values:
                      - redis
                  topologyKey: kubernetes.io/hostname
          containers:
          - name: redis
            image: "redis"
            command:
              - "redis-server"
            args:
              - "/conf/redis.conf"
              - "--protected-mode"
              - "no"
            resources:
              requests:
                cpu: "100m"
                memory: "100Mi"
            ports:
                - name: redis
                  containerPort: 6379
                  protocol: "TCP"
                - name: cluster
                  containerPort: 16379
                  protocol: "TCP"
            volumeMounts:
            - name: conf
              mountPath: /conf
              readOnly: false
            - name: data
              mountPath: /data
              readOnly: false
          volumes:
          - name: conf
            configMap:
              name: redis-cluster
              defaultMode: 0755
      volumeClaimTemplates:
      - metadata:
          name: data
        spec:
          accessModes: [ "ReadWriteOnce" ]
          resources:
            requests:
              storage: 1Gi
    
    
  5. 次のコマンドを実行して、StatefulSet をデプロイします。

    kubectl apply -f redis-cluster.yaml
    
  6. redis-service.yaml という名前のヘッドレス Service は、Redis ノードの接続用です。ヘッドレス Service を作成するには、clusterIP フィールドを None に設定します。

    apiVersion: v1
    kind: Service
    metadata:
      name: redis-cluster
    spec:
      clusterIP: None
      ports:
      - name: redis-port
        port: 6379
        protocol: TCP
        targetPort: 6379
      selector:
        app: redis
        appCluster: redis-cluster
      sessionAffinity: None
      type: ClusterIP
    
    
  7. 次のコマンドを実行して、Service をデプロイします。

    kubectl apply -f redis-service.yaml
    
  8. 約 2 分待ってから次のコマンドを使用して、すべての Pod が実行されていることを確認します。

    kubectl get pods
    

    出力は次の例のようになります。

    NAME      READY   STATUS              RESTARTS   AGE
    redis-0   1/1     Running             0          2m29s
    redis-1   1/1     Running             0          2m8s
    redis-2   1/1     Running             0          107s
    redis-3   1/1     Running             0          85s
    redis-4   1/1     Running             0          54s
    redis-5   1/1     Running             0          23s
    
  9. 次のコマンドを実行して、永続ボリュームが作成されたことを確認します。

    kubectl get pv
    

    出力は次の例のようになります。

    NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                  STORAGECLASS   REASON   AGE
    pvc-HASH   1Gi        RWO            Delete           Bound    default/data-redis-5   standard                75s
    pvc-HASH   1Gi        RWO            Delete           Bound    default/data-redis-1   standard                2m59s
    pvc-HASH   1Gi        RWO            Delete           Bound    default/data-redis-3   standard                2m16s
    pvc-HASH   1Gi        RWO            Delete           Bound    default/data-redis-2   standard                2m38s
    pvc-HASH   1Gi        RWO            Delete           Bound    default/data-redis-0   standard                3m20s
    pvc-HASH   1Gi        RWO            Delete           Bound    default/data-redis-4   standard                104s
    

    この出力で、HASH は各永続ボリューム名に関連付けられたハッシュを表します。

Redis クラスタへのロールの割り当て

構成が完了したら、Redis クラスタにロールを割り当てます。

ロールの割り当て手順は次のとおりです。

  1. 最初の 3 つの Redis ノードをリーダー、最後の 3 つの Redis ノードをフォロワーとして設定します。

    1. Pod の IP アドレスを取得してコピーします。

      kubectl get pods -l app=redis -o jsonpath='{range.items[*]}{.status.podIP} '
      
    2. 各 Pod の IP アドレスを次のコマンドを貼り付け、リーダーとフォロワーのロールを割り当てます。プロンプトが表示されたら「yes」と入力します。

      kubectl exec -it redis-0 -- redis-cli --cluster create --cluster-replicas 1 \
      POD-IP-1:6379 POD-IP-2:6379 POD-IP-3:6379 \
      POD-IP-4:6379 POD-IP-5:6379 POD-IP-6:6379
      
  2. Redis クラスタが実行中であることを確認します。

    kubectl exec -it redis-0 -- redis-cli cluster info
    

    出力は次の例のようになります。

    cluster_state:ok
    # ...other output...
    
  3. Redis ノードにログインしてロールを確認します。たとえば、redis-0 にリーダーのロールが割り当てられていることを確認するには、次のコマンドを実行します。

    kubectl exec -it redis-0 -- redis-cli role
    

    出力は次の例のようになります。

    1) "master"
    2) (integer) 574
    3) 1) 1) "10.28.2.3"
           2) "6379"
           3) "574"
    

Redis クライアント アプリケーションの作成

このセクションでは、Redis データベースをキャッシュ データベースとして使用し、受信したリクエストの数をカウントして、その数をウェブサイトに出力する hello-app-redis という名前のアプリケーションを作成します。Redis Service が機能している間、ユーザー数は増え続けます。

このイメージは、Google Cloud Console の gcr.io/google-samples/hello-app-redis:1.0 から直接ダウンロードできます。

イメージを pull するには、次のコマンドを実行します。

docker pull gcr.io/google-samples/hello-app-redis:1.0

イメージのビルド方法については、コンテナ イメージのビルドをご覧ください。

Redis クライアント アプリケーションの GKE へのデプロイ

作成した GKE クラスタにアプリケーションをデプロイするには、アプリケーションを定義する Deployment が必要です。

Deployment を作成するには、次の手順を行います。

  1. app-deployment.yaml という名前のファイルに、アプリケーションの詳細が格納されます。

    # Copyright 2020 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: hello-web
      name: hello-web
      namespace: default
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: hello-web
      template:
        metadata:
          labels:
            app: hello-web
        spec:
          # Pod anti affinity config START
          affinity:
            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
              - labelSelector:
                  matchExpressions:
                  - key: app
                    operator: In
                    values:
                    - hello-web
                topologyKey: kubernetes.io/hostname
          # Pod anti affinity config END
          containers:
          - image: gcr.io/google-samples/hello-app-redis:1.0  # change to the image name you built
            name: hello-app
            # Readiness probe config START
            readinessProbe:
              failureThreshold: 1
              httpGet:
                path: /healthz
                port: 8080
                scheme: HTTP
              initialDelaySeconds: 1
              periodSeconds: 1
              successThreshold: 1
              timeoutSeconds: 1
    

    この Deployment で使用される Probe と Pod のアフィニティ ルールの詳細については、GKE のベスト プラクティス: 高可用性クラスタの設計と構築をご覧ください。

  2. 次のコマンドを実行して Deployment を適用します。

    kubectl apply -f app-deployment.yaml
    

    このコマンドは、app-deployment.yaml と同じディレクトリで実行してください。

  3. ロードバランサ経由でアプリケーションを公開します。

    kubectl expose deployment hello-web \
        --type=LoadBalancer \
        --port 80 \
        --target-port 8080
    
  4. 約 1 分待ってから次のコマンドを実行して、アプリケーションの外部 IP アドレスを取得します。

    kubectl get service
    

    出力から、hello-web's EXTERNAL-IP 列に表示されている値をコピーします。

    NAME             TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)              AGE
    hello-web        LoadBalancer   10.13.10.55   EXTERNAL_IP   80:30703/TCP         166m
    
  5. ウェブブラウザに EXTERNAL_IP を貼り付けて、アプリケーションが機能していることを確認します。出力は次の例のようになります。

    I have been hit [1] times since deployment!
    

    アクセス数をメモしておきます。これは、アプリケーションの中断テストで必要になります。

  6. コピーした EXTERNAL_IP の変数を設定します。この値は、次のセクションでアプリケーションのテスト スクリプトを作成するときに使用します。

    export IP=EXTERNAL_IP
    

GKE クラスタのアップグレードとワークロードの中断のテスト

以降のセクションでは、GKE クラスタをアップグレードし、作成したスクリプトを使用して何が起こるかを監視します。

アプリケーションのテスト

このセクションでは、アプリケーションにリクエストを送信するスクリプトと、リクエストの成功率を測定する 2 つのスクリプトを使用します。これらのスクリプトを使用して、クラスタをアップグレードしたときに何が起こるかを測定します。

スクリプトを作成するには:

  1. スクリプトが含まれているディレクトリに移動します。

    cd
    cd kubernetes-engine-samples/hello-app-redis/scripts
    
  2. generate_load.sh という名前のスクリプトは、秒間クエリ数(QPS)リクエストをアプリケーションに送信します。このスクリプトは、現在のディレクトリの output という名前のファイルに HTTP レスポンス コードを保存します。output の値は、次の手順で作成するスクリプトで使用します。

    # Copyright 2020 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    #!/bin/bash
    # Usage: generate_load.sh <IP> <QPS>_
    
    IP=$1
    QPS=$2
    
    while true
      do for N in $(seq 1 $QPS)
        do curl -I -m 5 -s -w "%{http_code}\n" -o /dev/null http://${IP}/ >> output &
        done
      sleep 1
    done
    
  3. print_error_rate.sh という名前の 2 番目のスクリプトは、generate_load.sh によって生成された出力に基づいて成功率を計算します。

    # Copyright 2020 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    #!/bin/bash
    # Usage: watch ./print_error_rate.sh
    
    TOTAL=$(cat output | wc -l);
    SUCCESS=$(grep "200" output |  wc -l);
    ERROR1=$(grep "000" output |  wc -l)
    ERROR2=$(grep "503" output |  wc -l)
    ERROR3=$(grep "500" output |  wc -l)
    SUCCESS_RATE=$(($SUCCESS * 100 / TOTAL))
    ERROR_RATE=$(($ERROR1 * 100 / TOTAL))
    ERROR_RATE_2=$(($ERROR2 * 100 / TOTAL))
    ERROR_RATE_3=$(($ERROR3 * 100 / TOTAL))
    echo "Success rate: $SUCCESS/$TOTAL (${SUCCESS_RATE}%)"
    echo "App network Error rate: $ERROR1/$TOTAL (${ERROR_RATE}%)"
    echo "Resource Error rate: $ERROR2/$TOTAL (${ERROR_RATE_2}%)"
    echo "Redis Error rate: $ERROR3/$TOTAL (${ERROR_RATE_3}%)"
    
  4. スクリプトの実行権限を付与します。

    chmod u+x generate_load.sh print_error_rate.sh
    
  5. QPS の数を格納する変数を設定します。この値は、EXTERNAL_IP に設定した変数と同様に generate_load.sh スクリプトで使用されます。値に 40 を設定することをおすすめします。

    export QPS=40
    
  6. generate_load.sh スクリプトを実行して QPS の送信を開始します。

    ./generate_load.sh $IP $QPS 2>&1
    
  7. generate_load.sh スクリプトを実行したまま、新しいターミナルを開きます。新しいターミナルで、print_error_rate.sh スクリプトを実行してエラー率を確認します。

    watch ./print_error_rate.sh
    

    QPS が発生すると 100% の成功率と 0% エラー率が表示されます。

  8. 両方のスクリプトを実行したまま、次のセクション用に 3 つ目のターミナルを開きます。

クラスタのアップグレード

このセクションでは、ワークロードをアップグレードします。

  1. さきほど開いたターミナルで、サージ アップグレードを使用してアップグレードの設定を定義します。

    gcloud container node-pools update default-pool \
      --max-surge-upgrade=1 \
      --max-unavailable-upgrade=0 \
      --cluster=redis-test
    

    この構成(maxSurge=1maxUnavailable=0)では、アップグレード中にノードプールにサージノードを 1 つだけ追加できます。このため、一度にアップグレードできるノードは 1 つだけです。この設定では、アップグレード中の Pod の再起動が速くなります。

  2. redis-test クラスタが使用している GKE のバージョンを確認します。

    V=$(gcloud container clusters describe redis-test | grep "version:" | sed "s/version: //")
    echo $V
    

    出力は 1.15.12-gke.20 のようになります。

  3. 使用可能な Kubernetes バージョンのリストを取得します。

    gcloud container get-server-config
    
  4. バージョンのリストで validMasterVersions: セクションに移動し、前の手順で取得した redis-cluster バージョンを探します。バージョン スキューを回避するため、redis-cluster バージョンのすぐ上にあるバージョンをコピーします。

  5. クラスタのコントロール プレーンを選択したバージョンにアップグレードします。プロンプトが表示されたら「y」と入力します。

    gcloud container clusters upgrade redis-test \
        --master \
        --cluster-version VERSION
    

    VERSION は、前のステップで選択したバージョンに置き換えます。

    コントロール プレーンのアップグレードには数分かかります。

  6. クラスタのノードを選択したバージョンにアップグレードします。プロンプトが表示されたら「y」と入力します。

    gcloud container clusters upgrade redis-test \
        --cluster-version=VERSION \
        --node-pool=default-pool
    

    VERSION は、リストで選択したバージョンに置き換えます。

ワークロードの中断のテスト

このセクションでは、アプリケーションのステータスとワークロードの中断をテストします。

  1. ./print_error_rate.sh を実行しているターミナル ウィンドウに戻り、アップグレード中の成功率の変化を確認します。アップグレードのためにノードが停止すると、成功率が若干低下し、アプリのネットワーク エラー率がわずかに増加します。

    Success rate フィールドには、ウェブサイトに対して成功したアクセス数が表示されます。この値をメモします。

  2. 関連するターミナルで「CTRL+C」と入力して、両方のスクリプトの実行を停止します。

  3. アプリケーションの IP アドレス(GKE へのデプロイでコピーした EXTERNAL_IP)をブラウザに入力して、アプリケーションのウェブサイトに戻ります。

  4. アプリのアクセス数を確認します。表示された数値は次のようになるはずです。

    ORIGINAL_VISIT_NUMBER + SUCCESSFUL_VISIT_NUMBER

    ORIGINAL_VISIT_NUMBER は、GKE へのデプロイの最後で記録した数字です。SUCCESSFUL_VISIT_NUMBER は、このセクションの最初のステップで記録した数値です。

クリーンアップ

チュートリアルが終了したら、作成したリソースをクリーンアップして、割り当ての使用を停止し、課金されないようにできます。次のセクションで、リソースを削除または無効にする方法を説明します。

プロジェクトの削除

課金をなくす最も簡単な方法は、チュートリアル用に作成したプロジェクトを削除することです。

プロジェクトを削除するには:

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

クラスタの削除

このチュートリアル用に作成したクラスタを削除するには、次のコマンドを実行します。

gcloud container clusters delete redis-test

次のステップ

  • Google Cloud に関するリファレンス アーキテクチャ、図、チュートリアル、ベスト プラクティスを確認する。Cloud Architecture Center を確認します。