Implementa un clúster de MySQL con estado en GKE


Este documento está dirigido a los administradores de bases de datos, los arquitectos de la nube y los profesionales de operaciones interesados en implementar una topología de MySQL con alta disponibilidad en Google Kubernetes Engine.

Sigue este instructivo para aprender a implementar un MySQL InnoDB Cluster y un MySQL InnoDB ClusterSet, además del middleware MySQL Router en tu clúster de GKE y cómo realizar actualizaciones.

Objetivos

En este instructivo aprenderás realizar las siguientes tareas:

  • Crea e implementa un servicio de Kubernetes con estado.
  • Implementa un MySQL InnoDB Cluster para alta disponibilidad.
  • Implementar middleware de router para el enrutamiento de operaciones de base de datos.
  • Implementar un MySQL InnoDB ClusterSet para la tolerancia a desastres
  • Simular una conmutación por error de clúster de MySQL.
  • Realizar una actualización de la versión de MySQL.

En las siguientes secciones, se describe la arquitectura de la solución que compilarás en este instructivo.

MySQL InnoDB Cluster

En el clúster de GKE regional, mediante un StatefulSet, debes implementar una instancia de base de datos de MySQL con el nombre y la configuración necesarios para crear un MySQL InnoDB Cluster. Para proporcionar tolerancia a errores y alta disponibilidad, implementa tres Pods de instancias de base de datos. Esto garantiza que la mayoría de los Pods en diferentes zonas estén disponibles en cualquier momento para una elección principal exitosa mediante un protocolo de consenso, y hace que tu MySQL InnoDB Cluster tolere las fallas zonales individuales.

Diagrama de arquitectura que muestra la relación entre las aplicaciones, el router MySQL y el clúster de MySQL
Figura 1: Arquitectura de ejemplo de un único MySQL InnoDB Cluster

Una vez implementado, designa un Pod como la instancia principal para entregar operaciones de lectura y escritura. Los otros dos Pods son réplicas secundarias de solo lectura. Si la instancia principal experimenta una falla en la infraestructura, puedes promover uno de estos dos Pods de réplica para que se convierta en la instancia principal.

En un espacio de nombres independiente, implementa tres Pods de MySQL Router para proporcionar enrutamiento de conexión con el fin de mejorar la resiliencia. En lugar de conectarse directamente al servicio de base de datos, las aplicaciones se conectan a los Pods de MySQL Router. Cada Pod del router conoce el estado y el propósito de cada Pod de MySQL InnoDB Cluster, y enruta las operaciones de la aplicación al Pod en buen estado correspondiente. El estado de enrutamiento se almacena en caché en los Pods del router y se actualiza desde los metadatos del clúster almacenados en cada nodo de MySQL InnoDB Cluster. En caso de que se produzca una falla en la instancia, el router ajusta el enrutamiento de la conexión a una instancia activa.

MySQL InnoDB ClusterSet

Puedes crear un MySQL InnoDB ClusterSet a partir de un MySQL InnoDB Cluster inicial. Esto te permite aumentar la tolerancia a desastres si el clúster principal ya no está disponible.

En el diagrama, se muestra cómo los MySQL InnoDB Clusters principal y de réplica se mantienen sincronizados a través de la replicación asíncrona.
Figura 2: Ejemplo de arquitectura de ClusterSet multirregión, que contiene un clúster principal y un clúster de réplica

Si la instancia principal de MySQL InnoDB Cluster ya no está disponible, puedes ascender un clúster de réplica en el ClusterSet a principal. Cuando se usa middleware de MySQL Router, la aplicación no necesita realizar un seguimiento del estado de la instancia de la base de datos principal. El enrutamiento se ajusta para enviar conexiones al nuevo elemento principal después de que se produce la elección. Sin embargo, es tu responsabilidad asegurarte de que las aplicaciones que se conectan a tu middleware de MySQL Router sigan las prácticas recomendadas para resiliencia, de modo que se vuelvan a intentar las conexiones si se produce un error durante la conmutación por error del clúster.

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Cuando finalices las tareas que se describen en este documento, puedes borrar los recursos que creaste para evitar que continúe la facturación. Para obtener más información, consulta Cómo realizar una limpieza.

Antes de comenzar

Configurar tu proyecto

  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

Configura funciones

  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.

Configure su entorno

En este instructivo, usarás Cloud Shell para administrar recursos alojados en Google Cloud. Cloud Shell viene preinstalado con Docker y la CLI de gcloud y kubectl.

Sigue estos pasos para configurar tu entorno con Cloud Shell:

  1. Configurar variables de entorno

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

    Reemplaza los siguientes valores:

  2. Configura las variables de entorno predeterminadas.

     gcloud config set project PROJECT_ID
     gcloud config set compute/region COMPUTE_REGION
    
  3. Clona el repositorio de código.

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  4. Cambia al directorio de trabajo.

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

Crea un clúster de GKE

En esta sección, crearás un clúster de GKE regional. A diferencia de un clúster zonal, el plano de control de un clúster regional se replica en varias zonas, por lo que una interrupción en una sola zona no hace que el plano de control esté 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 \
        --region=$REGION
    
  2. Obtén las credenciales del clúster de GKE.

    gcloud container clusters get-credentials $CLUSTER_NAME \
      --region=$REGION
    
  3. Implementa un servicio en tres zonas.

    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. La implementación definida en prepare-for-ha.yaml garantiza que Autopilot aprovisione nodos en tres zonas de tu clúster mediante la configuración de replicas:3, podAntiAffinity con requiredDuringSchedulingIgnoredDuringExecution y topologyKey: "topology.kubernetes.io/zone".

  4. Verifica el estado del Deployment.

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

    Cuando veas tres Pods en el estado listo, cancela este comando con CTRL+C. El resultado es similar a este:

    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 hayan implementado 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 de la nube. El resultado es similar a este:

    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 de Standard de GKE en la región us-west1.

    gcloud container clusters create $CLUSTER_NAME \
      --region=$REGION \
      --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 \
      --region=$REGION
    

Implemente MySQL StatefulSets

En esta sección, debes implementar un MySQL StatefulSet. Cada StatefulSet consta de tres réplicas de MySQL.

Para implementar el MySQL StatefulSet, 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 las secuencias de comandos de administración y los comandos para la implementación de MySQL InnoDB Cluster y ClusterSet en este instructivo.

  3. Crea la 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 costo. El campo volumeBindingMode se establece en WaitForFirstConsumer, lo que significa que GKE retrasa el aprovisionamiento de un PersistentVolume hasta que se crea el Pod. Esta configuración garantiza que el disco se aprovisione en la misma zona en la que está programado el Pod.

  4. Implementar 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 instructivo, el clúster principal de MySQL se implementa en tres zonas de us-west1. El resultado es similar a este:

    service/mysql created
    statefulset.apps/dbc1 created
    

    En este instructivo, los límites y las solicitudes de recursos se establecen en valores mínimos para ahorrar costos. Cuando planifiques una carga de trabajo de producción, asegúrate de establecer estos valores de forma adecuada para las necesidades de tu organización.

  5. Verifica que el StatefulSet se haya creado correctamente.

    kubectl get statefulset -n mysql1 --watch
    

    El SatefulSet puede tardar unos 10 minutos en estar listo.

  6. Cuando los tres Pods estén listos, sal del comando mediante Ctrl+C. Si ves errores PodUnscheduleable debido a que no hay suficiente CPU o memoria, espera unos minutos para que el plano de control cambie de tamaño y se adapte a la gran carga de trabajo.

    El resultado es similar a este:

    NAME   READY   AGE
    dbc1   1/3     39s
    dbc1   2/3     50s
    dbc1   3/3     73s
    
  7. Para inspeccionar la ubicació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, y 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 del resultado representan el nombre de host, la zona de la nube y el nombre del Pod, respectivamente.

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

    La política podAntiAffinity aplica la restricción de que los Pods no se deben colocar en el mismo nodo del clúster de GKE (kubernetes.io/hostname). En el caso de los Pods de instancias de MySQL, esta política hace que los Pods se implementen de manera uniforme en las tres zonas de la región de Google Cloud. Esta ubicación permite una alta disponibilidad del MySQL InnoDB Cluster porque coloca cada instancia de la base de datos en un dominio con fallas separado.

Prepara el MySQL InnoDB Cluster principal

Para configurar un MySQL InnoDB Cluster, sigue estos pasos:

  1. En la terminal de Cloud Shell, establece las configuraciones de replicación del grupo para las instancias de MySQL que se agregarán a tu 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

    La secuencia de comandos se conectará de forma remota a cada una de las tres instancias de MySQL para establecer y conservar las siguientes variables de entorno:

    • group_replication_ip_allowlist: permite que la instancia dentro del clúster se conecte a cualquier instancia del grupo.
    • binlog_transaction_dependency_tracking='WRITESET': permite las transacciones en paralelo que no entrarán 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, de modo que no necesites crear una shell para cada Pod.

  3. Conéctate a la shell del MySQL 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 entidades permitidas de replicación de grupos de MySQL para conectarte a otras instancias.

    \sql SELECT @@group_replication_ip_allowlist;
    

    El resultado es similar a este:

    +----------------------------------+
    | @@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 es similar a este:

    +-------------+
    | @@server_id |
    +-------------+
    |          21 |
    +-------------+
    
  6. Configura cada instancia para el uso de MySQL InnoDB Cluster y crea una cuenta de administrador en cada una.

    \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 contraseña para que MySQL InnoDB Cluster funcione correctamente. Cada comando produce 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 MySQL InnoDB Cluster.

    dba.checkInstanceConfiguration()
    

    El resultado es similar a este:

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

    De manera opcional, puedes conectarte a cada instancia de MySQL y repetir este comando. Por ejemplo, ejecuta este comando para verificar el estado en 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()"'
    

Crea el MySQL InnoDB Cluster principal

Luego, crea el MySQL InnoDB Cluster mediante el comando createCluster del administrador de MySQL. Comienza con la instancia dbc1-0, que será la instancia principal del clúster y, luego, agrega dos réplicas adicionales al clúster.

Para inicializar el MySQL InnoDB Cluster, sigue estos pasos:

  1. Crea el MySQL InnoDB Cluster.

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

    Cuando ejecutas el comando createCluster, se activan estas operaciones:

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

    Este comando inicializa un MySQL InnoDB Cluster con el host dbc1-0 como la instancia 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. Agrega la instancia secundaria al clúster.

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

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

    El resultado es similar a este:

    ...
    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. De manera opcional, puedes llamar a cluster.status({extended:1}).

    El resultado es similar a este:

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

    De manera opcional, puedes llamar a cluster.status({extended:1}) para obtener detalles adicionales del estado.

Crea una base de datos de muestra

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

  1. Crea una base de datos y cargar 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. Ingresa datos de muestra en la base de datos. Para insertar datos, debes estar conectado 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 es similar a este:

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

Crea un MySQL InnoDB ClusterSet

Puedes crear un MySQL InnoDB ClusterSet para administrar la replicación de tu clúster principal a los clústeres de réplica mediante un canal de replicación de ClusterSet dedicado.

Un MySQL InnoDB ClusterSet proporciona tolerancia a desastres para implementaciones de MySQL InnoDB Cluster, mediante la vinculación de un MySQL InnoDB Cluster principal con una o más réplicas de sí mismas en ubicaciones alternativas, como varias zonas y regiones.

Si cerraste MySQL Shell, crea un shell nuevo mediante la ejecución de este comando en una terminal nueva 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 MySQL InnoDB ClusterSet, sigue estos pasos:

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

    \js
    cluster=dba.getCluster()
    

    El resultado es similar a este:

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

    clusterset=cluster.createClusterSet('clusterset')
    

    El resultado es similar a este:

    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. Comprueba el estado de tu MySQL InnoDB ClusterSet.

    clusterset.status()
    

    El resultado es similar a este:

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

    De manera opcional, puedes llamar a clusterset.status({extended:1}) para obtener detalles adicionales del estado, incluida la información sobre el clúster.

  4. Sal de MySQL Shell.

    \q
    

Implementa un MySQL Router

Puedes implementar un MySQL Router para dirigir el tráfico de las aplicaciones 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 enrutan a la instancia del clúster principal en el ClusterSet principal.
  • Las lecturas se pueden enrutar a cualquier instancia en el clúster principal.

Cuando inicias un MySQL Router, se inicia con la implementación del MySQL InnoDB ClusterSet. Las instancias de MySQL Router conectadas con el MySQL InnoDB ClusterSet conocen todos los cambios controlados o las conmutaciones por error de emergencia y dirigen el tráfico al clúster principal nuevo.

Para implementar un MySQL Router, sigue estos pasos:

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

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

    El resultado es similar a este:

    configmap/mysql-router-config created
    service/mysql-router created
    deployment.apps/mysql-router created
    
  2. Comprueba la preparación de la implementación del MySQL Router.

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

    Cuando los tres Pods están listos, el resultado es 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 existente.

    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, luego, inicia una shell conectada a la instancia de MySQL dbc1-0.

  4. Verifica la configuración del router.

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

    El resultado es similar a este:

    {
      "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 ubicación de los Pods del MySQL Router.

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

    La secuencia de comandos muestra el nodo y la ubicación de la zona de Cloud de todos los Pods en el espacio de nombres mysql1, en el que el resultado es similar al 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, que no estén ubicadas en el mismo nodo que un Pod de MySQL o en el mismo nodo que otro Pod de MySQL Router.

Administra las actualizaciones de GKE y MySQL InnoDB Cluster

Las actualizaciones para MySQL y Kubernetes se lanzan con regularidad. Sigue las prácticas recomendadas operativas para actualizar el entorno de software con regularidad. De forma predeterminada, GKE administra las actualizaciones del clúster y del grupo de nodos por ti. Kubernetes y GKE también proporcionan funciones adicionales para facilitar las actualizaciones de software de MySQL.

Planifica las actualizaciones de GKE

Puedes tomar medidas proactivas y establecer parámetros de configuración para mitigar el riesgo y facilitar una actualización del clúster sin problemas cuando ejecutas servicios con estado, incluidos los siguientes:

  • Clústeres de Standard: Sigue las prácticas recomendadas de GKE para actualizar clústeres. Elige una estrategia de actualización adecuada para garantizar que las actualizaciones se realicen durante el período de mantenimiento:

    • Elige actualizaciones de aumento si la optimización de costos es importante y si tus cargas de trabajo pueden tolerar un cierre ordenado en menos de 60 minutos.
    • Elige actualizaciones azul-verde si las cargas de trabajo son menos tolerantes a las interrupciones. Además, un aumento temporal del costo debido al mayor uso de recursos es aceptable.

    Para obtener más información, consulta Actualiza un clúster que ejecuta una carga de trabajo con estado. Los clústeres de Autopilot se actualizan de forma automática, según el canal de versiones que seleccionaste.

  • Usa los períodos de mantenimiento para garantizar que las actualizaciones se realicen cuando lo desees. Antes del período de mantenimiento, asegúrate de que las copias de seguridad de la base de datos se ejecuten correctamente.

  • Antes de permitir el tráfico a los nodos de MySQL actualizados, usa sondeos de preparación y funcionamiento a fin de asegurarte de que estén listos para el tráfico.

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

Configura una política de presupuesto de interrupción de Pods (PDB)

Cuando un MySQL InnoDB Cluster se ejecuta en GKE, debe haber una cantidad suficiente de instancias en ejecución en cualquier momento para cumplir con el requisito de quórum.

En este instructivo, dado un clúster de MySQL de tres instancias, dos instancias deben estar disponibles para formar un quórum. Una política PodDisruptionBudget te permite limitar la cantidad de Pods que se pueden finalizar en un momento determinado. Esto es útil para las operaciones de estado estable de tus servicios con estado y para las actualizaciones de clústeres.

Para asegurarte de que una cantidad limitada de Pods se interrumpa de forma simultánea, debes configurar el PDB de la carga de trabajo como maxUnavailable: 1. Esto garantiza que en cualquier momento de la operación del servicio no se ejecute más de un Pod.

En el manifiesto de la política PodDisruptionBudget siguiente, se establece la cantidad máxima de Pods no disponibles para uno para tu aplicación de 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 mediante kubectl.

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

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

    En la sección status del resultado, consulta los recuentos de Pods currentHealthy y desiredHealthy. El resultado es similar a este:

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

Planifica las actualizaciones de objeto binario de MySQL

Kubernetes y GKE proporcionan funciones para facilitar las actualizaciones de objeto binario de MySQL. Sin embargo, debes realizar algunas operaciones para prepararte para las mejoras.

Ten en cuenta las siguientes consideraciones antes de comenzar el proceso de actualización:

  • 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.
  • Para algunas versiones de objeto binario, no puedes cambiar a una versión inferior de la versión una vez que se realiza una actualización. Tómate el tiempo para comprender las implicaciones de una actualización.
  • Las fuentes de replicación pueden replicar a una versión más reciente. Sin embargo, por lo general, 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 que no se encuentra en el volumen persistente se perderá cuando se vuelva a implementar el Pod.
  • Para las actualizaciones de objeto binario de MySQL, usa el mismo PDB, la estrategia de actualización de grupos de nodos y los sondeos como se describió antes.

En un entorno de producción, debes seguir estas recomendaciones:

  • Crear una imagen de contenedor con la versión nueva de MySQL
  • Almacena las instrucciones de compilación de imágenes en un repositorio de control de origen.
  • Usa una canalización de compilación y prueba de imágenes automatizada, como Cloud Build, y almacena el objeto binario de la imagen en un registro de imágenes, como Artifact Registry.

Para que este instructivo sea simple, no compilarás ni conservarás una imagen de contenedor; en su lugar, usarás las imágenes públicas de MySQL.

Implementa el objeto binario actualizado de MySQL

Para realizar la actualización de objeto binario de MySQL, emite un comando declarativo que modifique la versión de la imagen del recurso StatefulSet. GKE realiza los pasos necesarios para detener el Pod actual, implementar un Pod nuevo con el objeto binario actualizado y conectar el disco persistente al Pod nuevo.

  1. Verifica que se haya creado el PDB.

    kubectl get poddisruptionbudgets -n mysql1
    
  2. Obtén la lista de conjuntos con 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 es similar a este:

    statefulset.apps/mysql image updated
    
  5. Verifica el estado de los Pods que se cerrarán y de los nuevos.

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

Valida la actualización de objeto binario de MySQL

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

  1. Confirma la actualización, ejecuta el comando rollout status.

    kubectl rollout status statefulset/dbc1 -n mysql1
    

    El resultado es similar a este:

    partitioned roll out complete: 3 new pods have been updated...
    
  2. Para confirmar la versión de la imagen, inspecciona el conjunto con estado.

    kubectl get statefulsets -o wide -n mysql1
    

    El resultado es similar a este:

    NAME   READY   AGE   CONTAINERS   IMAGES
    dbc1   3/3     37m   mysql        mysql/mysql-server:8.0.30
    
  3. Verifica 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\")"'
    

    Para cada instancia de clústeres, busca los valores de estado y versión en el resultado. El resultado es similar a este:

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

Revierte el último lanzamiento de la implementación de la app

Cuando reviertes la implementación de una versión binaria actualizada, el proceso de lanzamiento se revierte y se implementa un conjunto nuevo de Pods con la versión de la 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 es similar a este:

statefulset.apps/dbc1 rolled back

Escala tu clúster de base de datos de forma horizontal

Para escalar MySQL InnoDB Cluster de forma horizontal, agrega nodos adicionales al grupo de nodos del clúster de GKE (solo se requiere si usas Standard), implementa instancias de MySQL adicionales y, luego, agrega cada instancia al MySQL existente Clúster de InnoDB.

Agrega nodos a tu clúster de Standard

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

A fin de agregar nodos a tu clúster estándar, sigue las instrucciones que se encuentran a continuación para Cloud Shell o la consola de Google Cloud. Para obtener pasos detallados, consulta Modifica el tamaño de un grupo 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

Console

Para agregar nodos a tu clúster de Standard, haz lo siguiente:

  1. Abre la página de gkemulti-west1 Cluster en la consola de Google Cloud.
  2. Selecciona Nodos y haz clic en grupo predeterminado.
  3. Desplázate hacia abajo hasta Grupos de instancias.
  4. Para cada grupo de instancias, cambia el tamaño del valor Number of nodes de 5 a 8 nodos.

Agrega los Pods de MySQL al clúster principal

Para implementar Pods de MySQL adicionales a fin de escalar tu clúster de forma horizontal, sigue estos pasos:

  1. En Cloud Shell, actualiza la cantidad de réplicas en la implementación de MySQL de tres a cinco réplicas.

    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
    

    A fin de determinar si los Pods están listos, usa la marca --watch para ver la implementación. Si usas clústeres de Autopilot y ves errores Pod Unschedulable, esto podría indicar que GKE aprovisiona nodos para alojar los Pods adicionales.

  3. Establece la configuración de la replicación de grupos para las instancias de MySQL nuevas que se agregarán 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 instancias nuevas 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 se configuró de forma correcta para el uso del MySQL InnoDB Cluster y si realizaron los cambios de configuración necesarios.

  6. Agrega una de las instancias nuevas al clúster principal.

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

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

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

    El resultado es similar a este:

    "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
    

Realiza una limpieza

Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos usados en este instructivo, borra el proyecto que contiene los recursos o conserva el proyecto y borra los recursos individuales.

Borra el proyecto

La manera más fácil de evitar la facturación es borrar el proyecto que creaste para el instructivo.

Delete a Google Cloud project:

gcloud projects delete PROJECT_ID

¿Qué sigue?