ステートフル MySQL クラスタを GKE にデプロイする


このドキュメントは、高可用性 MySQL トポロジを Google Kubernetes Engine にデプロイすることに関心があるデータベース管理者、クラウド アーキテクト、運用の担当者を対象としています。

このチュートリアルに沿って、GKE クラスタに MySQL Router ミドルウェアのみならず、MySQL InnoDB クラスタMySQL InnoDB ClusterSet をデプロイする方法、およびアップグレードの実施方法を学びます。

目標

このチュートリアルの学習内容は次のとおりです。

  • ステートフルな Kubernetes Service を作成してデプロイする。
  • 高可用性対応の MySQL InnoDB クラスタをデプロイする。
  • データベース オペレーション ルーティング用の Router ミドルウェアをデプロイする。
  • 耐障害性のために MySQL InnoDB ClusterSet をデプロイする。
  • MySQL クラスタのフェイルオーバーをシミュレーションする。
  • MySQL のバージョン アップグレードを実行する。

以降のセクションでは、このチュートリアルで構築するソリューションのアーキテクチャについて説明します。

MySQL InnoDB クラスタ

リージョン GKE クラスタで、StatefulSet を使用して、必要な命名と構成の MySQL データベース インスタンスをデプロイし、MySQL InnoDB クラスタを作成します。フォールト トレラントと高可用性を実現するために、3 つのデータベース インスタンス Pod をデプロイします。これにより、コンセンサス プロトコルを使用してプライマリ選出が正常に行われたときに、異なるゾーンの Pod の大半が常に使用可能になり、MySQL InnoDB クラスタが単一ゾーンでの障害に耐えられるようになります。

アプリケーション、MySQL Router、MySQL Cluster の関係を示すアーキテクチャ図
図 1: 単一の MySQL InnoDB クラスタのアーキテクチャの例

デプロイしたら、1 つの Pod をプライマリ インスタンスに指定し、読み取りオペレーションと書き込みオペレーションの両方を処理します。他の 2 つの Pod はセカンダリ読み取り専用レプリカです。プライマリ インスタンスでインフラストラクチャに障害が発生した場合、この 2 つのレプリカ Pod のいずれかをプライマリに昇格できます。

別の Namespace に 3 つの MySQL Router Pod をデプロイして、復元力を高めるために接続ルーティングを提供します。アプリケーションはデータベース サービスに直接接続するのではなく、MySQL Router Pod に接続します。各 Router Pod は、各 MySQL InnoDB Cluster Pod のステータスと目的を認識し、アプリケーション オペレーションを該当する正常な Pod にルーティングします。ルーティング状態は Router Pod でキャッシュに保存され、MySQL InnoDB クラスタの各ノードに保存されているクラスタ メタデータから更新されます。インスタンスに障害が発生した場合、Router はライブ インスタンスへの接続ルーティングを調整します。

MySQL InnoDB ClusterSet

最初の MySQL InnoDB クラスタから、MySQL InnoDB ClusterSet を作成できます。 プライマリ クラスタが使用不能になった場合、耐障害性を向上させることができます。

図は、プライマリとレプリカの MySQL InnoDB クラスタが非同期レプリケーションによって同期が維持される方法を示しています。
図 2: 1 つのプライマリ クラスタと 1 つのレプリカ クラスタを含むマルチリージョン ClusterSet アーキテクチャの例

MySQL InnoDB クラスタのプライマリ インスタンスが使用できなくなった場合は、ClusterSet 内のレプリカ クラスタをプライマリに昇格できます。MySQL Router ミドルウェアを使用する場合、アプリケーションでプライマリ データベース インスタンスの正常性を追跡する必要はありません。選択が行われた後に、新しいプライマリに接続を送信するようにルーティングが調整されます。ただし、MySQL Router ミドルウェアに接続するアプリケーションが復元力を高めるベスト プラクティスに沿って、クラスタ フェイルオーバー中にエラーが発生した場合に接続を再試行するようにするのは、お客様の責任です。

費用

このドキュメントでは、課金対象である次の Google Cloudコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。

新規の Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

このドキュメントに記載されているタスクの完了後、作成したリソースを削除すると、それ以上の請求は発生しません。詳細については、クリーンアップをご覧ください。

始める前に

プロジェクトを設定する

  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, click Create project to begin creating a new Google Cloud project.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. Enable the GKE API.

    Enable the API

  5. In the Google Cloud console, on the project selector page, click Create project to begin creating a new Google Cloud project.

    Go to project selector

  6. Verify that billing is enabled for your Google Cloud project.

  7. Enable the GKE API.

    Enable the API

  8. ロールの設定

    1. Make sure that you have the following role or roles on the project: role/storage.objectViewer, role/logging.logWriter, role/artifactregistry.Admin, roles/container.clusterAdmin, role/container.serviceAgent, roles/serviceusage.serviceUsageAdmin, roles/iam.serviceAccountAdmin

      Check for the roles

      1. In the Google Cloud console, go to the IAM page.

        Go to IAM
      2. Select the project.
      3. In the Principal column, find all rows that identify you or a group that you're included in. To learn which groups you're included in, contact your administrator.

      4. For all rows that specify or include you, check the Role column to see whether the list of roles includes the required roles.

      Grant the roles

      1. In the Google Cloud console, go to the IAM page.

        IAM に移動
      2. プロジェクトを選択します。
      3. [ アクセスを許可] をクリックします。
      4. [新しいプリンシパル] フィールドに、ユーザー ID を入力します。 これは通常、Google アカウントのメールアドレスです。

      5. [ロールを選択] リストでロールを選択します。
      6. 追加のロールを付与するには、 [別のロールを追加] をクリックして各ロールを追加します。
      7. [保存] をクリックします。

      環境の設定

      このチュートリアルでは、Cloud Shell を使用してGoogle Cloudでホストされているリソースを管理します。Cloud Shell には、Docker と kubectl および gcloud CLI がプリインストールされています。

      Cloud Shell を使用して環境を設定するには:

      1. 環境変数を設定します。

        export PROJECT_ID=PROJECT_ID
        export CLUSTER_NAME=gkemulti-west
        export CONTROL_PLANE_LOCATION=CONTROL_PLANE_LOCATION
        

        次の値を置き換えます。

        • PROJECT_ID: 実際の Google Cloud プロジェクト ID
        • CONTROL_PLANE_LOCATION: クラスタのコントロール プレーンの Compute Engine の リージョン。このチュートリアルでは、リージョンは us-west1 です。通常、近くのリージョンがおすすめです。
      2. デフォルトの環境変数を設定します。

         gcloud config set project PROJECT_ID
         gcloud config set compute/region CONTROL_PLANE_LOCATION
        
      3. コード リポジトリのクローンを作成します。

        git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
        
      4. 作業ディレクトリを変更します。

        cd kubernetes-engine-samples/databases/gke-stateful-mysql/kubernetes
        

        GKE クラスタを作成する

        このセクションでは、GKE のリージョン クラスタを作成します。ゾーンクラスタとは異なり、リージョン クラスタのコントロール プレーンは複数のゾーンにレプリケートされるので、1 つのゾーンでサービスが停止してもコントロール プレーンが使用できなくなることはありません。

        GKE クラスタを作成するには、次の手順に沿って操作します。

        Autopilot

        1. Cloud Shell で、us-west1 リージョンに GKE Autopilot クラスタを作成します。

          gcloud container clusters create-auto $CLUSTER_NAME \
              --location=$CONTROL_PLANE_LOCATION
          
        2. GKE クラスタ認証情報を取得します。

          gcloud container clusters get-credentials $CLUSTER_NAME \
            --location=$CONTROL_PLANE_LOCATION
          
        3. 3 つのゾーンに Service をデプロイします。このチュートリアルでは、Kubernetes Deployment を使用します。Deployment は、クラスタ内のノードに分散された Pod の複数のレプリカを実行できる Kubernetes API オブジェクトです。

          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: prepare-three-zone-ha
            labels:
              app: prepare-three-zone-ha
          spec:
            replicas: 3
            selector:
              matchLabels:
                app: prepare-three-zone-ha
            template:
              metadata:
                labels:
                  app: prepare-three-zone-ha
              spec:
                affinity:
                  # Tell Kubernetes to avoid scheduling a replica in a zone where there
                  # is already a replica with the label "app: prepare-three-zone-ha"
                  podAntiAffinity:
                    requiredDuringSchedulingIgnoredDuringExecution:
                    - labelSelector:
                        matchExpressions:
                        - key: app
                          operator: In
                          values:
                          - prepare-three-zone-ha
                      topologyKey: "topology.kubernetes.io/zone"
                containers:
                - name: prepare-three-zone-ha
                  image: busybox:latest
                  command:
                      - "/bin/sh"
                      - "-c"
                      - "while true; do sleep 3600; done"
                  resources:
                    limits:
                      cpu: "500m"
                      ephemeral-storage: "10Mi"
                      memory: "0.5Gi"
                    requests:
                      cpu: "500m"
                      ephemeral-storage: "10Mi"
                      memory: "0.5Gi"
          kubectl apply -f prepare-for-ha.yaml
          

          デフォルトでは、Autopilot は 2 つのゾーンにリソースをプロビジョニングします。prepare-for-ha.yaml で定義された Deployment により、Autopilot は、replicas:3requiredDuringSchedulingIgnoredDuringExecutionpodAntiAffinitytopologyKey: "topology.kubernetes.io/zone" を設定して、クラスタ内の 3 つのゾーンにノードをプロビジョニングします。

        4. Deployment のステータスを確認します。

          kubectl get deployment prepare-three-zone-ha --watch
          

          準備完了状態の Pod が 3 つ表示されたら、CTRL+C を使用してこのコマンドをキャンセルします。出力は次のようになります。

          NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
          prepare-three-zone-ha   0/3     3            0           9s
          prepare-three-zone-ha   1/3     3            1           116s
          prepare-three-zone-ha   2/3     3            2           119s
          prepare-three-zone-ha   3/3     3            3           2m16s
          
        5. このスクリプトを実行して、Pod が 3 つのゾーンにデプロイされていることを確認します。

          bash ../scripts/inspect_pod_node.sh default
          

          出力の各行は Pod に対応し、2 番目の列はゾーンを示します。出力は次のようになります。

          gk3-gkemulti-west1-default-pool-eb354e2d-z6mv us-west1-b prepare-three-zone-ha-7885d77d9c-8f7qb
          gk3-gkemulti-west1-nap-25b73chq-739a9d40-4csr us-west1-c prepare-three-zone-ha-7885d77d9c-98fpn
          gk3-gkemulti-west1-default-pool-160c3578-bmm2 us-west1-a prepare-three-zone-ha-7885d77d9c-phmhj
          

        標準

        1. Cloud Shell で、us-west1 リージョンに GKE Standard クラスタを作成します。

          gcloud container clusters create $CLUSTER_NAME \
            --location=$CONTROL_PLANE_LOCATION \
            --machine-type="e2-standard-2" \
            --disk-type="pd-standard" \
            --num-nodes="5"
          
        2. GKE クラスタ認証情報を取得します。

          gcloud container clusters get-credentials $CLUSTER_NAME \
            --location=$CONTROL_PLANE_LOCATION
          

        MySQL StatefulSet をデプロイする

        このセクションでは、1 つの MySQL StatefulSet をデプロイします。StatefulSet は、各 Pod の永続的な一意の ID を保持する Kubernetes コントローラです。

        各 StatefulSet は、3 つの MySQL レプリカで構成されています。

        MySQL StatefulSet をデプロイする手順は次のとおりです。

        1. StatefulSet の名前空間を作成します。

          kubectl create namespace mysql1
          
        2. MySQL シークレットを作成します。

          apiVersion: v1
          kind: Secret
          metadata:
            name: mysql-secret
          type: Opaque
          data:
            password: UGFzc3dvcmQkMTIzNDU2 # Password$123456
            admin-password: UGFzc3dvcmQkMTIzNDU2 # Password$123456
          kubectl apply -n mysql1 -f secret.yaml
          

          パスワードは各 Pod でデプロイされ、このチュートリアルの MySQL InnoDB クラスタと ClusterSet のデプロイの管理スクリプトとコマンドで使用されます。

        3. StorageClass を作成します。

          apiVersion: storage.k8s.io/v1
          kind: StorageClass
          metadata:
            name: fast-storageclass
          provisioner: pd.csi.storage.gke.io
          volumeBindingMode: WaitForFirstConsumer
          reclaimPolicy: Retain
          allowVolumeExpansion: true
          parameters:
            type: pd-balanced
          kubectl apply -n mysql1 -f storageclass.yaml
          

          このストレージ クラスは、パフォーマンスとコストのバランスが取れた pd-balanced Persistent Disk タイプを使用します。volumeBindingMode フィールドは WaitForFirstConsumer に設定されています。つまり、GKE は Pod が作成されるまで PersistentVolume のプロビジョニングを遅延します。これにより、Pod がスケジュールされている同じゾーンでディスクがプロビジョニングされます。

        4. MySQL インスタンス Pod の StatefulSet をデプロイします。

          apiVersion: apps/v1
          kind: StatefulSet
          metadata:
            name: dbc1
            labels:
              app: mysql
          spec:
            replicas: 3
            selector:
              matchLabels:
                app: mysql
            serviceName: mysql
            template:
              metadata:
                labels:
                  app: mysql
              spec:
                topologySpreadConstraints:
                - maxSkew: 1
                  topologyKey: "topology.kubernetes.io/zone"
                  whenUnsatisfiable: DoNotSchedule
                  labelSelector:
                    matchLabels:
                      app: mysql
                affinity:
                  podAntiAffinity:
                    requiredDuringSchedulingIgnoredDuringExecution:
                    - labelSelector:
                        matchExpressions:
                        - key: app
                          operator: In
                          values:
                          - mysql
                      topologyKey: "kubernetes.io/hostname"
                containers:
                - name: mysql
                  image: mysql/mysql-server:8.0.28
                  command:
                  - /bin/bash
                  args:
                  - -c
                  - >-
                    /entrypoint.sh
                    --server-id=$((20 +  $(echo $HOSTNAME | grep -o '[^-]*$') + 1))
                    --report-host=${HOSTNAME}.mysql.mysql1.svc.cluster.local
                    --binlog-checksum=NONE
                    --enforce-gtid-consistency=ON
                    --gtid-mode=ON
                    --default-authentication-plugin=mysql_native_password
                  env:
                  - name: MYSQL_ROOT_PASSWORD
                    valueFrom:
                      secretKeyRef:
                        name: mysql-secret
                        key: password
                  - name: MYSQL_ADMIN_PASSWORD
                    valueFrom:
                      secretKeyRef:
                        name: mysql-secret
                        key: admin-password
                  - name: MYSQL_ROOT_HOST
                    value: '%'
                  ports:
                  - name: mysql
                    containerPort: 3306
                  - name: mysqlx
                    containerPort: 33060
                  - name: xcom
                    containerPort: 33061
                  resources:
                    limits:
                      cpu: "500m"
                      ephemeral-storage: "1Gi"
                      memory: "1Gi"
                    requests:
                      cpu: "500m"
                      ephemeral-storage: "1Gi"
                      memory: "1Gi"
                  volumeMounts:
                  - name: mysql
                    mountPath: /var/lib/mysql
                    subPath: mysql
                  readinessProbe:
                    exec:
                      command:
                      - bash
                      - "-c"
                      - |
                        mysql -h127.0.0.1 -uroot -p$MYSQL_ROOT_PASSWORD -e'SELECT 1'
                    initialDelaySeconds: 30
                    periodSeconds: 2
                    timeoutSeconds: 1
                  livenessProbe:
                    exec:
                      command:
                      - bash
                      - "-c"
                      - |
                        mysqladmin -uroot -p$MYSQL_ROOT_PASSWORD ping
                    initialDelaySeconds: 30
                    periodSeconds: 10
                    timeoutSeconds: 5
            updateStrategy:
              rollingUpdate:
                partition: 0
              type: RollingUpdate
            volumeClaimTemplates:
            - metadata:
                name: mysql
                labels:
                  app: mysql
              spec:
                storageClassName: fast-storageclass
                volumeMode: Filesystem
                accessModes:
                - ReadWriteOnce
                resources:
                  requests:
                    storage: 10Gi
          kubectl apply -n mysql1 -f c1-mysql.yaml
          

          このコマンドは、3 つのレプリカで構成される StatefulSet をデプロイします。このチュートリアルでは、プライマリ MySQL クラスタは us-west1 の 3 つのゾーンにデプロイされます。出力は次のようになります。

          service/mysql created
          statefulset.apps/dbc1 created
          

          このチュートリアルでは、費用を節約するために、リソースの上限とリクエストを最小値に設定します。本番環境のワークロードを計画する場合は、これらの値を組織のニーズに合わせて 3 つの値を適切に設定してください。

        5. StatefulSet が正常に作成されたことを確認します。

          kubectl get statefulset -n mysql1 --watch
          

          StatefulSet の準備が整うまでに 10 分ほどかかることがあります。

        6. 3 つの Pod がすべて準備完了の状態になったら、Ctrl+C を使用してコマンドを終了します。CPU またはメモリ不足が原因で PodUnscheduleable エラーが発生した場合は、大きなワークロードに対応できるようにコントロール プレーンのサイズが変更されるまで数分待ちます。

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

          NAME   READY   AGE
          dbc1   1/3     39s
          dbc1   2/3     50s
          dbc1   3/3     73s
          
        7. GKE クラスタノード上の Pod の配置を検査するには、次のスクリプトを実行します。

          bash ../scripts/inspect_pod_node.sh mysql1 mysql
          

          出力には、Pod 名、GKE ノード名、ノードがプロビジョニングされたゾーンが表示され、次のようになります。

          gke-gkemulti-west-5-default-pool-4bcaca65-jch0 us-west1-b dbc1-0
          gke-gkemulti-west-5-default-pool-1ac6e8b5-ddjx us-west1-c dbc1-1
          gke-gkemulti-west-5-default-pool-1f5baa66-bf8t us-west1-a dbc1-2
          

          出力の列は、ホスト名、クラウドゾーン、Pod 名をそれぞれ表します。

          StatefulSet 仕様の topologySpreadConstraints ポリシー(c1-mysql.yaml)は、障害発生ドメイン(topology.kubernetes.io/zone)全体で Pod を均等に配置するようスケジューラに指示します。

          podAntiAffinity ポリシーでは、同じ GKE クラスタノード(kubernetes.io/hostname)に Pod を配置しないように制約が適用されます。MySQL インスタンス Pod の場合、このポリシーにより、 Google Cloud リージョンの 3 つのゾーンに Pod が均等にデプロイされます。この配置により、各データベース インスタンスを別々の障害発生ドメインに配置することで、MySQL InnoDB クラスタの高可用性を実現できます。

        プライマリ MySQL InnoDB クラスタを準備する

        MySQL InnoDB クラスタを構成する手順は次のとおりです。

        1. Cloud Shell ターミナルで、クラスタに追加する MySQL インスタンスのグループ レプリケーション構成を設定します。

          bash ../scripts/c1-clustersetup.sh
          
          POD_ORDINAL_START=${1:-0}
          POD_ORDINAL_END=${2:-2}
          for i in $(seq ${POD_ORDINAL_START} ${POD_ORDINAL_END}); do
            echo "Configuring pod mysql1/dbc1-${i}"
            cat <<'  EOF' | kubectl -n mysql1 exec -i dbc1-${i} -- bash -c 'mysql -uroot -proot --password=${MYSQL_ROOT_PASSWORD}'
          INSTALL PLUGIN group_replication SONAME 'group_replication.so';
          RESET PERSIST IF EXISTS group_replication_ip_allowlist;
          RESET PERSIST IF EXISTS binlog_transaction_dependency_tracking;
          SET @@PERSIST.group_replication_ip_allowlist = 'mysql.mysql1.svc.cluster.local';
          SET @@PERSIST.binlog_transaction_dependency_tracking = 'WRITESET';
            EOF
          done

          このスクリプトは、3 つの MySQL インスタンスにそれぞれリモート接続し、次の環境変数を設定して保持します。

          • group_replication_ip_allowlist: クラスタ内のインスタンスがグループ内の任意のインスタンスに接続できるようにします。
          • binlog_transaction_dependency_tracking='WRITESET': 競合しない並列トランザクションを許可します。

          8.0.22 より前の MySQL バージョンでは、group_replication_ip_allowlist ではなく group_replication_ip_whitelist を使用します。

        2. Pod ごとにシェルを作成する必要がないように、2 つ目のターミナルを開きます。

        3. Pod dbc1-0 で MySQL Shell に接続します。

          kubectl -n mysql1 exec -it dbc1-0 -- \
              /bin/bash \
              -c 'mysqlsh --uri="root:$MYSQL_ROOT_PASSWORD@dbc1-0.mysql.mysql1.svc.cluster.local"'
          
        4. 他のインスタンスに接続するための MySQL グループ レプリケーション許可リストを確認します。

          \sql SELECT @@group_replication_ip_allowlist;
          

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

          +----------------------------------+
          | @@group_replication_ip_allowlist |
          +----------------------------------+
          | mysql.mysql1.svc.cluster.local   |
          +----------------------------------+
          
        5. server-id が各インスタンスで一意であることを確認します。

          \sql SELECT @@server_id;
          

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

          +-------------+
          | @@server_id |
          +-------------+
          |          21 |
          +-------------+
          
        6. MySQL InnoDB クラスタを使用するように各インスタンスを構成し、各インスタンスに管理者アカウントを作成します。

          \js
          dba.configureInstance('root@dbc1-0.mysql.mysql1.svc.cluster.local', {password: os.getenv("MYSQL_ROOT_PASSWORD"),clusterAdmin: 'icadmin', clusterAdminPassword: os.getenv("MYSQL_ADMIN_PASSWORD")});
          dba.configureInstance('root@dbc1-1.mysql.mysql1.svc.cluster.local', {password: os.getenv("MYSQL_ROOT_PASSWORD"),clusterAdmin: 'icadmin', clusterAdminPassword: os.getenv("MYSQL_ADMIN_PASSWORD")});
          dba.configureInstance('root@dbc1-2.mysql.mysql1.svc.cluster.local', {password: os.getenv("MYSQL_ROOT_PASSWORD"),clusterAdmin: 'icadmin', clusterAdminPassword: os.getenv("MYSQL_ADMIN_PASSWORD")});
          

          MySQL InnoDB クラスタを正常に機能させるには、すべてのインスタンスで同じユーザー名とパスワードを使用する必要があります。各コマンドの出力は次のようになります。

          ...
          
          The instance 'dbc1-2.mysql:3306' is valid to be used in an InnoDB cluster.
          
          Cluster admin user 'icadmin'@'%' created.
          The instance 'dbc1-2.mysql.mysql1.svc.cluster.local:3306' is already
          ready to be used in an InnoDB cluster.
          
          Successfully enabled parallel appliers.
          
        7. インスタンスが MySQL InnoDB クラスタで使用できる状態であることを確認します。

          dba.checkInstanceConfiguration()
          

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

          ...
          
          The instance 'dbc1-0.mysql.mysql1.svc.cluster.local:3306' is valid to be used in an InnoDB cluster.
          
          {
              "status": "ok"
          }
          

          必要に応じて、各 MySQL インスタンスに接続して、このコマンドを繰り返すことができます。たとえば、dbc1-1 インスタンスのステータスを確認するには、次のコマンドを実行します。

          kubectl -n mysql1 exec -it dbc1-0 -- \
              /bin/bash \
              -c 'mysqlsh --uri="root:$MYSQL_ROOT_PASSWORD@dbc1-1.mysql.mysql1.svc.cluster.local" \
              --js --execute "dba.checkInstanceConfiguration()"'
          

        プライマリ MySQL InnoDB クラスタを作成する

        次に、MySQL 管理の createCluster コマンドを使用して MySQL InnoDB クラスタを作成します。dbc1-0 インスタンス(クラスタのプライマリ インスタンス)から始めて、2 つのレプリカをクラスタに追加します。

        MySQL InnoDB クラスタを初期化する手順は次のとおりです。

        1. MySQL InnoDB クラスタを作成します。

          var cluster=dba.createCluster('mycluster');
          

          createCluster コマンドを実行すると、次のオペレーションがトリガーされます。

          • メタデータ スキーマをデプロイします。
          • グループ レプリケーションの構成が正しいことを確認します。
          • これを新しいクラスタのシード インスタンスとして登録します。
          • レプリケーション ユーザー アカウントなど、必要な内部アカウントを作成します。
          • グループのレプリケーションを開始します。

          このコマンドは、ホスト dbc1-0 をプライマリとして MySQL InnoDB クラスタを初期化します。クラスタ参照はクラスタ変数に格納されます。

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

          A new InnoDB cluster will be created on instance 'dbc1-0.mysql:3306'.
          
          Validating instance configuration at dbc1-0.mysql:3306...
          
          This instance reports its own address as dbc1-0.mysql.mysql1.svc.cluster.local:3306
          
          Instance configuration is suitable.
          NOTE: Group Replication will communicate with other instances using
          'dbc1-0.mysql:33061'. Use the localAddress
          option to override.
          
          Creating InnoDB cluster 'mycluster' on
          'dbc1-0.mysql.mysql1.svc.cluster.local:3306'...
          
          Adding Seed Instance...
          Cluster successfully created. Use Cluster.addInstance() to add MySQL
          instances.
          At least 3 instances are needed for the cluster to be able to withstand
          up to one server failure.
          
        2. 2 つ目のインスタンスをクラスタに追加します。

          cluster.addInstance('icadmin@dbc1-1.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
          
        3. 残りのインスタンスをクラスタに追加します。

          cluster.addInstance('icadmin@dbc1-2.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
          

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

          ...
          The instance 'dbc1-2.mysql:3306' was successfully added to the cluster.
          
        4. クラスタのステータスを確認します。

          cluster.status()
          

          このコマンドにより、クラスタのステータスが表示されます。このトポロジは、3 つのホスト(1 つのプライマリ インスタンスと 2 つのセカンダリ インスタンス)で構成されています。必要に応じて、cluster.status({extended:1}) を呼び出すこともできます。

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

          {
              "clusterName": "mysql1",
              "defaultReplicaSet": {
                  "name": "default",
                  "primary": "dbc1-0.mysql:3306",
                  "ssl": "REQUIRED",
                  "status": "OK",
                  "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
                  "topology": {
                      "dbc1-0.mysql:3306": {
                          "address": "dbc1-0.mysql:3306",
                          "memberRole": "PRIMARY",
                          "mode": "R/W",
                          "readReplicas": {},
                          "replicationLag": null,
                          "role": "HA",
                          "status": "ONLINE",
                          "version": "8.0.28"
                      },
                      "dbc1-1.mysql:3306": {
                          "address": "dbc1-1.mysql:3306",
                          "memberRole": "SECONDARY",
                          "mode": "R/O",
                          "readReplicas": {},
                          "replicationLag": null,
                          "role": "HA",
                          "status": "ONLINE",
                          "version": "8.0.28"
                      },
                      "dbc1-2.mysql:3306": {
                          "address": "dbc1-2.mysql:3306",
                          "memberRole": "SECONDARY",
                          "mode": "R/O",
                          "readReplicas": {},
                          "replicationLag": null,
                          "role": "HA",
                          "status": "ONLINE",
                          "version": "8.0.28"
                      }
                  },
                  "topologyMode": "Single-Primary"
              },
              "groupInformationSourceMember": "dbc1-0.mysql:3306"
          }
          

          必要に応じて、cluster.status({extended:1}) を呼び出して追加のステータスの詳細を取得できます。

        サンプル データベースを作成する

        サンプル データベースを作成する手順は次のとおりです。

        1. データベースを作成し、データをデータベースに読み込みます。

          \sql
          create database loanapplication;
          use loanapplication
          CREATE TABLE loan (loan_id INT unsigned AUTO_INCREMENT PRIMARY KEY, firstname VARCHAR(30) NOT NULL, lastname VARCHAR(30) NOT NULL , status VARCHAR(30) );
          
        2. データベースにサンプルデータを挿入します。データを挿入するには、クラスタのプライマリ インスタンスに接続する必要があります。

          INSERT INTO loan (firstname, lastname, status) VALUES ( 'Fred','Flintstone','pending');
          INSERT INTO loan (firstname, lastname, status) VALUES ( 'Betty','Rubble','approved');
          
        3. テーブルに、前の手順で挿入した 3 つの行が含まれていることを確認します。

          SELECT * FROM loan;
          

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

          +---------+-----------+------------+----------+
          | loan_id | firstname | lastname   | status   |
          +---------+-----------+------------+----------+
          |       1 | Fred      | Flintstone | pending  |
          |       2 | Betty     | Rubble     | approved |
          +---------+-----------+------------+----------+
          2 rows in set (0.0010 sec)
          

        MySQL InnoDB ClusterSet を作成する

        MySQL InnoDB ClusterSet を作成すると、専用の ClusterSet レプリケーション チャネルを使用して、プライマリ クラスタからレプリカ クラスタへのレプリケーションを管理できます。

        MySQL InnoDB ClusterSet は、複数のゾーンや複数のリージョンなどの代替のロケーションにある複数のレプリカを使用してプライマリ MySQL InnoDB クラスタをリンクさせて、MySQL InnoDB クラスタ デプロイの耐障害性を提供します。

        MySQL Shell を閉じた場合は、新しい Cloud Shell ターミナルで次のコマンドを実行して新しいシェルを作成します。

          kubectl -n mysql1 exec -it dbc1-0 -- \
              /bin/bash -c 'mysqlsh \
              --uri="root:$MYSQL_ROOT_PASSWORD@dbc1-0.mysql.mysql1.svc.cluster.local"'
        

        MySQL InnoDB ClusterSet を作成する手順は次のとおりです。

        1. MySQL Shell ターミナルで、クラスタ オブジェクトを取得します。

          \js
          cluster=dba.getCluster()
          

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

          <Cluster:mycluster>
          
        2. クラスタ オブジェクトにプライマリとして保存されている既存の MySQL InnoDB クラスタを使用して、MySQL InnoDB ClusterSet を初期化します。

          clusterset=cluster.createClusterSet('clusterset')
          

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

          A new ClusterSet will be created based on the Cluster 'mycluster'.
          
          * Validating Cluster 'mycluster' for ClusterSet compliance.
          
          * Creating InnoDB ClusterSet 'clusterset' on 'mycluster'...
          
          * Updating metadata...
          
          ClusterSet successfully created. Use ClusterSet.createReplicaCluster() to add Replica Clusters to it.
          
          <ClusterSet:clusterset>
          
        3. MySQL InnoDB ClusterSet のステータスを確認します。

          clusterset.status()
          

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

          {
              "clusters": {
                  "mycluster": {
                      "clusterRole": "PRIMARY",
                      "globalStatus": "OK",
                      "primary": "dbc1-0.mysql:3306"
                  }
              },
              "domainName": "clusterset",
              "globalPrimaryInstance": "dbc1-0.mysql:3306",
              "primaryCluster": "mycluster",
              "status": "HEALTHY",
              "statusText": "All Clusters available."
          }
          

          必要に応じて、clusterset.status({extended:1}) を呼び出して、クラスタに関する情報など、追加のステータスの詳細を取得できます。

        4. MySQL Shell を終了します。

          \q
          

        MySQL Router をデプロイする

        MySQL Router をデプロイして、クライアント アプリケーションのトラフィックを適切なクラスタに転送できます。ルーティングは、データベース オペレーションを発行するアプリケーションの接続ポートに基づきます。

        • 書き込みは、プライマリ ClusterSet のプライマリ クラスタ インスタンスにルーティングされます。
        • 読み取りは、プライマリ クラスタ内の任意のインスタンスにルーティングできます。

        MySQL Router を起動すると、MySQL InnoDB ClusterSet デプロイに対してブートストラップされます。MySQL InnoDB ClusterSet に接続されている MySQL Router インスタンスは、制御されたスイッチオーバーまたは緊急フェイルオーバーを認識し、トラフィックを新しいプライマリ クラスタに転送します。

        MySQL Router をデプロイする手順は次のとおりです。

        1. Cloud Shell ターミナルで、MySQL Router をデプロイします。

          kubectl apply -n mysql1 -f c1-router.yaml
          

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

          configmap/mysql-router-config created
          service/mysql-router created
          deployment.apps/mysql-router created
          
        2. MySQL Router のデプロイの準備状況を確認します。

          kubectl -n mysql1 get deployment mysql-router --watch
          

          3 つの Pod がすべて準備完了になると、出力は次のようになります。

          NAME           READY   UP-TO-DATE   AVAILABLE   AGE
          mysql-router   3/3     3            0           3m36s
          

          コンソールに PodUnschedulable エラーが表示された場合は、GKE が追加のノードをプロビジョニングするまで 1~2 分待ちます。更新すると、3/3 OK が表示されます。

        3. 既存のクラスタの任意のメンバーで MySQL Shell を起動します。

          kubectl -n mysql1 exec -it dbc1-0 -- \
              /bin/bash -c 'mysqlsh --uri="root:$MYSQL_ROOT_PASSWORD@dbc1-0.mysql"'
          

          このコマンドは、dbc1-0 Pod に接続し、dbc1-0 MySQL インスタンスに接続されたシェルを起動します。

        4. ルーターの構成を確認します。

          clusterset=dba.getClusterSet()
          clusterset.listRouters()
          

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

          {
            "domainName": "clusterset",
            "routers": {
              "mysql-router-7cd8585fbc-74pkm::": {
                  "hostname": "mysql-router-7cd8585fbc-74pkm",
                  "lastCheckIn": "2022-09-22 23:26:26",
                  "roPort": 6447,
                  "roXPort": 6449,
                  "rwPort": 6446,
                  "rwXPort": 6448,
                  "targetCluster": null,
                  "version": "8.0.27"
              },
              "mysql-router-7cd8585fbc-824d4::": {
                ...
              },
              "mysql-router-7cd8585fbc-v2qxz::": {
                ...
              }
            }
          }
          
        5. MySQL Shell を終了します。

          \q
          
        6. このスクリプトを実行して、MySQL Router Pod の配置を調べます。

          bash ../scripts/inspect_pod_node.sh mysql1 | sort
          

          このスクリプトは、mysql1 Namespace 内のすべての Pod のノードとクラウドゾーンの配置を表示します。出力は次のようになります。

          gke-gkemulti-west-5-default-pool-1ac6e8b5-0h9v us-west1-c mysql-router-6654f985f5-df97q
          gke-gkemulti-west-5-default-pool-1ac6e8b5-ddjx us-west1-c dbc1-1
          gke-gkemulti-west-5-default-pool-1f5baa66-bf8t us-west1-a dbc1-2
          gke-gkemulti-west-5-default-pool-1f5baa66-kt03 us-west1-a mysql-router-6654f985f5-qlfj9
          gke-gkemulti-west-5-default-pool-4bcaca65-2l6s us-west1-b mysql-router-6654f985f5-5967d
          gke-gkemulti-west-5-default-pool-4bcaca65-jch0 us-west1-b dbc1-0
          

          MySQL Router の Pod がゾーン間で均等に分散されていることがわかります。つまり、MySQL Pod と同じノードに配置されていないか、別の MySQL Router Pod と同じノードに配置されていません。

        GKE と MySQL InnoDB クラスタのアップグレードを管理する

        MySQL と Kubernetes の更新は定期的にリリースされます。運用のベスト プラクティスに従って、ソフトウェア環境を定期的に更新します。デフォルトでは、GKE はクラスタとノードプールを管理して、アップグレードします。Kubernetes と GKE には、MySQL ソフトウェアのアップグレードを容易にする追加機能も用意されています。

        GKE のアップグレードを計画する

        ステートフル サービスの実行時に、次のように予防的な措置を講じて、リスクを軽減してクラスタのアップグレードをよりスムーズに行うための構成を設定できます。

        • 標準クラスタ: クラスタのアップグレードに関する GKE のベスト プラクティスに沿って実施します。適切なアップグレード戦略を選択して、メンテナンスの時間枠の期間内にアップグレードが確実に行われるようにします。

          • コストの最適化が重要であり、ワークロードが 60 分未満で正常なシャットダウンを許容できる場合は、サージ アップグレードを選択します。
          • ワークロードの中断を許容できず、リソース使用量の増加によって一時的な費用の増加が許容される場合は、Blue/Green アップグレードを選択します。

          詳細については、ステートフル ワークロードを実行するクラスタをアップグレードするをご覧ください。Autopilot クラスタは、選択したリリース チャンネルに基づいて自動的にアップグレードされます。

        • メンテナンスの時間枠を使用すると、アップグレードが意図したときに行われるようになります。メンテナンスの時間枠の前に、データベースのバックアップが成功したことを確認します。

        • アップグレードされた MySQL ノードへのトラフィックを許可する前に、Readiness プローブと Liveness プローブを使用して、トラフィックの準備ができていることを確認します。

        • トラフィックの受信前に、レプリケーションが同期しているかどうかを評価するプローブを作成します。これは、データベースの複雑さと規模に応じて、カスタム スクリプトを通じて実行できます。

        Pod 停止予算(PDB)ポリシーを設定する

        MySQL InnoDB クラスタが GKE で実行されている場合、クォーラムの要件を満たすために十分な数のインスタンスが常に実行されている必要があります。

        このチュートリアルでは、3 つのインスタンスから成る MySQL クラスタを想定し、クォーラムを形成するために 2 つのインスタンスを使用できる必要があります。PodDisruptionBudget ポリシーによって、任意の時点で終了できる Pod の数の制限が可能になります。これは、ステートフル サービスの安定状態でのオペレーションと、クラスタのアップグレードの両方に役立ちます。

        同時に中断される Pod の数を限定するには、ワークロードの PDB を maxUnavailable: 1 に設定します。これにより、サービス オペレーションのどの時点でも、複数の Pod が実行されないことが保証されます。

        次の PodDisruptionBudget ポリシー マニフェストは、MySQL アプリケーションで使用できない Pod の最大数を 1 に設定します。

        apiVersion: policy/v1
        kind: PodDisruptionBudget
        metadata:
          name: mysql-pdb
        spec:
          maxUnavailable: 1
          selector:
            matchLabels:
              app: mysql

        PDB ポリシーをクラスタに適用する手順は次のとおりです。

        1. kubectl を使用して PDB ポリシーを適用します。

          kubectl apply -n mysql1 -f mysql-pdb-maxunavailable.yaml
          
        2. PDB のステータスを表示します。

          kubectl get poddisruptionbudgets -n mysql1 mysql-pdb -o yaml
          

          出力の status セクションで、currentHealthydesiredHealthy の Pod 数を確認します。出力は次のようになります。

          status:
          ...
            currentHealthy: 3
            desiredHealthy: 2
            disruptionsAllowed: 1
            expectedPods: 3
          ...
          

        MySQL のバイナリ アップグレードを計画する

        Kubernetes と GKE には、MySQL バイナリのアップグレードを容易にする機能があります。ただし、アップグレードの準備として、いくつかの操作を行う必要があります。

        アップグレード プロセスを開始する前に、次の点を考慮してください。

        • アップグレードは、まずテスト環境で実施する必要があります。本番環境システムのために、本番前環境でさらにテストを実施する必要があります。
        • 一部のバイナリ リリースでは、アップグレードを実行した後にバージョンをダウングレードすることはできません。時間をかけてアップグレードの影響を理解してください。
        • レプリケーション ソースは新しいバージョンに複製できます。ただし、通常、新しいバージョンから古いバージョンにコピーすることはできません。
        • アップグレードされたバージョンをデプロイする前に、データベースの完全なバックアップが行われていることを確認してください。
        • Kubernetes Pod のエフェメラルであるという特性に注意してください。Pod が再デプロイされると、永続ボリュームにない Pod に保存されている構成状態はすべて失われます。
        • MySQL のバイナリ アップグレードの場合は、前述と同じ PDB、ノードプールの更新戦略、プローブを使用します。

        本番環境では、次のベスト プラクティスに沿って実施してください。

        • 新しいバージョンの MySQL でコンテナ イメージを作成します。
        • イメージのビルド手順をソース コントロール リポジトリに保持します。
        • Cloud Build などの自動化されたイメージのビルドとテスト パイプラインを使用し、Artifact Registry などのイメージ レジストリにイメージ バイナリを保存します。

        このチュートリアルをシンプルにするため、コンテナ イメージをビルドして永続化するのではなく、公開されている MySQL イメージを使用します。

        アップグレードされた MySQL バイナリをデプロイする

        MySQL バイナリのアップグレードを実行するには、StatefulSet リソースのイメージ バージョンを変更する宣言型コマンドを実行します。現在の Pod を停止して、アップグレードされたバイナリで新しい Pod をデプロイし、新しい Pod に永続ディスクを接続するために必要な手順が GKE により実施されます。

        1. PDB が作成されたことを確認します。

          kubectl get poddisruptionbudgets -n mysql1
          
        2. ステートフル セットのリストを取得します。

          kubectl get statefulsets -n mysql1
          
        3. app ラベルを使用して、実行中の Pod のリストを取得します。

          kubectl get pods --selector=app=mysql -n mysql1
          
        4. ステートフル セット内の MySQL イメージを更新します。

          kubectl  -n mysql1 \
              set image statefulset/dbc1 \
              mysql=mysql/mysql-server:8.0.30
          

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

          statefulset.apps/mysql image updated
          
        5. 終了元の Pod と新しい Pod のステータスを確認します。

          kubectl get pods --selector=app=mysql -n mysql1
          

        MySQL バイナリのアップグレードを検証する

        アップグレード中に、ロールアウト、新しい Pod、既存の Service のステータスを確認できます。

        1. rollout status コマンドを実行してアップグレードを確認します。

          kubectl rollout status statefulset/dbc1 -n mysql1
          

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

          partitioned roll out complete: 3 new pods have been updated...
          
        2. ステートフル セットを調べてイメージのバージョンを確認します。

          kubectl get statefulsets -o wide -n mysql1
          

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

          NAME   READY   AGE   CONTAINERS   IMAGES
          dbc1   3/3     37m   mysql        mysql/mysql-server:8.0.30
          
        3. クラスタのステータスを確認します。

          kubectl -n mysql1 \
               exec -it dbc1-0 -- \
                 /bin/bash \
                   -c 'mysqlsh \
                   --uri="root:$MYSQL_ROOT_PASSWORD@dbc1-1.mysql.mysql1.svc.cluster.local" \
                   --js \
                   --execute "print(dba.getClusterSet().status({extended:1})); print(\"\\n\")"'
          

          クラスタ インスタンスごとに、出力でステータスとバージョンの値を探します。出力は次のようになります。

          ...
            "status": "ONLINE",
            "version": "8.0.30"
          ...
          

        最後のアプリのデプロイのロールアウトをロールバックする

        アップグレードされたバイナリ バージョンの Deployment を元に戻すと、ロールアウト プロセスが逆になり、前のイメージ バージョンで Pod の新しいセットがデプロイされます。

        Deployment を以前の作業バージョンに戻すには、rollout undo コマンドを使用します。

        kubectl rollout undo statefulset/dbc1 -n mysql1
        

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

        statefulset.apps/dbc1 rolled back
        

        データベース クラスタの水平スケーリング

        MySQL InnoDB クラスタを水平方向にスケーリングするには、GKE クラスタのノードプールにノードを追加し(Standard クラスタを使用している場合にのみ必要)、追加の MySQL インスタンスをデプロイして、各インスタンスを既存の MySQL InnoDB クラスタに追加します。

        Standard クラスタにノードを追加する

        Autopilot クラスタを使用している場合、このオペレーションは必要ありません。

        Standard クラスタにノードを追加するには、Cloud Shell または Google Cloud コンソールの次の手順に沿って操作します。詳しい手順については、ノードプールのサイズを変更するをご覧ください。

        gcloud

        Cloud Shell で、デフォルトのノードプールを各マネージド インスタンス グループ内の 8 つのインスタンスにサイズ変更します。

        gcloud container clusters resize ${CLUSTER_NAME} \
             --node-pool default-pool \
             --num-nodes=8
        

        Console

        Standard クラスタにノードを追加するには:

        1. Google Cloud コンソールで gkemulti-west1 クラスタページを開きます。
        2. [ノード] を選択し、[デフォルト プール] をクリックします。
        3. [インスタンス グループ] までスクロールします。
        4. インスタンス グループごとに、Number of nodes 値を 5 から 8 ノードにサイズ変更します。

        MySQL Pod をプライマリ クラスタに追加する

        追加の MySQL Pod をデプロイしてクラスタを水平方向にスケーリングする手順は次のとおりです。

        1. Cloud Shell で、MySQL Deployment のレプリカ数を 3 から 5 に更新します。

          kubectl scale  -n mysql1 --replicas=5 -f c1-mysql.yaml
          
        2. デプロイの進行状況を確認します。

          kubectl -n mysql1 get pods --selector=app=mysql -o wide
          

          Pod の準備ができているかどうかを確認するには、--watch フラグを使用してデプロイを監視します。Autopilot クラスタを使用していて、Pod Unschedulable エラーが表示される場合は、GKE が追加の Pod に対応するようにノードをプロビジョニングしている可能性があります。

        3. クラスタに追加する新しい MySQL インスタンスのグループ レプリケーション設定を構成します。

          bash ../scripts/c1-clustersetup.sh 3 4
          

          このスクリプトは、序数 3~4 で Pod で実行されているインスタンスにコマンドを送信します。

        4. MySQL Shell を開きます。

          kubectl -n mysql1 \
            exec -it dbc1-0 -- \
                /bin/bash \
                  -c 'mysqlsh \
                  --uri="root:$MYSQL_ROOT_PASSWORD@dbc1-0.mysql"'
          
        5. 2 つの新しい MySQL インスタンスを構成します。

          dba.configureInstance('root:$MYSQL_ROOT_PASSWORD@dbc1-3.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"),clusterAdmin: 'icadmin', clusterAdminPassword: os.getenv("MYSQL_ADMIN_PASSWORD")});
          dba.configureInstance('root:$MYSQL_ROOT_PASSWORD@dbc1-4.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"),clusterAdmin: 'icadmin', clusterAdminPassword: os.getenv("MYSQL_ADMIN_PASSWORD")});
          

          このコマンドにより、インスタンスが MySQL InnoDB クラスタの使用に適切に構成されているかを確認し、必要な構成の変更を行います。

        6. 新しいインスタンスのいずれかをプライマリ クラスタに追加します。

          cluster = dba.getCluster()
          cluster.addInstance('icadmin@dbc1-3.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
          
        7. プライマリ クラスタに 2 つ目の新しいインスタンスを追加します。

          cluster.addInstance('icadmin@dbc1-4.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
          
        8. ClusterSet のステータスを取得します。これにはクラスタのステータスも含まれます。

          clusterset = dba.getClusterSet()
          clusterset.status({extended: 1})
          

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

          "domainName": "clusterset",
          "globalPrimaryInstance": "dbc1-0.mysql:3306",
          "metadataServer": "dbc1-0.mysql:3306",
          "primaryCluster": "mycluster",
          "status": "HEALTHY",
          "statusText": "All Clusters available."
          
        9. MySQL Shell を終了します。

          \q
          

        クリーンアップ

        このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。

        プロジェクトを削除する

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

      5. In the Google Cloud console, go to the Manage resources page.

        Go to Manage resources

      6. In the project list, select the project that you want to delete, and then click Delete.
      7. In the dialog, type the project ID, and then click Shut down to delete the project.
      8. 次のステップ