Desplegar un clúster de MySQL con reconocimiento del estado en GKE

Este documento está dirigido a administradores de bases de datos, arquitectos de soluciones en la nube y profesionales de operaciones que quieran desplegar una topología de MySQL de alta disponibilidad en Google Kubernetes Engine.

Sigue este tutorial para aprender a desplegar un clúster InnoDB de MySQL y un conjunto de clústeres InnoDB de MySQL, así como el middleware MySQL Router en tu clúster de GKE, y a realizar actualizaciones.

Crear un clúster de GKE

En esta sección, crearás un clúster de GKE regional. A diferencia de los clústeres zonales, el plano de control de los clústeres regionales se replica en varias zonas, por lo que una interrupción en una sola zona no provoca que el plano de control deje de estar disponible.

Para crear un clúster de GKE, sigue estos pasos:

Autopilot

  1. En Cloud Shell, crea un clúster de Autopilot de GKE en la región us-west1.

    gcloud container clusters create-auto $CLUSTER_NAME \
        --location=$CONTROL_PLANE_LOCATION
    
  2. Obtén las credenciales del clúster de GKE.

    gcloud container clusters get-credentials $CLUSTER_NAME \
      --location=$CONTROL_PLANE_LOCATION
    
  3. Despliega un servicio en tres zonas. En este tutorial se usa un Deployment de Kubernetes. Un Deployment es un objeto de la API de Kubernetes que te permite ejecutar varias réplicas de pods distribuidas entre los nodos de un clúster.

    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
    

    De forma predeterminada, Autopilot aprovisiona recursos en dos zonas. El Deployment definido en prepare-for-ha.yaml asegura que Autopilot aprovisione nodos en tres zonas de tu clúster. Para ello, se definen replicas:3 y podAntiAffinity con requiredDuringSchedulingIgnoredDuringExecution y topologyKey: "topology.kubernetes.io/zone".

  4. Comprueba el estado del despliegue.

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

    Cuando veas tres pods en estado listo, cancela este comando con CTRL+C. El resultado debería ser similar al siguiente:

    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. Ejecuta esta secuencia de comandos para validar que tus pods se han desplegado en tres zonas.

    bash ../scripts/inspect_pod_node.sh default
    

    Cada línea del resultado corresponde a un pod y la segunda columna indica la zona. El resultado debería ser similar al siguiente:

    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
    

Estándar

  1. En Cloud Shell, crea un clúster estándar de GKE en la región us-west1.

    gcloud container clusters create $CLUSTER_NAME \
      --location=$CONTROL_PLANE_LOCATION \
      --machine-type="e2-standard-2" \
      --disk-type="pd-standard" \
      --num-nodes="5"
    
  2. Obtén las credenciales del clúster de GKE.

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

Desplegar StatefulSets de MySQL

En esta sección, implementarás un MySQL StatefulSet. Un StatefulSet es un controlador de Kubernetes que mantiene una identidad única y persistente para cada uno de sus pods.

Cada StatefulSet consta de tres réplicas de MySQL.

Para implementar el StatefulSet de MySQL, sigue estos pasos:

  1. Crea un espacio de nombres para el StatefulSet.

    kubectl create namespace mysql1
    
  2. Crea el secreto de 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
    

    La contraseña se implementa con cada pod y la usan los comandos y las secuencias de comandos de gestión para la implementación de clústeres y ClusterSet de MySQL InnoDB en este tutorial.

  3. Crea el objeto 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
    

    Esta clase de almacenamiento usa el tipo de disco persistente pd-balanced, que equilibra el rendimiento y el coste. El campo volumeBindingMode se define como WaitForFirstConsumer, lo que significa que GKE retrasa el aprovisionamiento de un PersistentVolume hasta que se crea el pod. Este ajuste asegura que el disco se aprovisione en la misma zona en la que se ha programado el pod.

  4. Despliega el StatefulSet de los pods de la instancia de MySQL.

    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
    

    Este comando implementa el StatefulSet que consta de tres réplicas. En este tutorial, el clúster de MySQL principal se despliega en tres zonas de us-west1. El resultado debería ser similar al siguiente:

    service/mysql created
    statefulset.apps/dbc1 created
    

    En este tutorial, los límites y las solicitudes de recursos se han definido con valores mínimos para ahorrar costes. Cuando planifiques una carga de trabajo de producción, asegúrate de definir estos valores de forma adecuada para las necesidades de tu organización.

  5. Verifica que el StatefulSet se ha creado correctamente.

    kubectl get statefulset -n mysql1 --watch
    

    El StatefulSet puede tardar unos 10 minutos en estar listo.

  6. Cuando los tres pods estén en estado Ready, sal del comando con Ctrl+C. Si aparecen errores PodUnscheduleable debido a que no hay suficiente CPU o memoria, espera unos minutos a que el plano de control cambie de tamaño para adaptarse a la carga de trabajo grande.

    El resultado debería ser similar al siguiente:

    NAME   READY   AGE
    dbc1   1/3     39s
    dbc1   2/3     50s
    dbc1   3/3     73s
    
  7. Para inspeccionar la colocación de tus pods en los nodos del clúster de GKE, ejecuta esta secuencia de comandos:

    bash ../scripts/inspect_pod_node.sh mysql1 mysql
    

    El resultado muestra el nombre del pod, el nombre del nodo de GKE y la zona en la que se aprovisiona el nodo. Es similar al siguiente:

    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
    

    Las columnas de la salida representan el nombre de host, la zona de nube y el nombre del pod, respectivamente.

    La política topologySpreadConstraints de la especificación de StatefulSet (c1-mysql.yaml) indica al programador que coloque los pods de forma uniforme en el dominio de errores (topology.kubernetes.io/zone).

    La política podAntiAffinity aplica la restricción de que los pods no se coloquen en el mismo nodo de clúster de GKE (kubernetes.io/hostname). En el caso de los pods de la instancia de MySQL, esta política hace que los pods se implementen de forma uniforme en las tres zonas de la región Google Cloud . Este emplazamiento permite la alta disponibilidad del clúster InnoDB de MySQL colocando cada instancia de base de datos en un dominio de errores independiente.

Preparar el clúster InnoDB de MySQL principal

Para configurar un clúster InnoDB de MySQL, sigue estos pasos:

  1. En la terminal Cloud Shell, define las configuraciones de replicación de grupos de las instancias de MySQL que se añadirán al clúster.

    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

    El script se conectará de forma remota a cada una de las tres instancias de MySQL para definir y conservar las siguientes variables de entorno:

    • group_replication_ip_allowlist: permite que la instancia del clúster se conecte a cualquier instancia del grupo.
    • binlog_transaction_dependency_tracking='WRITESET': permite transacciones paralelas que no entran en conflicto.

    En las versiones de MySQL anteriores a la 8.0.22, usa group_replication_ip_whitelist en lugar de group_replication_ip_allowlist.

  2. Abre una segunda terminal para no tener que crear un shell para cada pod.

  3. Conéctate a MySQL Shell en el pod 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. Verifica la lista de permitidos de la replicación de grupo de MySQL para conectarte a otras instancias.

    \sql SELECT @@group_replication_ip_allowlist;
    

    El resultado debería ser similar al siguiente:

    +----------------------------------+
    | @@group_replication_ip_allowlist |
    +----------------------------------+
    | mysql.mysql1.svc.cluster.local   |
    +----------------------------------+
    
  5. Verifica que el server-id sea único en cada una de las instancias.

    \sql SELECT @@server_id;
    

    El resultado debería ser similar al siguiente:

    +-------------+
    | @@server_id |
    +-------------+
    |          21 |
    +-------------+
    
  6. Configura cada instancia para usar el clúster InnoDB de MySQL y crea una cuenta de administrador en cada instancia.

    \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")});
    

    Todas las instancias deben tener el mismo nombre de usuario y la misma contraseña para que el clúster InnoDB de MySQL funcione correctamente. Cada comando genera un resultado similar al siguiente:

    ...
    
    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. Verifica que la instancia esté lista para usarse en un clúster InnoDB de MySQL.

    dba.checkInstanceConfiguration()
    

    El resultado debería ser similar al siguiente:

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

    También puedes conectarte a cada instancia de MySQL y repetir este comando. Por ejemplo, ejecuta este comando para comprobar el estado de la instancia 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()"'
    

Crear el clúster InnoDB de MySQL principal

A continuación, crea el clúster InnoDB de MySQL con el comando MySQL Admin createCluster. Empieza con la instancia dbc1-0, que será la instancia principal del clúster, y, a continuación, añade dos réplicas más al clúster.

Para inicializar el clúster InnoDB de MySQL, sigue estos pasos:

  1. Crea el clúster InnoDB de MySQL.

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

    Al ejecutar el comando createCluster, se activan estas operaciones:

    • Implementa el esquema de metadatos.
    • Verifica que la configuración sea correcta para la replicación de grupo.
    • Regístrala como instancia inicial del nuevo clúster.
    • Crea las cuentas internas necesarias, como la cuenta de usuario de replicación.
    • Inicia la replicación de grupo.

    Este comando inicializa un clúster InnoDB de MySQL con el host dbc1-0 como principal. La referencia del clúster se almacena en la variable del clúster.

    El resultado es similar al siguiente:

    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. Añade la segunda instancia al clúster.

    cluster.addInstance('icadmin@dbc1-1.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
    
  3. Añade la instancia restante al clúster.

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

    El resultado debería ser similar al siguiente:

    ...
    The instance 'dbc1-2.mysql:3306' was successfully added to the cluster.
    
  4. Verifica el estado del clúster.

    cluster.status()
    

    Este comando muestra el estado del clúster. La topología consta de tres hosts: una instancia principal y dos secundarias. También puedes llamar a cluster.status({extended:1}).

    El resultado debería ser similar al siguiente:

    {
        "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"
    }
    

    Si quieres, puedes llamar al cluster.status({extended:1}) para obtener más información sobre el estado.

Crear una base de datos de muestra

Para crear una base de datos de ejemplo, sigue estos pasos:

  1. Crea una base de datos y carga datos en ella.

    \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. Inserta datos de muestra en la base de datos. Para insertar datos, debes conectarte a la instancia principal del clúster.

    INSERT INTO loan (firstname, lastname, status) VALUES ( 'Fred','Flintstone','pending');
    INSERT INTO loan (firstname, lastname, status) VALUES ( 'Betty','Rubble','approved');
    
  3. Verifica que la tabla contenga las tres filas insertadas en el paso anterior.

    SELECT * FROM loan;
    

    El resultado debería ser similar al siguiente:

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

Crear un ClusterSet de InnoDB de MySQL

Puedes crear un ClusterSet de MySQL InnoDB para gestionar la replicación desde tu clúster principal a los clústeres de réplica mediante un canal de replicación de ClusterSet específico.

Un conjunto de clústeres InnoDB de MySQL proporciona tolerancia a desastres para las implementaciones de clústeres InnoDB de MySQL. Para ello, vincula un clúster InnoDB de MySQL principal con una o varias réplicas de sí mismo en ubicaciones alternativas, como varias zonas y varias regiones.

Si has cerrado MySQL Shell, crea uno nuevo ejecutando este comando en un nuevo terminal de 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"'

Para crear un conjunto de clústeres InnoDB de MySQL, siga estos pasos:

  1. En el terminal de MySQL Shell, obtén un objeto de clúster.

    \js
    cluster=dba.getCluster()
    

    El resultado debería ser similar al siguiente:

    <Cluster:mycluster>
    
  2. Inicializa un ClusterSet de MySQL InnoDB con el clúster de MySQL InnoDB almacenado en el objeto de clúster como principal.

    clusterset=cluster.createClusterSet('clusterset')
    

    El resultado debería ser similar al siguiente:

    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. Consulta el estado de tu conjunto de clústeres InnoDB de MySQL.

    clusterset.status()
    

    El resultado debería ser similar al siguiente:

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

    Si quieres, puedes llamar a clusterset.status({extended:1}) para obtener más detalles sobre el estado, incluida información sobre el clúster.

  4. Sal de MySQL Shell.

    \q
    

Desplegar un enrutador MySQL

Puedes desplegar un router MySQL para dirigir el tráfico de la aplicación cliente a los clústeres adecuados. El enrutamiento se basa en el puerto de conexión de la aplicación que emite una operación de base de datos:

  • Las escrituras se dirigen a la instancia de clúster principal del ClusterSet principal.
  • Las lecturas se pueden dirigir a cualquier instancia del clúster principal.

Cuando inicias un MySQL Router, se inicia en la implementación de MySQL InnoDB ClusterSet. Las instancias de MySQL Router conectadas con el conjunto de clústeres InnoDB de MySQL detectan cualquier cambio controlado o conmutación por error de emergencia y dirigen el tráfico al nuevo clúster principal.

Para implementar un MySQL Router, sigue estos pasos:

  1. En el terminal de Cloud Shell, implementa MySQL Router.

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

    El resultado debería ser similar al siguiente:

    configmap/mysql-router-config created
    service/mysql-router created
    deployment.apps/mysql-router created
    
  2. Comprueba si el despliegue de MySQL Router está listo.

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

    Cuando los tres pods estén listos, el resultado será similar al siguiente:

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

    Si ves un error PodUnschedulable en la consola, espera uno o dos minutos mientras GKE aprovisiona más nodos. Actualiza la página y deberías ver 3/3 OK.

  3. Inicia MySQL Shell en cualquier miembro del clúster.

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

    Este comando se conecta al pod dbc1-0 y, a continuación, inicia un shell conectado a la instancia de MySQL dbc1-0.

  4. Verifica la configuración del router.

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

    El resultado debería ser similar al siguiente:

    {
      "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. Sal de MySQL Shell.

    \q
    
  6. Ejecuta esta secuencia de comandos para inspeccionar la colocación de los pods de MySQL Router.

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

    La secuencia de comandos muestra la colocación de nodos y zonas de Cloud de todos los pods del espacio de nombres mysql1, donde la salida es similar a la siguiente:

    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
    

    Puedes observar que los pods de MySQL Router se distribuyen de forma equitativa entre las zonas, es decir, no se colocan en el mismo nodo que un pod de MySQL ni en el mismo nodo que otro pod de MySQL Router.

Gestionar las actualizaciones de GKE y MySQL InnoDB Cluster

Las actualizaciones de MySQL y Kubernetes se publican de forma periódica. Sigue las prácticas recomendadas operativas para actualizar tu entorno de software con regularidad. De forma predeterminada, GKE gestiona las actualizaciones de clústeres y grupos de nodos. Kubernetes y GKE también ofrecen funciones adicionales para facilitar las actualizaciones del software MySQL.

Planificar las actualizaciones de GKE

Puedes tomar medidas proactivas y definir configuraciones para reducir los riesgos y facilitar una actualización del clúster más fluida cuando ejecutes servicios con estado, como los siguientes:

  • Clústeres estándar: sigue las prácticas recomendadas de GKE para actualizar clústeres. Elige una estrategia de actualización adecuada para asegurarte de que las actualizaciones se realicen durante el periodo de la ventana de mantenimiento:

    • Elige actualizaciones de picos si la optimización de costes es importante y si tus cargas de trabajo pueden tolerar un cierre gradual en menos de 60 minutos.
    • Elige las actualizaciones azul-verde si tus cargas de trabajo toleran menos las interrupciones y si puedes asumir un aumento temporal de los costes debido a un mayor uso de los recursos.

    Para obtener más información, consulta Actualizar un clúster en el que se ejecuta una carga de trabajo con reconocimiento del estado. Los clústeres Autopilot se actualizan automáticamente en función del canal de lanzamiento que hayas seleccionado.

  • Usa las ventanas de mantenimiento para asegurarte de que las actualizaciones se realicen cuando quieras. Antes de la ventana de mantenimiento, asegúrate de que las copias de seguridad de tu base de datos se hayan creado correctamente.

  • Antes de permitir el tráfico a los nodos de MySQL actualizados, usa las sondas de preparación y las sondas de actividad para asegurarte de que están listos para recibir tráfico.

  • Crea sondas que evalúen si la replicación está sincronizada antes de aceptar el tráfico. Esto se puede hacer mediante secuencias de comandos personalizadas, en función de la complejidad y la escala de tu base de datos.

Definir una política de cobertura para interrupciones de pods (PDB)

Cuando un clúster de MySQL InnoDB se ejecuta en GKE, debe haber un número suficiente de instancias en ejecución en cualquier momento para cumplir el requisito de quórum.

En este tutorial, dado un clúster de MySQL de tres instancias, dos instancias deben estar disponibles para formar un quórum. Una política de PodDisruptionBudget te permite limitar el número de pods que se pueden finalizar en un momento dado. Esto es útil tanto para las operaciones de estado estable de tus servicios con estado como para las actualizaciones de clústeres.

Para asegurarte de que solo se interrumpa un número limitado de pods simultáneamente, define el PDB de tu carga de trabajo en maxUnavailable: 1. De esta forma, se asegura de que, en cualquier momento de la operación del servicio, no haya más de un pod sin ejecutar.

El siguiente manifiesto de la política PodDisruptionBudget define el número máximo de pods no disponibles en uno para tu aplicación MySQL.

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

Para aplicar la política de PDB a tu clúster, sigue estos pasos:

  1. Aplica la política de PDB con kubectl.

    kubectl apply -n mysql1 -f mysql-pdb-maxunavailable.yaml
    
  2. Consulta el estado de la PDB.

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

    En la sección status del resultado, consulta los recuentos de currentHealthy y desiredHealthy Pods. El resultado debería ser similar al siguiente:

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

Planificar las actualizaciones binarias de MySQL

Kubernetes y GKE ofrecen funciones para facilitar las actualizaciones del binario de MySQL. Sin embargo, debe realizar algunas operaciones para preparar las actualizaciones.

Antes de empezar el proceso de actualización, ten en cuenta lo siguiente:

  • Las actualizaciones deben realizarse primero en un entorno de prueba. En el caso de los sistemas de producción, debes realizar más pruebas en un entorno de preproducción.
  • En algunas versiones binarias, no puedes cambiar a una versión anterior una vez que se haya realizado una actualización. Dedica tiempo a comprender las implicaciones de una actualización.
  • Las fuentes de replicación pueden replicarse en una versión más reciente. Sin embargo, normalmente no se admite la copia de una versión más reciente a una anterior.
  • Asegúrate de tener una copia de seguridad completa de la base de datos antes de implementar la versión actualizada.
  • Ten en cuenta la naturaleza efímera de los pods de Kubernetes. Cualquier estado de configuración que almacene el pod y que no esté en el volumen persistente se perderá cuando se vuelva a implementar el pod.
  • Para las actualizaciones binarias de MySQL, usa la misma PDB, estrategia de actualización del grupo de nodos y sondas que se han descrito anteriormente.

En un entorno de producción, debe seguir estas prácticas recomendadas:

  • Crea una imagen de contenedor con la nueva versión de MySQL.
  • Conserva las instrucciones de compilación de la imagen en un repositorio de control de versiones.
  • Usa una canalización automatizada de compilación y pruebas de imágenes, como Cloud Build, y almacena el archivo binario de la imagen en un registro de imágenes, como Artifact Registry.

Para que este tutorial sea sencillo, no crearás ni conservarás una imagen de contenedor, sino que usarás las imágenes públicas de MySQL.

Desplegar el archivo binario de MySQL actualizado

Para llevar a cabo la actualización binaria de MySQL, debes ejecutar un comando declarativo que modifique la versión de la imagen del recurso StatefulSet. GKE lleva a cabo los pasos necesarios para detener el pod actual, implementar un nuevo pod con el binario actualizado y adjuntar el disco persistente al nuevo pod.

  1. Verifica que se haya creado la PDB.

    kubectl get poddisruptionbudgets -n mysql1
    
  2. Obtén la lista de conjuntos con reconocimiento del estado.

    kubectl get statefulsets -n mysql1
    
  3. Obtén la lista de pods en ejecución con la etiqueta app.

    kubectl get pods --selector=app=mysql -n mysql1
    
  4. Actualiza la imagen de MySQL en el conjunto con estado.

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

    El resultado debería ser similar al siguiente:

    statefulset.apps/mysql image updated
    
  5. Comprueba el estado de los pods que se están terminando y de los nuevos.

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

Validar la actualización binaria de MySQL

Durante la actualización, puedes verificar el estado del lanzamiento, los nuevos pods y el servicio actual.

  1. Confirma la actualización ejecutando el comando rollout status.

    kubectl rollout status statefulset/dbc1 -n mysql1
    

    El resultado debería ser similar al siguiente:

    partitioned roll out complete: 3 new pods have been updated...
    
  2. Confirma la versión de la imagen inspeccionando el conjunto con estado.

    kubectl get statefulsets -o wide -n mysql1
    

    El resultado debería ser similar al siguiente:

    NAME   READY   AGE   CONTAINERS   IMAGES
    dbc1   3/3     37m   mysql        mysql/mysql-server:8.0.30
    
  3. Comprueba el estado del clúster.

    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\")"'
    

    En cada instancia del clúster, busque los valores de estado y versión en el resultado. El resultado debería ser similar al siguiente:

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

Restaurar la última versión de la aplicación

Cuando reviertes la implementación de una versión binaria actualizada, el proceso de lanzamiento se invierte y se implementa un nuevo conjunto de pods con la versión de imagen anterior.

Para revertir la implementación a la versión anterior que funcionaba, usa el comando rollout undo

kubectl rollout undo statefulset/dbc1 -n mysql1

El resultado debería ser similar al siguiente:

statefulset.apps/dbc1 rolled back

Escalar horizontalmente un clúster de base de datos

Para escalar horizontalmente tu clúster InnoDB de MySQL, añade nodos al pool de nodos del clúster de GKE (solo es necesario si usas el modo Estándar), implementa instancias de MySQL adicionales y, a continuación, añade cada instancia al clúster InnoDB de MySQL.

Añadir nodos a un clúster Standard

Esta operación no es necesaria si utilizas un clúster de Autopilot.

Para añadir nodos a tu clúster estándar, sigue las instrucciones que se indican a continuación para Cloud Shell o la consola de Google Cloud . Para ver los pasos detallados, consulta Cambiar el tamaño de un pool de nodos.

gcloud

En Cloud Shell, cambia el tamaño del grupo de nodos predeterminado a ocho instancias en cada grupo de instancias administrado.

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

Consola

Para añadir nodos a tu clúster estándar, sigue estos pasos:

  1. Abre la gkemulti-west1página Clústeres Google Cloud en la consola.
  2. Selecciona Nodos y haz clic en grupo predeterminado.
  3. Desplázate hacia abajo hasta Grupos de instancias.
  4. En cada grupo de instancias, cambia el valor de Number of nodes de 5 a 8 nodos.

Añadir pods de MySQL al clúster principal

Para implementar pods de MySQL adicionales y escalar tu clúster horizontalmente, sigue estos pasos:

  1. En Cloud Shell, actualiza el número de réplicas de la implementación de MySQL de tres a cinco.

    kubectl scale  -n mysql1 --replicas=5 -f c1-mysql.yaml
    
  2. Verifica el progreso de la implementación.

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

    Para determinar si los pods están listos, usa la marca --watch para monitorizar la implementación. Si utilizas clústeres de Autopilot y ves errores Pod Unschedulable, puede que GKE esté aprovisionando nodos para dar cabida a los pods adicionales.

  3. Configura los ajustes de replicación de grupo de las nuevas instancias de MySQL que quieras añadir al clúster.

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

    La secuencia de comandos envía los comandos a las instancias que se ejecutan en los pods con ordinales del 3 al 4.

  4. Abre MySQL Shell.

    kubectl -n mysql1 \
      exec -it dbc1-0 -- \
          /bin/bash \
            -c 'mysqlsh \
            --uri="root:$MYSQL_ROOT_PASSWORD@dbc1-0.mysql"'
    
  5. Configura las dos nuevas instancias de 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")});
    

    Los comandos comprueban si la instancia está configurada correctamente para usar el clúster InnoDB de MySQL y realizan los cambios de configuración necesarios.

  6. Añade una de las nuevas instancias al clúster principal.

    cluster = dba.getCluster()
    cluster.addInstance('icadmin@dbc1-3.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
    
  7. Añade una segunda instancia nueva al clúster principal.

    cluster.addInstance('icadmin@dbc1-4.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
    
  8. Obtiene el estado de ClusterSet, que también incluye el estado de Cluster.

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

    El resultado debería ser similar al siguiente:

    "domainName": "clusterset",
    "globalPrimaryInstance": "dbc1-0.mysql:3306",
    "metadataServer": "dbc1-0.mysql:3306",
    "primaryCluster": "mycluster",
    "status": "HEALTHY",
    "statusText": "All Clusters available."
    
  9. Sal de MySQL Shell.

    \q