GKE で Persistent Disk から Hyperdisk に MySQL データを移行する

このチュートリアルでは、既存の MySQL データを Google Kubernetes Engine の Persistent Disk(PD)から Hyperdisk に移行して、ストレージ パフォーマンスをアップグレードする方法について説明します。Hyperdisk は Persistent Disk よりも高い IOPS とスループットを提供します。これにより、データベース クエリとトランザクションのレイテンシを短縮し、MySQL のパフォーマンスを向上させることができます。ディスク スナップショットを使用すると、マシンタイプの互換性に応じて、データを別のディスクタイプに移行できます。たとえば、Hyperdisk ボリュームは、Persistent Disk をサポートしていない N4 などの第 3 世代、第 4 世代以降の一部のマシンタイプとのみ互換性があります。詳細については、利用可能なマシンシリーズをご覧ください。

このチュートリアルでは、Persistent Disk から Hyperdisk への移行を説明するために、Sakila データベースを使用してサンプル データセットを提供します。Sakila は、MySQL が提供するサンプル データベースです。チュートリアルや例のスキーマとして使用できます。これは架空の DVD レンタル店を表しており、映画、俳優、顧客、レンタルのテーブルが含まれています。

このガイドは、ストレージの作成と割り当てを行い、データ セキュリティとデータアクセスを管理するストレージ スペシャリストとストレージ管理者を対象としています。 Google Cloud のコンテンツで使用されている一般的なロールとタスクの例の詳細については、一般的な GKE ユーザー ロールとタスクをご覧ください。

デプロイ アーキテクチャ

次の図は、Persistent Disk から Hyperdisk への移行プロセスを示しています。

  • MySQL アプリケーションは、N2 マシンタイプを使用する GKE ノードプールで実行され、データを Persistent Disk SSD に保存します。
  • データの整合性を確保するため、アプリケーションはスケールダウンされ、新しい書き込みが防止されます。
  • Persistent Disk のスナップショットが作成され、データの完全なポイントインタイム バックアップとして機能します。
  • スナップショットから新しい Hyperdisk がプロビジョニングされ、新しい MySQL インスタンスが Hyperdisk 互換の別の N4 ノードプールにデプロイされます。この新しいインスタンスが新しく作成された Hyperdisk にアタッチされ、高パフォーマンス ストレージへの移行が完了します。
スナップショットを使用して Persistent Disk から Hyperdisk に MySQL データを移行するアーキテクチャ図。
図 1: スナップショットを使用した Persistent Disk から Hyperdisk への MySQL データの移行。

目標

このチュートリアルでは、次の方法について学習します。

  • MySQL クラスタをデプロイします。
  • テスト データセットをアップロードします。
  • データのスナップショットを作成します。
  • スナップショットから Hyperdisk を作成します。
  • Hyperdisk 対応の N4 マシンタイプ ノードプールで新しい MySQL クラスタを起動します。
  • データの完全性を検証して、移行が成功したことを確認します。

費用

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

  • GKE
  • Compute Engine, which includes:
    • Storage capacity provisioned for both Persistent Disk and Hyperdisk.
    • Storage costs for the snapshots.

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

新規の 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, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

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

  4. Enable the Compute Engine, GKE, Identity and Access Management Service Account Credentials APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

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

  7. Enable the Compute Engine, GKE, Identity and Access Management Service Account Credentials APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  8. Make sure that you have the following role or roles on the project: roles/container.admin, roles/iam.serviceAccountAdmin, roles/compute.admin

    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. [保存] をクリックします。
    8. Cloud Shell を設定する

      1. In the Google Cloud console, activate Cloud Shell.

        Activate Cloud Shell

        Cloud Shell セッションが開始し、コマンドライン プロンプトが表示されます。セッションが初期化されるまで数秒かかることがあります。

      2. デフォルト プロジェクトを設定します。

          gcloud config set project PROJECT_ID
        

        PROJECT_ID は、実際のプロジェクト ID に置き換えます。

      3. 環境を準備する

        1. Cloud Shell で、プロジェクト、ロケーション、クラスタ プレフィックスの環境変数を設定します。

          export PROJECT_ID=PROJECT_ID
          export EMAIL_ADDRESS=EMAIL_ADDRESS
          export KUBERNETES_CLUSTER_PREFIX=offline-hyperdisk-migration
          export LOCATION=us-central1-a
          

          次のように置き換えます。

          • PROJECT_ID: 実際の Google Cloud プロジェクト ID
          • EMAIL_ADDRESS: メールアドレス。
          • LOCATION: デプロイ リソースを作成するゾーン。このチュートリアルでは、us-central1-a ゾーンを使用します。
        2. GitHub からサンプルコード リポジトリのクローンを作成します。

          git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
          
        3. offline-hyperdisk-migration ディレクトリに移動して、Deployment リソースの作成を開始します。

          cd kubernetes-engine-samples/databases/offline-hyperdisk-migration
          

        GKE クラスタとノードプールを作成する

        このチュートリアルでは、Hyperdisk ボリュームはゾーンリソースであり、単一のゾーン内でのみアクセスできるため、簡略化のためにゾーンクラスタを使用します。

        1. ゾーン GKE クラスタを作成します。

          gcloud container clusters create ${KUBERNETES_CLUSTER_PREFIX}-cluster \
              --location ${LOCATION} \
              --node-locations ${LOCATION} \
              --shielded-secure-boot \
              --shielded-integrity-monitoring \
              --machine-type "e2-micro" \
              --num-nodes "1"
          
        2. 初期の MySQL デプロイ用に N2 マシンタイプのノードプールを追加します。

          gcloud container node-pools create regular-pool \
              --cluster ${KUBERNETES_CLUSTER_PREFIX}-cluster \
              --machine-type n2-standard-4 \
              --location ${LOCATION} \
              --num-nodes 1
          
        3. MySQL デプロイが移行されて実行される Hyperdisk に、N4 マシンタイプを使用してノードプールを追加します。

          gcloud container node-pools create hyperdisk-pool \
              --cluster ${KUBERNETES_CLUSTER_PREFIX}-cluster \
              --machine-type n4-standard-4 \
              --location ${LOCATION} \
              --num-nodes 1
          
        4. クラスタに接続します。

          gcloud container clusters get-credentials ${KUBERNETES_CLUSTER_PREFIX}-cluster --location ${LOCATION}
          

        Persistent Disk に MySQL をデプロイする

        このセクションでは、ストレージに Persistent Disk を使用する MySQL インスタンスをデプロイし、サンプルデータを読み込みます。

        1. Hyperdisk の StorageClass を作成して適用します。この StorageClass は、このチュートリアルの後半で使用します。

          apiVersion: storage.k8s.io/v1
          kind: StorageClass
          metadata:
            name: balanced-storage
          provisioner: pd.csi.storage.gke.io
          volumeBindingMode: WaitForFirstConsumer
          allowVolumeExpansion: true
          parameters:
            type: hyperdisk-balanced
            provisioned-throughput-on-create: "250Mi"
            provisioned-iops-on-create: "7000"
          kubectl apply -f manifests/01-storage-class/storage-class-hdb.yaml
          
        2. ノード アフィニティを含む MySQL インスタンスを作成してデプロイし、Pod が regular-pool ノードでスケジュール設定されるようにして、Persistent Disk SSD ボリュームをプロビジョニングします。

          apiVersion: v1
          kind: Service
          metadata:
            name: regular-mysql
            labels:
              app: mysql
          spec:
            ports:
              - port: 3306
            selector:
              app: mysql
            clusterIP: None
          ---
          apiVersion: v1
          kind: PersistentVolumeClaim
          metadata:
            name: mysql-pv-claim
            labels:
              app: mysql
          spec:
            accessModes:
              - ReadWriteOnce
            resources:
              requests:
                storage: 30Gi
            storageClassName: premium-rwo
          ---
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: existing-mysql
            labels:
              app: mysql
          spec:
            selector:
              matchLabels:
                app: mysql
            strategy:
              type: Recreate
            template:
              metadata:
                labels:
                  app: mysql
              spec:
                containers:
                - image: mysql:8.0
                  name: mysql
                  env:
                  - name: MYSQL_ROOT_PASSWORD
                    value: migration
                  - name: MYSQL_DATABASE
                    value: mysql
                  - name: MYSQL_USER
                    value: app
                  - name: MYSQL_PASSWORD
                    value: migration
                  ports:
                  - containerPort: 3306
                    name: mysql
                  volumeMounts:
                  - name: mysql-persistent-storage
                    mountPath: /var/lib/mysql
                affinity: 
                  nodeAffinity:
                    preferredDuringSchedulingIgnoredDuringExecution:
                    - weight: 1
                      preference:
                        matchExpressions:
                        - key: "node.kubernetes.io/instance-type"
                          operator: In
                          values:
                          - "n2-standard-4"
                volumes:
                - name: mysql-persistent-storage
                  persistentVolumeClaim:
                    claimName: mysql-pv-claim
          kubectl apply -f manifests/02-mysql/mysql-deployment.yaml
          

          このマニフェストは、データ ストレージ用に動的にプロビジョニングされた永続ディスクを使用して、MySQL のデプロイとサービスを作成します。root ユーザーのパスワードは migration です。

        3. MySQL クライアント Pod をデプロイしてデータを読み込み、データ移行を検証します。

          apiVersion: v1
          kind: Pod
          metadata:
            name: mysql-client
          spec:
            containers:
            - name: main
              image: mysql:8.0
              command: ["sleep", "360000"]
              resources:
                requests:
                  memory: 1Gi
                  cpu: 500m
                limits:
                  memory: 1Gi
                  cpu: "1"
              env:
              - name: MYSQL_ROOT_PASSWORD
                value: migration
          kubectl apply -f manifests/02-mysql/mysql-client.yaml
          kubectl wait pods mysql-client --for condition=Ready --timeout=300s
          
        4. クライアント Pod に接続します。

          kubectl exec -it mysql-client -- bash
          
        5. クライアント Pod シェルから、Sakila サンプル データセットをダウンロードしてインポートします。

          # Download the dataset
          curl --output dataset.tgz "https://downloads.mysql.com/docs/sakila-db.tar.gz"
          
          # Extract the dataset
          tar -xvzf dataset.tgz -C /home/mysql
          
          # Import the dataset into MySQL (the password is "migration").
          mysql -u root -h regular-mysql.default -p
              SOURCE /sakila-db/sakila-schema.sql;
              SOURCE /sakila-db/sakila-data.sql;
          
        6. データがインポートされたことを確認します。

          USE sakila;
          SELECT      table_name,      table_rows  FROM      INFORMATION_SCHEMA.TABLES  WHERE TABLE_SCHEMA = 'sakila';
          

          出力には、行数を含むテーブルのリストが表示されます。

          | TABLE_NAME                 | TABLE_ROWS |
          +----------------------------+------------+
          | actor                      |        200 |
          | actor_info                 |       NULL |
          | address                    |        603 |
          | category                   |         16 |
          | city                       |        600 |
          | country                    |        109 |
          | customer                   |        599 |
          | customer_list              |       NULL |
          | film                       |       1000 |
          | film_actor                 |       5462 |
          | film_category              |       1000 |
          | film_list                  |       NULL |
          | film_text                  |       1000 |
          | inventory                  |       4581 |
          | language                   |          6 |
          | nicer_but_slower_film_list |       NULL |
          | payment                    |      16086 |
          | rental                     |      16419 |
          | sales_by_film_category     |       NULL |
          | sales_by_store             |       NULL |
          | staff                      |          2 |
          | staff_list                 |       NULL |
          | store                      |          2 |
          +----------------------------+------------+
          23 rows in set (0.01 sec)
          
        7. mysql セッションを終了します。

          exit;
          
        8. クライアント Pod のシェルを終了します。

          exit
          
        9. MySQL 用に作成された PersistentVolume(PV)の名前を取得し、環境変数に保存します。

          export PV_NAME=$(kubectl get pvc mysql-pv-claim -o jsonpath='{.spec.volumeName}')
          

        データを Hyperdisk ボリュームに移行する

        これで、Persistent Disk SSD ボリュームにデータが保存された MySQL ワークロードができました。このセクションでは、スナップショットを使用してこのデータを Hyperdisk ボリュームに移行する方法について説明します。この移行方法では、元の Persistent Disk ボリュームも保持されるため、必要に応じて元の MySQL インスタンスに戻すことができます。

        1. ワークロードからディスクを切り離さずにスナップショットを作成できますが、MySQL のデータ整合性を確保するには、スナップショットの作成中にディスクへの新しい書き込みを停止する必要があります。MySQL Deployment を 0 レプリカにスケールダウンして、書き込みを停止します。

          kubectl scale deployment regular-mysql --replicas=0
          
        2. 既存の Persistent Disk からスナップショットを作成します。

          gcloud compute disks snapshot ${PV_NAME} --location=${LOCATION} --snapshot-name=original-snapshot --description="snapshot taken from pd-ssd"
          
        3. スナップショットから mysql-recovery という名前の新しい Hyperdisk ボリュームを作成します。

          gcloud compute disks create mysql-recovery --project=${PROJECT_ID} \
              --type=hyperdisk-balanced \
              --size=150GB --location=${LOCATION} \
              --source-snapshot=projects/${PROJECT_ID}/global/snapshots/original-snapshot
          
        4. 復元された PV のマニフェスト ファイルをプロジェクト ID で更新します。

          apiVersion: v1
          kind: PersistentVolume
          metadata:
            name: backup
          spec:
            storageClassName: balanced-storage
            capacity:
              storage: 150G
            accessModes:
              - ReadWriteOnce
            claimRef:
              name: hyperdisk-recovery
              namespace: default
            csi:
              driver: pd.csi.storage.gke.io
              volumeHandle: projects/PRJCTID/zones/us-central1-a/disks/mysql-recovery
              fsType: ext4
          ---
          apiVersion: v1
          kind: PersistentVolumeClaim
          metadata:
            namespace: default
            name: hyperdisk-recovery
          spec:
            storageClassName: balanced-storage
            accessModes:
              - ReadWriteOnce
            resources:
              requests:
                storage: 150G
          sed -i "s/PRJCTID/$PROJECT_ID/g" manifests/02-mysql/restore_pv.yaml
          
        5. 新しい Hyperdisk から PersistentVolume(PVC)と PersistentVolumeClaim を作成します。

          kubectl apply -f manifests/02-mysql/restore_pv.yaml
          

        データ移行を確認する

        新しく作成した Hyperdisk ボリュームを使用する新しい MySQL インスタンスをデプロイします。この Pod は、N4 ノードで構成される hyperdisk-pool ノードプールでスケジュールされます。

        1. 新しい MySQL インスタンスをデプロイします。

          apiVersion: v1
          kind: Service
          metadata:
            name: recovered-mysql
            labels:
              app: new-mysql
          spec:
            ports:
              - port: 3306
            selector:
              app: new-mysql
            clusterIP: None
          ---
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: new-mysql
            labels:
              app: new-mysql
          spec:
            selector:
              matchLabels:
                app: new-mysql
            strategy:
              type: Recreate
            template:
              metadata:
                labels:
                  app: new-mysql
              spec:
                containers:
                - image: mysql:8.0
                  name: mysql
                  env:
                  - name: MYSQL_ROOT_PASSWORD
                    value: migration
                  - name: MYSQL_DATABASE
                    value: mysql
                  - name: MYSQL_USER
                    value: app
                  - name: MYSQL_PASSWORD
                    value: migration
                  ports:
                  - containerPort: 3306
                    name: mysql
                  volumeMounts:
                  - name: mysql-persistent-storage
                    mountPath: /var/lib/mysql
                affinity: 
                  nodeAffinity:
                    preferredDuringSchedulingIgnoredDuringExecution:
                    - weight: 1
                      preference:
                        matchExpressions:
                        - key: "cloud.google.com/gke-nodepool"
                          operator: In
                          values:
                          - "hyperdisk-pool"      
                volumes:
                - name: mysql-persistent-storage
                  persistentVolumeClaim:
                    claimName: hyperdisk-recovery
          kubectl apply -f manifests/02-mysql/recovery_mysql_deployment.yaml
          
        2. データの完全性を確認するには、MySQL クライアント Pod に再度接続します。

          kubectl exec -it mysql-client -- bash
          
        3. クライアント Pod 内で、新しい MySQL データベース(recovered-mysql.default)に接続し、データを確認します。パスワードは migration です。

          mysql -u root -h recovered-mysql.default -p
          USE sakila;
          SELECT table_name, table_rows FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'sakila';
          

          データは、Persistent Disk ボリューム上の元の MySQL インスタンスと同じである必要があります。

        4. mysql セッションを終了します。

          exit;
          
        5. クライアント Pod のシェルを終了します。

          exit
          

        クリーンアップ

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

        プロジェクトの削除

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

          Go to Manage resources

        2. In the project list, select the project that you want to delete, and then click Delete.
        3. In the dialog, type the project ID, and then click Shut down to delete the project.

        リソースを個別に削除する

        既存のプロジェクトを使用していて、そのプロジェクトを削除しない場合は、個々のリソースを削除します。

        1. クリーンアップの環境変数を設定し、mysql-pv-claim PersistentVolumeClaim によって作成された Persistent Disk ボリュームの名前を取得します。

          export PROJECT_ID=PROJECT_ID
          export KUBERNETES_CLUSTER_PREFIX=offline-hyperdisk-migration
          export location=us-central1-a
          export PV_NAME=$(kubectl get pvc mysql-pv-claim -o jsonpath='{.spec.volumeName}')
          

          PROJECT_ID は、実際のプロジェクト ID に置き換えます。

        2. スナップショットを削除します。

          gcloud compute snapshots delete original-snapshot --quiet
          
        3. GKE クラスタを削除します。

          gcloud container clusters delete ${KUBERNETES_CLUSTER_PREFIX}-cluster --location=${LOCATION} --quiet
          
        4. Persistent Disk ボリュームと Hyperdisk ボリュームを削除します。

          gcloud compute disks delete ${PV_NAME} --location=${LOCATION} --quiet
          gcloud compute disks delete mysql-recovery --location=${LOCATION} --quiet
          

        次のステップ