GKE에서 Persistent Disk의 MySQL 데이터를 Hyperdisk로 마이그레이션

이 튜토리얼에서는 Google Kubernetes Engine에서 영구 디스크 (PD)의 기존 MySQL 데이터를 Hyperdisk로 마이그레이션하여 스토리지 성능을 업그레이드하는 방법을 보여줍니다. 하이퍼디스크는 영구 디스크보다 높은 IOPS와 처리량을 제공하므로 데이터베이스 쿼리 및 트랜잭션의 지연 시간을 줄여 MySQL 성능을 개선할 수 있습니다. 디스크 스냅샷을 사용하여 머신 유형 호환성에 따라 데이터를 다른 디스크 유형으로 이전할 수 있습니다. 예를 들어 Hyperdisk 볼륨은 Persistent Disk를 지원하지 않는 일부 3세대, 4세대 및 이후 세대 머신 유형(예: N4)과만 호환됩니다. 자세한 내용은 사용 가능한 머신 시리즈를 참고하세요.

Persistent Disk에서 Hyperdisk로의 마이그레이션을 보여주기 위해 이 튜토리얼에서는 Sakila 데이터베이스를 사용하여 샘플 데이터 세트를 제공합니다. Sakila는 튜토리얼과 예의 스키마로 사용할 수 있는 MySQL에서 제공하는 샘플 데이터베이스입니다. 가상의 DVD 대여점을 나타내며 영화, 배우, 고객, 대여 테이블이 포함되어 있습니다.

이 가이드는 스토리지를 만들고 할당하며 데이터 보안 및 데이터 액세스를 관리하는 스토리지 전문가 및 스토리지 관리자를 대상으로 합니다. Google Cloud 콘텐츠에서 참조하는 일반적인 역할과 예시 태스크를 자세히 알아보려면 일반 GKE 사용자 역할 및 태스크를 참고하세요.

배포 아키텍처

다음 다이어그램은 Persistent Disk에서 Hyperdisk로의 마이그레이션 프로세스를 보여줍니다.

  • MySQL 애플리케이션이 N2 머신 유형이 있는 GKE 노드 풀에서 실행되며 영구 디스크 SSD에 데이터를 저장합니다.
  • 데이터 일관성을 보장하기 위해 애플리케이션이 축소되어 새로운 쓰기가 방지됩니다.
  • 영구 디스크의 스냅샷이 생성되어 데이터의 완전한 특정 시점 백업 역할을 합니다.
  • 스냅샷에서 새 하이퍼디스크가 프로비저닝되고 별도의 하이퍼디스크 호환 N4 노드 풀에 새 MySQL 인스턴스가 배포됩니다. 이 새 인스턴스는 새로 생성된 Hyperdisk에 연결되어 고성능 스토리지로의 마이그레이션을 완료합니다.
스냅샷을 사용하여 영구 디스크에서 Hyperdisk로 MySQL 데이터를 마이그레이션하는 방법을 보여주는 아키텍처 다이어그램
그림 1: 스냅샷을 사용하여 Persistent Disk에서 Hyperdisk로 MySQL 데이터 마이그레이션

환경 준비

  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 디렉터리로 이동하여 배포 리소스 만들기를 시작합니다.

    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}
    

영구 디스크에 MySQL 배포

이 섹션에서는 스토리지를 위해 영구 디스크를 사용하는 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. 포드가 regular-pool 노드에 예약되도록 노드 선호도가 포함된 MySQL 인스턴스를 만들고 배포하여 영구 디스크 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
    

    이 매니페스트는 데이터 저장을 위해 동적으로 프로비저닝된 Persistent Disk를 사용하여 MySQL 배포 및 서비스를 만듭니다. root 사용자의 비밀번호는 migration입니다.

  3. MySQL 클라이언트 포드를 배포하여 데이터를 로드하고 데이터 이전이 완료되었는지 확인합니다.

    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. 클라이언트 포드에 연결합니다.

    kubectl exec -it mysql-client -- bash
    
  5. 클라이언트 포드 셸에서 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. 클라이언트 포드 셸을 종료합니다.

    exit
    
  9. MySQL용으로 생성된 PersistentVolume (PV)의 이름을 가져와 환경 변수에 저장합니다.

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

데이터를 하이퍼디스크 볼륨으로 마이그레이션

이제 Persistent Disk SSD 볼륨에 데이터가 저장된 MySQL 워크로드가 있습니다. 이 섹션에서는 스냅샷을 사용하여 이 데이터를 Hyperdisk 볼륨으로 마이그레이션하는 방법을 설명합니다. 이 마이그레이션 접근 방식은 원래 Persistent Disk 볼륨도 보존하므로 필요한 경우 원래 MySQL 인스턴스로 롤백할 수 있습니다.

  1. 워크로드에서 디스크를 분리하지 않고 스냅샷을 만들 수 있지만 MySQL의 데이터 무결성을 보장하려면 스냅샷을 만드는 동안 디스크에 새 쓰기가 발생하지 않도록 해야 합니다. MySQL 배포를 0개의 복제본으로 축소하여 쓰기를 중지합니다.

    kubectl scale deployment regular-mysql --replicas=0
    
  2. 기존 영구 디스크에서 스냅샷을 만듭니다.

    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. 새 하이퍼디스크에서 PersistentVolume (PVC) 및 PersistentVolumeClaim을 만듭니다.

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

데이터 이전 확인

새로 만든 Hyperdisk 볼륨을 사용하는 새 MySQL 인스턴스를 배포합니다. 이 포드는 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. 클라이언트 포드 내에서 새 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';
    

    데이터는 영구 디스크 볼륨의 원래 MySQL 인스턴스와 동일해야 합니다.

  4. mysql 세션을 종료합니다.

    exit;
    
  5. 클라이언트 포드 셸을 종료합니다.

    exit