Implantar um cluster MySQL com estado no GKE


Este documento é destinado a administradores de bancos de dados, arquitetos de nuvem e profissionais de operações interessados em implantar uma topologia de MySQL altamente disponível no Google Kubernetes Engine.

Siga este tutorial para saber como implantar um cluster do InnoDB do MySQL e um clusterSet do InnoDB do MySQL, além do MySQL Router middleware no seu cluster do GKE e como realizar upgrades.

Objetivos

Neste tutorial, você aprenderá a:

  • Crie e implante um serviço do Kubernetes com estado.
  • Implante um cluster do MySQL InnoDB para alta disponibilidade.
  • Implante o middleware do roteador para o roteamento de operações do banco de dados.
  • Implante um ClusterSet MySQL MySQL para tolerância a desastres.
  • Simular um failover de cluster do MySQL.
  • Execute um upgrade de versão do MySQL.

As seções a seguir descrevem a arquitetura da solução que você vai criar neste tutorial.

Cluster do MySQL InnoDBB

No cluster regional do GKE, usando um StatefulSet, você implanta uma instância de banco de dados MySQL com a nomenclatura e a configuração necessárias para criar um cluster InnoDB do MySQL. Para fornecer tolerância a falhas e alta disponibilidade, implante três pods de instâncias de banco de dados. Isso garante que a maioria dos pods em diferentes zonas esteja disponível a qualquer momento para uma eleição primária bem-sucedida usando um protocolo de consenso, além de tornar o cluster do MySQL InnoDB tolerante a falhas de zona única.

Diagrama da arquitetura que mostra a relação entre aplicativos, roteador MySQL e cluster MySQL
Figura 1: exemplo de arquitetura de um único cluster InnoDB do MySQL

Após a implantação, você designa um pod como a instância principal para atender às operações de leitura e gravação. Os outros dois pods são réplicas somente leitura secundárias. Se a instância principal apresentar uma falha de infraestrutura, será possível promover um desses dois pods de réplica para se tornar o principal.

Em um namespace separado, você implanta três pods do MySQL Router para fornecer roteamento de conexão e melhora a resiliência. Em vez de se conectar diretamente ao serviço de banco de dados, os aplicativos se conectam aos pods do MySQL Router. Cada pod do roteador está ciente do status e da finalidade de cada pod de cluster do MySQL InnoDB e encaminha as operações de aplicativos para o respectivo pod íntegro. O estado de roteamento é armazenado em cache nos pods do roteador e atualizado a partir dos metadados de cluster armazenados em cada node do cluster do MySQL InnoDB. Em caso de falha de uma instância, o roteador ajusta o roteamento de conexão a uma instância ativa.

ClusterSet MySQL do MySQL

É possível criar um conjunto de cluster do MySQL InnoDB de um cluster inicial do MySQL InnoDB. Isso permite aumentar a tolerância a desastres se o cluster principal não estiver mais disponível.

O diagrama mostra como os clusters primário e de réplica do MySQL InnoDB são mantidos em sincronia por meio da replicação assíncrona.
Figura 2: exemplo de arquitetura ClusterSet multirregional que contém um cluster principal e um de réplica

Se a instância principal do cluster do MySQL InnoDB não estiver mais disponível, será possível promover um cluster de réplica no ClusterSet para primário. Ao usar o middleware MySQL Router, o aplicativo não precisa rastrear a integridade da instância do banco de dados principal. O roteamento é ajustado para enviar conexões à nova principal após a eleição. No entanto, é sua responsabilidade garantir que os aplicativos que se conectam ao middleware do roteador MySQL sigam as práticas recomendadas para resiliência, de modo que novas conexões serão realizadas se um erro ocorrer durante o failover do cluster.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Ao concluir as tarefas descritas neste documento, é possível evitar o faturamento contínuo excluindo os recursos criados. Saiba mais em Limpeza.

Antes de começar

Configurar o projeto

  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. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  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. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  7. Enable the GKE API.

    Enable the API

Configurar papéis

  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_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 seu ambiente

Neste tutorial, você usará o Cloud Shell para gerenciar recursos hospedados no Google Cloud. O Cloud Shell vem pré-instalado com o Docker, bem como kubectl e a CLI gcloud.

Siga estes passos para configurar o ambiente usando o Cloud Shell:

  1. Defina variáveis de ambiente.

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

    Substitua os seguintes valores:

  2. Defina as variáveis de ambiente padrão.

     gcloud config set project PROJECT_ID
     gcloud config set compute/region COMPUTE_REGION
    
  3. Clone o repositório do código.

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  4. Mude para o diretório de trabalho.

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

Criar um cluster do GKE

Nesta seção, você cria um cluster regional do GKE. Ao contrário de um cluster zonal, o plano de controle de um cluster regional é replicado em várias zonas. Portanto, uma falha temporária em uma única zona não torna o plano de controle indisponível.

Para criar um cluster do GKE, siga estas etapas:

Piloto automático

  1. No Cloud Shell, crie um cluster do Autopilot do GKE na região us-west1.

    gcloud container clusters create-auto $CLUSTER_NAME \
        --region=$REGION
    
  2. Consiga as credenciais do cluster do GKE:.

    gcloud container clusters get-credentials $CLUSTER_NAME \
      --region=$REGION
    
  3. Implantar um Serviço em três 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
    

    Por padrão, o Autopilot provisiona recursos em duas zonas. A implantação definida em prepare-for-ha.yaml garante que o Autopilot provisione nós em três zonas do seu cluster, definindo replicas:3, podAntiAffinity com requiredDuringSchedulingIgnoredDuringExecution e topologyKey: "topology.kubernetes.io/zone"

  4. Verifique o status da implantação.

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

    Quando você vir três pods no estado pronto, cancele este comando com CTRL+C. O resultado será assim:

    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. Execute esse script para validar se os pods foram implantados em três zonas.

    bash ../scripts/inspect_pod_node.sh default
    

    Cada linha da saída corresponde a um pod, e a segunda coluna indica a zona da nuvem. O resultado será assim:

    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
    

Padrão

  1. No Cloud Shell, crie um cluster do GKE Standard na região us-west1.

    gcloud container clusters create $CLUSTER_NAME \
      --region=$REGION \
      --machine-type="e2-standard-2" \
      --disk-type="pd-standard" \
      --num-nodes="5"
    
  2. Consiga as credenciais do cluster do GKE:.

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

Implantar os StatefulSets do MySQL

Nesta seção, você implantará um StatefulSet do MySQL. Cada StatefulSet consiste em três réplicas do MySQL.

Para implantar o StatefulSet do MySQL, siga estas etapas:

  1. Crie um namespace para o StatefulSet.

    kubectl create namespace mysql1
    
  2. Crie o secret do 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
    

    Neste tutorial, a senha é implantada a cada pod e usada por scripts e comandos de gerenciamento para o cluster do MySQL InnoDB e a implantação do ClusterSet.

  3. Crie o 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
    

    Essa classe de armazenamento usa o tipo de disco permanente pd-balanced que equilibra desempenho e custo. O campo volumeBindingMode está definido como WaitForFirstConsumer, o que significa que o GKE atrasa o provisionamento de um PersistentVolume até que o pod seja criado. Essa configuração garante que o disco seja provisionado na mesma zona em que o pod está programado.

  4. Implante o StatefulSet dos pods de instância do 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
    

    Esse comando implanta o StatefulSet composto por três réplicas. Neste tutorial, o cluster principal do MySQL é implantado em três zonas em us-west1. O resultado será assim:

    service/mysql created
    statefulset.apps/dbc1 created
    

    Neste tutorial, os limites de recursos e as solicitações são definidos como valores mínimos para economizar custos. Ao planejar uma carga de trabalho de produção, defina esses valores adequadamente para as necessidades da sua organização.

  5. Verifique se o StatefulSet foi criado.

    kubectl get statefulset -n mysql1 --watch
    

    Pode levar cerca de 10 minutos para o SatefulSet ficar pronto.

  6. Quando os três pods estiverem em estado pronto, saia do comando usando Ctrl+C. Se você vir erros PodUnscheduleable devido à CPU ou memória insuficientes, aguarde alguns minutos para que o plano de controle seja redimensionado a fim de acomodar a carga de trabalho grande.

    O resultado será assim:

    NAME   READY   AGE
    dbc1   1/3     39s
    dbc1   2/3     50s
    dbc1   3/3     73s
    
  7. Para inspecionar o posicionamento dos pods nos nós do cluster do GKE, execute este script:

    bash ../scripts/inspect_pod_node.sh mysql1 mysql
    

    A saída mostra o nome do pod, o nome do nó do GKE e a zona em que o nó é provisionado e é semelhante ao seguinte:

    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
    

    As colunas na saída representam o nome do host, a zona da nuvem e o nome do pod, respectivamente.

    A política topologySpreadConstraints na especificação do StatefulSet (c1-mysql.yaml) direciona o programador para colocar os pods de maneira uniforme no domínio de falha (topology.kubernetes.io/zone).

    A política podAntiAffinity aplica a restrição que exige que os pods não sejam colocados no mesmo nó de cluster do GKE (kubernetes.io/hostname). Para os pods de instância do MySQL, essa política resulta na implantação uniforme dos pods nas três zonas da região do Google Cloud. Com esse posicionamento, você terá alta disponibilidade do cluster do MySQL InnoDB colocando cada instância de banco de dados em um domínio de falha separado.

Preparar o cluster principal do MySQL InnoDB

Para configurar um cluster do InnoDB do MySQL, siga estas etapas:

  1. No terminal do Cloud Shell, defina as configurações de replicação de grupo para que as instâncias do MySQL sejam adicionadas ao seu cluster.

    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

    O script se conecta remotamente a cada uma das três instâncias do MySQL para definir e manter as seguintes variáveis de ambiente:

    • group_replication_ip_allowlist: permite que a instância do cluster se conecte a qualquer instância do grupo.
    • binlog_transaction_dependency_tracking='WRITESET': permite transações paralelas que não entrem em conflito.

    Nas versões do MySQL anteriores à 8.0.22, use group_replication_ip_whitelist em vez de group_replication_ip_allowlist.

  2. Abra um segundo terminal para que não seja necessário criar um shell para cada pod.

  3. Conecte-se ao MySQL Shell no 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. Verifique a lista de permissões de replicação do grupo do MySQL para se conectar a outras instâncias.

    \sql SELECT @@group_replication_ip_allowlist;
    

    O resultado será assim:

    +----------------------------------+
    | @@group_replication_ip_allowlist |
    +----------------------------------+
    | mysql.mysql1.svc.cluster.local   |
    +----------------------------------+
    
  5. Verifique se o server-id é exclusivo em cada uma das instâncias.

    \sql SELECT @@server_id;
    

    O resultado será assim:

    +-------------+
    | @@server_id |
    +-------------+
    |          21 |
    +-------------+
    
  6. Configurar cada instância para uso do cluster do InnoDB do MySQL e criar uma conta de administrador em cada instância.

    \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 as instâncias precisam ter o mesmo nome de usuário e senha para que o cluster do InnoDB do MySQL funcione corretamente. Cada comando produz um resultado semelhante a este:

    ...
    
    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. Verifique se a instância está pronta para ser usada em um cluster do MySQL InnoDB.

    dba.checkInstanceConfiguration()
    

    O resultado será assim:

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

    Também é possível se conectar a cada instância do MySQL e repetir esse comando. Por exemplo, execute este comando para verificar o status da instância 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()"'
    

Criar o cluster principal do MySQL InnoDB

Em seguida, crie o cluster do MySQL InnoDB usando o comando createCluster do MySQL Admin. Comece com a instância dbc1-0, que será a instância principal do cluster e, em seguida, adicione duas réplicas adicionais ao cluster.

Para inicializar o cluster do MySQL InnoDB, siga estas etapas:

  1. Criar o cluster do MySQL InnoDB

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

    A execução do comando createCluster aciona estas operações:

    • Implantar o esquema de metadados.
    • Verifique se a configuração está correta para replicação de grupos.
    • Registre-o como a instância de origem do novo cluster.
    • Crie as contas internas necessárias, como a conta de usuário de replicação.
    • Inicie a replicação de grupo.

    Esse comando inicializa um cluster do MySQL InnoDB com o host dbc1-0 como principal. A referência do cluster é armazenada na variável de cluster.

    A resposta será semelhante a:

    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. Adicione a segunda instância ao cluster.

    cluster.addInstance('icadmin@dbc1-1.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
    
  3. Adicionar a instância restante ao cluster.

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

    O resultado será assim:

    ...
    The instance 'dbc1-2.mysql:3306' was successfully added to the cluster.
    
  4. Verifique o status do cluster.

    cluster.status()
    

    Esse comando mostra o status do cluster. A topologia consiste em três hosts, uma instância primária e duas secundárias. Se quiser, chame cluster.status({extended:1}).

    O resultado será assim:

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

    Como alternativa, é possível chamar cluster.status({extended:1}) para ver mais detalhes do status.

Criar um banco de dados de exemplo

Para criar um banco de dados de amostra, siga estas etapas:

  1. Crie um banco de dados e carregar dados nele.

    \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. Insira dados de amostra no banco de dados. Para inserir dados, é necessário estar conectado à instância principal do cluster.

    INSERT INTO loan (firstname, lastname, status) VALUES ( 'Fred','Flintstone','pending');
    INSERT INTO loan (firstname, lastname, status) VALUES ( 'Betty','Rubble','approved');
    
  3. Verifique se a tabela contém as três linhas inseridas na etapa anterior.

    SELECT * FROM loan;
    

    O resultado será assim:

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

Criar um ClusterSet do MySQL InnoDB

É possível criar um ClusterSet MySQL InnoDB para gerenciar a replicação do cluster principal para os clusters de réplica, usando um canal de replicação dedicado do ClusterSet.

Um MySQLInnoDB ClusterSet fornece tolerância a desastres para implantações de clusters do MySQL InnoDB vinculando um cluster MySQL primário InnoDB a uma ou mais réplicas próprias em locais alternativos, como várias zonas e várias regiões.

Se você tiver fechado o MySQL Shell, crie um novo shell executando este comando em um novo terminal do 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 criar um ClusterInnoDB MySQLSet, siga estas etapas:

  1. No terminal do MySQL Shell, consiga um objeto de cluster.

    \js
    cluster=dba.getCluster()
    

    O resultado será assim:

    <Cluster:mycluster>
    
  2. Inicialize um cluster do MySQL InnoDB com o cluster do InnoDB do MySQL armazenado no objeto do cluster como principal.

    clusterset=cluster.createClusterSet('clusterset')
    

    O resultado será assim:

    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. Verifique o status do ClusterSet do MySQL InnoDB.

    clusterset.status()
    

    O resultado será assim:

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

    Opcionalmente, é possível chamar clusterset.status({extended:1}) para receber mais detalhes sobre o status, incluindo informações sobre o cluster.

  4. Saia do MySQL Shell.

    \q
    

Implantar um roteador MySQL

É possível implantar um roteador MySQL para direcionar o tráfego de aplicativos do cliente para os clusters adequados. O roteamento é baseado na porta de conexão do aplicativo que emite uma operação de banco de dados:

  • As gravações são roteadas para a instância do cluster principal no ClusterSet principal.
  • As leituras podem ser roteadas para qualquer instância no cluster principal.

Quando você inicia um roteador MySQL, ele é inicializado na implantação do ClusterSet do MySQL InnoDB. As instâncias do roteador do MySQL conectadas com o MySQLInnoDB ClusterSet estão cientes das mudanças controladas ou de failovers de emergência e do tráfego direto para o novo cluster principal.

Para implantar um roteador MySQL, siga estas etapas:

  1. No terminal do Cloud Shell, implante o roteador do MySQL.

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

    O resultado será assim:

    configmap/mysql-router-config created
    service/mysql-router created
    deployment.apps/mysql-router created
    
  2. Verifique se a implantação do MySQL Roteador está pronta.

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

    Quando os três pods estiverem prontos, a saída vai ser semelhante a esta:

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

    Se você vir um erro PodUnschedulable no console, aguarde um ou dois minutos enquanto o GKE provisiona mais nodes. Atualize e você verá 3/3 OK.

  3. Inicie o MySQL Shell em qualquer membro do cluster atual.

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

    Esse comando se conecta ao pod dbc1-0 e inicia um shell conectado à instância do MySQL de dbc1-0.

  4. Verifique a configuração do roteador.

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

    O resultado será assim:

    {
      "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. Saia do MySQL Shell.

    \q
    
  6. Execute esse script para inspecionar o posicionamento dos pods do MySQL Router.

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

    O script mostra o nó e a colocação da zona do Cloud de todos os pods no namespace mysql1, em que a saída é semelhante a esta:

    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
    

    É possível observar que os pods do MySQL Router estão distribuídos igualmente entre as zonas. Ou seja, não é colocado no mesmo node que um pod do MySQL ou no mesmo nó de outro pod do roteador do MySQL.

Gerenciar upgrades de clusters do GKE e do MySQL InnoDB

As atualizações para MySQL e Kubernetes são lançadas regularmente. Siga as práticas recomendadas operacionais para atualizar o ambiente de software regularmente. Por padrão, o GKE gerencia os upgrades de clusters e pools de nodes para você. O Kubernetes e o GKE também oferecem recursos adicionais para facilitar os upgrades de software do MySQL.

Plano para upgrades do GKE

É possível tomar medidas proativas e definir configurações para reduzir os riscos e facilitar um upgrade mais tranquilo do cluster quando estiver executando serviços com estado, incluindo:

  • Clusters padrão: siga as práticas recomendadas do GKE para fazer upgrade de clusters. Escolha uma estratégia de upgrade apropriada para garantir que os upgrades aconteçam durante o período da janela de manutenção:

    • Escolha upgrades súbitos se a otimização de custos for importante e se as cargas de trabalho estiverem tolerar um encerramento otimizado em menos de 60 minutos.
    • Escolha upgrades azul-verde se as cargas de trabalho forem menos tolerantes a interrupções e um aumento temporário do custo devido ao maior uso de recursos for aceitável.

    Para saber mais, consulte Fazer upgrade de um cluster que executa uma carga de trabalho com estado. Os clusters do Autopilot são atualizados automaticamente, com base no canal de lançamento que você selecionou.

  • Use as janelas de manutenção para garantir que os upgrades aconteçam quando você quiser. Antes da janela de manutenção, verifique se os backups do banco de dados foram concluídos.

  • Antes de permitir o tráfego para os nodes do MySQL atualizados, use as Sondagens de prontidão e atividades para verificar se estão prontas para tráfego.

  • Crie sondagens que avaliem se a replicação está sincronizada antes de aceitar o tráfego. Isso pode ser feito com scripts personalizados, dependendo da complexidade e da escala do seu banco de dados.

Definir uma política de orçamento de interrupção de pod (PDB)

Quando um cluster do MySQL InnoDB está em execução no GKE, é preciso ter um número suficiente de instâncias em execução a qualquer momento para atender ao requisito de quórum.

Neste tutorial, considerando um cluster MySQL de três instâncias, duas instâncias precisam estar disponíveis para formar um quórum. Com uma política PodDisruptionBudget, é possível limitar o número de pods que podem ser encerrados a qualquer momento. Isso é útil para operações de estado estável dos serviços com estado e para upgrades de cluster.

Para garantir que um número limitado de pods seja interrompido simultaneamente, defina o PDB para sua carga de trabalho como maxUnavailable: 1. Isso garante que, em qualquer ponto da operação do serviço, apenas um pod não esteja em execução.

O manifesto de política PodDisruptionBudget a seguir define o máximo de pods indisponíveis para um do seu aplicativo MySQL.

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

Para aplicar a política de PDB ao cluster, siga estas etapas:

  1. Aplique a política de PDB usando kubectl.

    kubectl apply -n mysql1 -f mysql-pdb-maxunavailable.yaml
    
  2. Veja o status do PDB.

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

    Na seção status da saída, veja as contagens de pods currentHealthy e desiredHealthy. O resultado será assim:

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

Planejar upgrades binários do MySQL

O Kubernetes e o GKE oferecem recursos para facilitar os upgrades para o binário do MySQL. No entanto, é necessário realizar algumas operações para se preparar para os upgrades.

Lembre-se das seguintes considerações antes de iniciar o processo de upgrade:

  • Os upgrades devem ser realizados primeiro em um ambiente de teste. Para sistemas de produção, é preciso realizar mais testes em um ambiente de pré-produção.
  • Em algumas versões binárias, não é possível fazer downgrade da versão depois da atualização. Dedique um tempo para entender as implicações de um upgrade.
  • As origens de replicação podem ser replicadas para uma versão mais recente. No entanto, geralmente não é possível copiar de uma versão mais recente para uma mais antiga.
  • Faça um backup completo do banco de dados antes de implantar a versão atualizada.
  • Considere a natureza efêmera dos pods do Kubernetes. Todo estado de configuração armazenado pelo pod que não está no volume permanente será perdido quando o pod for reimplantado.
  • Para upgrades binários do MySQL, use o mesmo PDB, estratégia de atualização de pool de nós e sondagens, conforme descrito anteriormente.

Em um ambiente de produção, siga estas práticas recomendadas:

  • Crie uma imagem de contêiner com a nova versão do MySQL.
  • Mantenha as instruções de criação de imagem em um repositório de controle de origem.
  • Use um pipeline automatizado de build e teste de imagens, como o Cloud Build, e armazene o binário da imagem em um registro de imagens, como o Artifact Registry.

Para manter este tutorial simples, você não criará nem manterá uma imagem de contêiner. Em vez disso, use as imagens públicas do MySQL.

Implante o binário MySQL atualizado

Para realizar o upgrade binário do MySQL, você emite um comando declarativo que modifica a versão da imagem do recurso StatefulSet. O GKE executa as etapas necessárias para interromper o pod atual, implantar um novo pod com o binário atualizado e anexar o disco permanente ao novo pod.

  1. Verifique se o PDB foi criado.

    kubectl get poddisruptionbudgets -n mysql1
    
  2. Veja a lista de conjuntos com estado.

    kubectl get statefulsets -n mysql1
    
  3. Veja a lista de pods em execução usando o rótulo app.

    kubectl get pods --selector=app=mysql -n mysql1
    
  4. Atualizar a imagem do MySQL no conjunto com estado.

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

    O resultado será assim:

    statefulset.apps/mysql image updated
    
  5. Verifique o status dos pods de encerramento e dos novos pods.

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

Validar o upgrade binário do MySQL

Durante o upgrade, é possível verificar o status do lançamento, os novos pods e o serviço atual.

  1. Valide o upgrade executando o comando rollout status.

    kubectl rollout status statefulset/dbc1 -n mysql1
    

    O resultado será assim:

    partitioned roll out complete: 3 new pods have been updated...
    
  2. Inspecione o conjunto com estado para confirmar a versão da imagem.

    kubectl get statefulsets -o wide -n mysql1
    

    O resultado será assim:

    NAME   READY   AGE   CONTAINERS   IMAGES
    dbc1   3/3     37m   mysql        mysql/mysql-server:8.0.30
    
  3. Verifique o status do cluster.

    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 instância do cluster, procure os valores de status e versão na saída. O resultado será assim:

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

Reverter o último lançamento de implantação do app

Quando você reverte a implantação de uma versão binária atualizada, o processo de lançamento é revertido e um novo conjunto de pods é implantado com a versão de imagem anterior.

Para reverter a implantação para a versão de trabalho anterior, use o comando rollout undo:

kubectl rollout undo statefulset/dbc1 -n mysql1

O resultado será assim:

statefulset.apps/dbc1 rolled back

Escalonar o cluster do banco de dados horizontalmente

Para escalonar o cluster do InnoDB do MySQL horizontalmente, você adiciona outros nós ao pool de nós do cluster do GKE (obrigatório apenas se você estiver usando o Standard), implanta outras instâncias do MySQL e, em seguida, adiciona cada instância ao MySQL existente Cluster do InnoDB.

Adicionar nodes ao cluster padrão

Essa operação não é necessária se você estiver usando um cluster do Autopilot.

Para adicionar nós ao cluster padrão, siga as instruções abaixo para o Cloud Shell ou o Console do Google Cloud. Para ver as etapas detalhadas, consulte Redimensionar um pool de nodes.

gcloud

No Cloud Shell, redimensione o pool de nodes padrão para oito instâncias em cada grupo gerenciado.

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

Console

Para adicionar nós ao cluster padrão:

  1. Abra a página gkemulti-west1 cluster no Console do Google Cloud.
  2. Selecione Nodes e clique em pool padrão.
  3. Role para baixo até Grupos de instâncias.
  4. Para cada grupo de instâncias, redimensione o valor Number of nodes de 5 para 8 nodes.

Adicionar pods do MySQL ao cluster principal

Para implantar outros pods do MySQL de modo a escalonar o cluster horizontalmente, siga estas etapas:

  1. No Cloud Shell, atualize o número de réplicas na implantação do MySQL de três réplicas para cinco réplicas.

    kubectl scale  -n mysql1 --replicas=5 -f c1-mysql.yaml
    
  2. Verifique o andamento da implantação.

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

    Para determinar se os pods estão prontos, use a sinalização --watch para observar a implantação. Se você estiver usando clusters do Autopilot e vir erros Pod Unschedulable, isso poderá indicar que o GKE está provisionando nós para acomodar os pods adicionais.

  3. Defina as configurações de replicação de grupo para as novas instâncias do MySQL a serem adicionadas ao cluster.

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

    O script envia os comandos para as instâncias em execução nos pods com ordinais 3 a 4.

  4. Abra o MySQL Shell.

    kubectl -n mysql1 \
      exec -it dbc1-0 -- \
          /bin/bash \
            -c 'mysqlsh \
            --uri="root:$MYSQL_ROOT_PASSWORD@dbc1-0.mysql"'
    
  5. Configurar as duas novas instâncias do 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")});
    

    Os comandos verificam se a instância está configurada corretamente para o uso do cluster do MySQL InnoDB e realizam as alterações de configuração necessárias.

  6. Adicione uma das novas instâncias ao cluster principal.

    cluster = dba.getCluster()
    cluster.addInstance('icadmin@dbc1-3.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
    
  7. Adicione uma segunda instância nova ao cluster principal.

    cluster.addInstance('icadmin@dbc1-4.mysql', {password: os.getenv("MYSQL_ROOT_PASSWORD"), recoveryMethod: 'clone'});
    
  8. Acessar o status do ClusterSet, que também inclui o status do cluster.

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

    O resultado será assim:

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

    \q
    

Limpar

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados no tutorial, exclua o projeto que os contém ou mantenha o projeto e exclua os recursos individuais.

Excluir o projeto

A maneira mais fácil de evitar o faturamento é excluir o projeto criado para o tutorial.

Delete a Google Cloud project:

gcloud projects delete PROJECT_ID

A seguir