在 GKE 上部署具狀態的 MySQL 叢集


本文適用於有興趣在 Google Kubernetes Engine 上部署高可用性 MySQL 拓撲的資料庫管理員、雲端架構師和營運專員。

本教學課程將說明如何在 GKE 叢集上部署 MySQL InnoDB ClusterMySQL InnoDB ClusterSet,以及 MySQL Router 中介軟體,並瞭解如何執行升級。

目標

您在本教學課程中將學習以下內容:

  • 建立及部署有狀態的 Kubernetes 服務。
  • 部署 MySQL InnoDB Cluster,確保高可用性。
  • 部署 Router 中介軟體,用於資料庫作業的路由。
  • 部署 MySQL InnoDB ClusterSet,確保容錯能力。
  • 模擬 MySQL 叢集容錯移轉。
  • 升級 MySQL 版本。

下列各節說明您將在本教學課程中建構的解決方案架構。

MySQL InnoDB Cluster

在區域 GKE 叢集中,使用 StatefulSet 部署 MySQL 資料庫執行個體,並提供必要命名和設定,以建立 MySQL InnoDB 叢集。為提供容錯能力和高可用性,您會部署三個資料庫執行個體 Pod。這可確保在任何時間,不同區域的大多數 Pod 都能使用共識通訊協定成功選出主要節點,並讓 MySQL InnoDB 叢集容許單一區域故障。

架構圖:顯示應用程式、MySQL Router 和 MySQL Cluster 之間的關係
圖 1:單一 MySQL InnoDB Cluster 的架構範例

部署完成後,您會將一個 Pod 指定為主要執行個體,負責處理讀取和寫入作業。另外兩個 Pod 是次要唯讀副本。如果主要執行個體發生基礎架構故障,您可以將這兩個備用 Pod 之一升級為主要執行個體。

在另一個命名空間中,您會部署三個 MySQL Router Pod,提供連線路徑,提升韌性。應用程式會連線至 MySQL Router Pod,而不是直接連線至資料庫服務。每個 Router Pod 都會瞭解各個 MySQL InnoDB 叢集 Pod 的狀態和用途,並將應用程式作業轉送至相應的正常 Pod。路由狀態會快取在 Router Pod 中,並從儲存在 MySQL InnoDB 叢集每個節點上的叢集中繼資料更新。如果執行個體發生故障,路由器會將連線路徑調整至正常運作的執行個體。

MySQL InnoDB ClusterSet

您可以從初始 MySQL InnoDB Cluster 建立 MySQL InnoDB ClusterSet。 如果主要叢集無法再使用,您就能提高容錯能力。

這張圖顯示主要和副本 MySQL InnoDB 叢集如何透過非同步複製保持同步。
圖 2:多區域 ClusterSet 架構範例,其中包含一個主要叢集和一個副本叢集

如果 MySQL InnoDB Cluster 主要執行個體無法再使用,您可以將 ClusterSet 中的副本叢集升級為主要叢集。使用 MySQL Router 中介軟體時,應用程式不需要追蹤主要資料庫執行個體的健康狀態。選舉完成後,系統會調整路由,將連線傳送至新的主要節點。不過,您有責任確保連線至 MySQL Router 中介軟體的應用程式遵循復原能力最佳做法,以便在叢集容錯移轉期間發生錯誤時重試連線。

費用

在本文件中,您會使用 Google Cloud的下列計費元件:

如要根據預測用量估算費用,請使用 Pricing Calculator

初次使用 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. Make sure 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. Make sure that billing is enabled for your Google Cloud project.

  7. Enable the GKE API.

    Enable the API

  8. 設定角色

    1. Grant roles to your user account. Run the following command once for each of the following IAM roles: role/storage.objectViewer, role/logging.logWriter, role/artifactregistry.Admin, roles/container.clusterAdmin, role/container.serviceAgent, roles/serviceusage.serviceUsageAdmin, roles/iam.serviceAccountAdmin

      gcloud projects add-iam-policy-binding PROJECT_ID --member="user:USER_IDENTIFIER" --role=ROLE
      • Replace PROJECT_ID with your project ID.
      • Replace USER_IDENTIFIER with the identifier for your user account. For example, user:myemail@example.com.

      • Replace ROLE with each individual role.
    2. 設定環境

      在本教學課程中,您將使用 Cloud Shell 管理託管於Google Cloud的資源。Cloud Shell 已預先安裝 Docker,以及 kubectl 和 gcloud CLI。

      如要使用 Cloud Shell 設定環境:

      1. 設定環境變數。

        export PROJECT_ID=PROJECT_ID
        export CLUSTER_NAME=gkemulti-west
        export REGION=COMPUTE_REGION
        

        替換下列值:

        • PROJECT_ID:您的 Google Cloud 專案 ID
        • COMPUTE_REGION:您的 Compute Engine 區域。 在本教學課程中,區域為 us-west1。通常會希望將函數部署到靠近您所在位置的區域。
      2. 設定預設環境變數。

         gcloud config set project PROJECT_ID
         gcloud config set compute/region COMPUTE_REGION
        
      3. 複製程式碼存放區。

        git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
        
      4. 變更為工作目錄。

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

建立 GKE 叢集

在本節中,您將建立地區 GKE 叢集。與區域叢集不同,地區叢集的控制層會複製到多個區域,因此單一區域發生服務中斷時,控制層仍可正常運作。

如要建立 GKE 叢集,請按照下列步驟操作:

Autopilot

  1. 在 Cloud Shell 中,於 us-west1 區域建立 GKE Autopilot 叢集。

    gcloud container clusters create-auto $CLUSTER_NAME \
        --region=$REGION
    
  2. 取得 GKE 叢集憑證。

    gcloud container clusters get-credentials $CLUSTER_NAME \
      --region=$REGION
    
  3. 在三個可用區部署服務。本教學課程使用 Kubernetes Deployment。Deployment 是 Kubernetes API 物件,可讓您執行多個 Pod 副本,並將這些副本分散到叢集的節點中。

    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 會在兩個區域中佈建資源。prepare-for-ha.yaml 中定義的 Deployment 會確保 Autopilot 在叢集的三個區域中佈建節點,方法是設定 replicas:3podAntiAffinityrequiredDuringSchedulingIgnoredDuringExecution,以及 topologyKey: "topology.kubernetes.io/zone"

  4. 檢查部署作業的狀態。

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

    看到三個 Pod 處於就緒狀態時,請使用 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 是否已部署到三個區域。

    bash ../scripts/inspect_pod_node.sh default
    

    輸出內容的每一行都對應至一個 Pod,第二欄則會指出可用區。輸出結果會與下列內容相似:

    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 \
      --region=$REGION \
      --machine-type="e2-standard-2" \
      --disk-type="pd-standard" \
      --num-nodes="5"
    
  2. 取得 GKE 叢集憑證。

    gcloud container clusters get-credentials $CLUSTER_NAME \
      --region=$REGION
    

部署 MySQL StatefulSet

在本節中,您將部署一個 MySQL StatefulSet。StatefulSet 是 Kubernetes 控制器,可為每個 Pod 維護永久的專屬 ID。

每個 StatefulSet 都包含三個 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 Cluster 和 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永久磁碟類型,在效能和成本之間取得平衡。volumeBindingMode 欄位會設為 WaitForFirstConsumer,表示 GKE 會延遲佈建 PersistentVolume,直到建立 Pod 為止。這項設定可確保磁碟佈建在 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
    

    這項指令會部署由三個副本組成的 StatefulSet。在本教學課程中,主要 MySQL 叢集會部署在 us-west1 的三個可用區。輸出結果會與下列內容相似:

    service/mysql created
    statefulset.apps/dbc1 created
    

    在本教學課程中,資源限制和要求會設為最小值,以節省費用。規劃實際運作工作負載時,請務必根據貴機構的需求適當設定這些值。

  5. 確認 StatefulSet 是否已成功建立。

    kubectl get statefulset -n mysql1 --watch
    

    StatefulSet 大約需要 10 分鐘才能準備就緒。

  6. 當這三個 Pod 都處於就緒狀態時,請使用 Ctrl+C 結束指令。如果因 CPU 或記憶體不足而出現 PodUnscheduleable 錯誤,請稍候幾分鐘,控制平面就會調整大小,以因應大量工作負載。

    輸出結果會與下列內容相似:

    NAME   READY   AGE
    dbc1   1/3     39s
    dbc1   2/3     50s
    dbc1   3/3     73s
    
  7. 如要檢查 Pod 在 GKE 叢集節點上的放置位置,請執行下列指令碼:

    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) 會指示排程器將 Pod 平均放置在故障網域 (topology.kubernetes.io/zone) 中。

    podAntiAffinity 政策會強制執行限制,規定 Pod 不得放置在同一個 GKE 叢集節點 (kubernetes.io/hostname)。對於 MySQL 執行個體 Pod,這項政策會導致 Pod 平均部署在 Google Cloud 區域的三個可用區。這項放置作業會將每個資料庫執行個體放在不同的故障網域,確保 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

    這個指令碼會遠端連線至三個 MySQL 執行個體,設定並保留下列環境變數:

    • group_replication_ip_allowlist:允許叢集內的執行個體連線至群組中的任何執行個體。
    • binlog_transaction_dependency_tracking='WRITESET':允許不會衝突的並行交易。

    在 8.0.22 之前的 MySQL 版本中,請使用 group_replication_ip_whitelist,而非 group_replication_ip_allowlist

  2. 開啟第二個終端機,這樣就不必為每個 Pod 建立殼層。

  3. 連線至 Pod 上的 MySQL Shell dbc1-0

    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 Cluster 中使用。

    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 Admin createCluster 指令建立 MySQL InnoDB 叢集。從 dbc1-0 例項開始,這會是叢集的主要例項,然後在叢集中新增兩個額外副本。

如要初始化 MySQL InnoDB 叢集,請按照下列步驟操作:

  1. 建立 MySQL InnoDB 叢集。

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

    執行 createCluster 指令會觸發下列作業:

    • 部署中繼資料結構定義。
    • 確認群組複寫的設定正確無誤。
    • 將其註冊為新叢集的種子執行個體。
    • 建立必要的內部帳戶,例如複製使用者帳戶。
    • 啟動群組複寫。

    這項指令會以主機 dbc1-0 為主要主機,初始化 MySQL InnoDB Cluster。叢集參照會儲存在叢集變數中。

    輸出看起來類似以下內容:

    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. 將第二個執行個體新增至叢集。

    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()
    

    這個指令會顯示叢集狀態。拓撲包含三個主機,一個主要執行個體和兩個次要執行個體。您可以選擇呼叫 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. 確認資料表包含上一個步驟中插入的三個資料列。

    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 Cluster 連結至替代位置 (例如多個可用區和多個區域) 的一或多個副本,為 MySQL InnoDB Cluster 部署作業提供容錯能力。

如果關閉了 MySQL Shell,請在新的 Cloud Shell 終端機中執行下列指令,建立新的 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
    

    當所有三個 Pod 都就緒時,輸出內容會類似以下內容:

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

    如果在控制台中看到 PodUnschedulable 錯誤,請稍候一到兩分鐘,讓 GKE 佈建更多節點。重新整理後,畫面應會顯示 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 命名空間中所有 Pod 的節點和 Cloud Zone 位置,輸出內容類似於下列內容:

    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 Cluster 升級

MySQL 和 Kubernetes 的更新會定期發布。請遵循作業最佳做法,定期更新軟體環境。根據預設,GKE 會為您管理叢集和節點集區升級作業。Kubernetes 和 GKE 也提供額外功能,方便您升級 MySQL 軟體。

規劃 GKE 升級作業

執行有狀態服務時,您可以採取主動措施並設定相關設定,以降低風險並順利升級叢集,包括:

  • 標準叢集:請遵循 GKE 叢集升級最佳做法。選擇適當的升級策略,確保升級作業會在維護期間進行:

    • 如果成本效益很重要,且工作負載能在 60 分鐘內正常關機,請選擇突增升級
    • 如果工作負載較無法容忍中斷,且可接受因資源用量增加而暫時提高成本,請選擇藍綠升級

    詳情請參閱「將執行有狀態工作負載的叢集升級」。 Autopilot 叢集會根據您選取的發布版本自動升級

  • 使用維護期間,確保升級作業會在您預期的時間進行。在維護時段前,請確認資料庫備份作業是否成功。

  • 允許流量進入升級後的 MySQL 節點前,請先使用就緒探測和存活探測,確保節點已準備好接收流量。

  • 建立探查,評估複寫是否同步,再接受流量。視資料庫的複雜度和規模而定,您可以透過自訂指令碼完成這項作業。

設定 Pod disruption budget (PDB) 政策

在 GKE 上執行 MySQL InnoDB 叢集時,隨時都必須有足夠的執行個體數量,才能滿足法定人數需求。

在本教學課程中,假設您有三個執行個體的 MySQL 叢集,則必須有兩個執行個體可用,才能形成法定人數。PodDisruptionBudget 政策可讓您限制在任何時間終止的 Pod 數量。這對有狀態服務的穩定狀態作業和叢集升級都很有用。

如要確保同時中斷的 Pod 數量有限,請將工作負載的 PDB 設為 maxUnavailable: 1。這可確保在服務運作的任何時間點,最多只會有一個 Pod 未執行。

下列 PodDisruptionBudget 政策資訊清單會將 MySQL 應用程式的無法使用 Pod 數量上限設為一。

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 資源的映像檔版本。GKE 會執行必要步驟,停止目前的 Pod、部署搭載升級後二進位檔的新 Pod,並將永久磁碟連接至新 Pod。

  1. 確認 PDB 是否已建立。

    kubectl get poddisruptionbudgets -n mysql1
    
  2. 取得 StatefulSet 清單。

    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 和現有服務。

  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"
    ...
    

復原上次的應用程式部署作業

當您還原升級後的二進位檔版本部署作業時,推出程序會反向執行,並以先前的映像檔版本部署一組新的 Pod。

如要將部署作業還原為先前的有效版本,請使用 rollout undo 指令:

kubectl rollout undo statefulset/dbc1 -n mysql1

輸出結果會與下列內容相似:

statefulset.apps/dbc1 rolled back

水平擴充資料庫叢集

如要水平擴充 MySQL InnoDB 叢集,請將其他節點新增至 GKE 叢集節點集區 (僅在使用 Standard 時需要),部署其他 MySQL 執行個體,然後將每個執行個體新增至現有的 MySQL InnoDB 叢集。

將節點新增至 Standard 叢集

如果您使用 Autopilot 叢集,則不需要執行這項作業。

如要將節點新增至標準叢集,請按照下方 Cloud Shell 或 Google Cloud 控制台的操作說明進行。如需詳細步驟,請參閱「調整節點集區的大小」一文。

gcloud

在 Cloud Shell 中,將預設節點集區的大小調整為每個代管執行個體群組有八個執行個體。

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

控制台

如要將節點新增至標準叢集,請按照下列步驟操作:

  1. 在 Google Cloud 控制台中開啟「叢集」gkemulti-west1頁面。
  2. 選取「節點」,然後按一下「預設集區」
  3. 向下捲動至「執行個體群組」
  4. 針對每個執行個體群組,將 Number of nodes 值從 5 個節點調整為 8 個節點。

將 MySQL Pod 新增至主要叢集

如要部署其他 MySQL Pod,以水平擴展叢集,請按照下列步驟操作:

  1. 在 Cloud Shell 中,將 MySQL 部署作業中的備用資源數量從三個更新為五個。

    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. 設定兩個新的 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 Cluster,並執行必要的設定變更。

  6. 將其中一個新執行個體新增至主要叢集。

    cluster = dba.getCluster()
    cluster.addInstance('icadmin@dbc1-3.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
    
  7. 在主要叢集中新增第二個執行個體。

    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 帳戶收取本教學課程中所用資源的相關費用,請刪除含有該項資源的專案,或者保留專案但刪除個別資源。

刪除專案

如要避免付費,最簡單的方法就是刪除您為了本教學課程而建立的專案。

Delete a Google Cloud project:

gcloud projects delete PROJECT_ID

後續步驟