Implemente um cluster MySQL com estado no GKE

Este documento destina-se a administradores de bases de dados, arquitetos da nuvem e profissionais de operações interessados na implementação de uma topologia do MySQL de elevada disponibilidade no Google Kubernetes Engine.

Siga este tutorial para saber como implementar um MySQL InnoDB Cluster e um MySQL InnoDB ClusterSet, além do middleware MySQL Router no seu cluster do GKE e como fazer atualizações.

Crie um cluster do GKE

Nesta secção, cria um cluster do GKE regional. Ao contrário de um cluster zonal, o painel de controlo de um cluster regional é replicado em várias zonas, pelo que uma indisponibilidade numa única zona não torna o painel de controlo indisponível.

Para criar um cluster do GKE, siga estes passos:

Piloto automático

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

    gcloud container clusters create-auto $CLUSTER_NAME \
        --location=$CONTROL_PLANE_LOCATION
    
  2. Obtenha as credenciais do cluster do GKE.

    gcloud container clusters get-credentials $CLUSTER_NAME \
      --location=$CONTROL_PLANE_LOCATION
    
  3. Implemente um serviço em três zonas. Este tutorial usa uma implementação do Kubernetes. Uma implementação é um objeto da API Kubernetes que lhe permite executar várias réplicas de pods distribuídas entre os nós num cluster.

    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 predefinição, o Autopilot aprovisiona recursos em duas zonas. A implementação definida em prepare-for-ha.yaml garante que o Autopilot aprovisiona nós em três zonas no seu cluster, definindo replicas:3, podAntiAffinity com requiredDuringSchedulingIgnoredDuringExecution e topologyKey: "topology.kubernetes.io/zone".

  4. Verifique o estado da implementação.

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

    Quando vir três pods no estado pronto, cancele este comando com CTRL+C. O resultado é semelhante ao seguinte:

    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 este script para validar se os seus pods foram implementados em três zonas.

    bash ../scripts/inspect_pod_node.sh default
    

    Cada linha do resultado corresponde a um agrupamento e a segunda coluna indica a zona. O resultado é semelhante ao seguinte:

    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
    

Standard

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

    gcloud container clusters create $CLUSTER_NAME \
      --location=$CONTROL_PLANE_LOCATION \
      --machine-type="e2-standard-2" \
      --disk-type="pd-standard" \
      --num-nodes="5"
    
  2. Obtenha as credenciais do cluster do GKE.

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

Implemente StatefulSets do MySQL

Nesta secção, implementa um StatefulSet do MySQL. Um StatefulSet é um controlador do Kubernetes que mantém uma identidade única persistente para cada um dos respetivos pods.

Cada StatefulSet consiste em três réplicas do MySQL.

Para implementar o StatefulSet do MySQL, siga estes passos:

  1. Crie um espaço de nomes para o StatefulSet.

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

    A palavra-passe é implementada com cada pod e é usada por comandos e scripts de gestão para a implementação do MySQL InnoDB Cluster e do ClusterSet neste tutorial.

  3. Crie a 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 classe de armazenamento usa o tipo de disco persistente pd-balanced que equilibra o desempenho e o custo. O campo volumeBindingMode está definido como WaitForFirstConsumer, o que significa que o GKE atrasa o aprovisionamento de um PersistentVolume até o pod ser criado. Esta definição garante que o disco é aprovisionado na mesma zona onde o pod está agendado.

  4. Implemente o StatefulSet de pods de instâncias 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
    

    Este comando implementa o StatefulSet composto por três réplicas. Neste tutorial, o cluster MySQL principal é implementado em três zonas no us-west1. O resultado é semelhante ao seguinte:

    service/mysql created
    statefulset.apps/dbc1 created
    

    Neste tutorial, os limites e os pedidos de recursos estão definidos para valores mínimos para poupar custos. Ao planear uma carga de trabalho de produção, certifique-se de que define estes valores adequadamente para as necessidades da sua organização.

  5. Verifique se o StatefulSet foi criado com êxito.

    kubectl get statefulset -n mysql1 --watch
    

    O StatefulSet pode demorar cerca de 10 minutos a ficar pronto.

  6. Quando todos os 3 pods estiverem no estado pronto, saia do comando com Ctrl+C. Se vir PodUnscheduleable erros devido a CPU ou memória insuficientes, aguarde alguns minutos para que o plano de controlo seja redimensionado de forma a acomodar a grande carga de trabalho.

    O resultado é semelhante ao seguinte:

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

    bash ../scripts/inspect_pod_node.sh mysql1 mysql
    

    O resultado mostra o nome do pod, o nome do nó do GKE e a zona onde o nó é aprovisionado, 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 anfitrião, a zona na nuvem e o nome do pod, respetivamente.

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

    A política podAntiAffinity aplica a restrição de que os pods não podem ser colocados no mesmo nó do cluster do GKE (kubernetes.io/hostname). Para os pods da instância do MySQL, esta política resulta na implementação dos pods de forma uniforme nas três zonas na região Google Cloud . Este posicionamento permite a alta disponibilidade do cluster MySQL InnoDB ao colocar cada instância da base de dados num domínio de falha separado.

Prepare o cluster InnoDB do MySQL principal

Para configurar um cluster InnoDB do MySQL, siga estes passos:

  1. No terminal do Cloud Shell, defina as configurações de replicação de grupo para as instâncias do MySQL a adicionar ao 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 vai estabelecer ligação 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 no cluster se ligue a qualquer instância no grupo.
    • binlog_transaction_dependency_tracking='WRITESET': permite transações paralelizadas que não entram 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 não ter de criar uma shell para cada Pod.

  3. Estabeleça ligação 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. Valide a lista de autorizações da replicação de grupo do MySQL para estabelecer ligação a outras instâncias.

    \sql SELECT @@group_replication_ip_allowlist;
    

    O resultado é semelhante ao seguinte:

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

    \sql SELECT @@server_id;
    

    O resultado é semelhante ao seguinte:

    +-------------+
    | @@server_id |
    +-------------+
    |          21 |
    +-------------+
    
  6. Configure cada instância para utilização do cluster MySQL InnoDB e crie 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 têm de ter o mesmo nome de utilizador e palavra-passe para que o cluster MySQL InnoDB funcione corretamente. Cada comando produz um resultado semelhante ao seguinte:

    ...
    
    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 num cluster InnoDB do MySQL.

    dba.checkInstanceConfiguration()
    

    O resultado é semelhante ao seguinte:

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

    Opcionalmente, pode estabelecer ligação a cada instância do MySQL e repetir este comando. Por exemplo, execute este comando para verificar o estado na 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()"'
    

Crie o cluster InnoDB do MySQL principal

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

Para inicializar o MySQL InnoDB Cluster, siga estes passos:

  1. Crie o cluster InnoDB do MySQL.

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

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

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

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

    O resultado tem um aspeto semelhante ao seguinte:

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

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

    O resultado é semelhante ao seguinte:

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

    cluster.status()
    

    Este comando mostra o estado do cluster. A topologia consiste em três anfitriões, uma instância principal e duas secundárias. Opcionalmente, pode ligar para cluster.status({extended:1}).

    O resultado é semelhante ao seguinte:

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

    Opcionalmente, pode ligar para cluster.status({extended:1}) para obter detalhes adicionais do estado.

Crie uma base de dados de amostra

Para criar uma base de dados de exemplo, siga estes passos:

  1. Crie uma base de dados e carregue dados na base de dados.

    \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. Inserir dados de amostra na base de dados. Para inserir dados, tem de ter ligação à 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 no passo anterior.

    SELECT * FROM loan;
    

    O resultado é semelhante ao seguinte:

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

Crie um ClusterSet do MySQL InnoDB

Pode criar um ClusterSet do MySQL InnoDB para gerir a replicação do cluster principal para clusters de réplica, usando um canal de replicação do ClusterSet dedicado.

Um MySQL InnoDB ClusterSet oferece tolerância a desastres para implementações do MySQL InnoDB Cluster, associando um MySQL InnoDB Cluster principal a uma ou mais réplicas do mesmo em localizações alternativas, como várias zonas e várias regiões.

Se fechou o MySQL Shell, crie uma nova shell executando este comando num 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 MySQL InnoDB ClusterSet, siga estes passos:

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

    \js
    cluster=dba.getCluster()
    

    O resultado é semelhante ao seguinte:

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

    clusterset=cluster.createClusterSet('clusterset')
    

    O resultado é semelhante ao seguinte:

    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 estado do seu MySQL InnoDB ClusterSet.

    clusterset.status()
    

    O resultado é semelhante ao seguinte:

    {
        "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, pode chamar clusterset.status({extended:1}) para obter detalhes adicionais do estado, incluindo informações sobre o cluster.

  4. Saia do MySQL Shell.

    \q
    

Implemente um MySQL Router

Pode implementar um MySQL Router para direcionar o tráfego da aplicação cliente para os clusters adequados. O encaminhamento baseia-se na porta de ligação da aplicação que emite uma operação de base de dados:

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

Quando inicia um MySQL Router, este é inicializado em relação à implementação do MySQL InnoDB ClusterSet. As instâncias do MySQL Router associadas ao MySQL InnoDB ClusterSet têm conhecimento de quaisquer comutações controladas ou failovers de emergência e direcionam o tráfego para o novo cluster principal.

Para implementar um MySQL Router, siga estes passos:

  1. No terminal do Cloud Shell, implemente o MySQL Router.

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

    O resultado é semelhante ao seguinte:

    configmap/mysql-router-config created
    service/mysql-router created
    deployment.apps/mysql-router created
    
  2. Verifique a prontidão da implementação do MySQL Router.

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

    Quando os três pods estiverem prontos, o resultado é semelhante ao seguinte:

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

    Se vir um erro PodUnschedulable na consola, aguarde um ou dois minutos enquanto o GKE aprovisiona mais nós. Atualize a página e deverá ver 3/3 OK.

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

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

    Este comando liga-se ao pod dbc1-0 e, em seguida, inicia um shell ligado à instância do MySQL dbc1-0.

  4. Valide a configuração do router.

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

    O resultado é semelhante ao seguinte:

    {
      "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 este script para inspecionar o posicionamento dos pods do MySQL Router.

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

    O script mostra o posicionamento do nó e da zona na nuvem de todos os pods no espaço de nomes mysql1, em que o resultado é semelhante ao seguinte:

    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
    

    Pode observar que os pods do router MySQL estão distribuídos igualmente pelas zonas, ou seja, não estão colocados no mesmo nó que um pod MySQL nem no mesmo nó que outro pod do router MySQL.

Faça a gestão das atualizações do GKE e do MySQL InnoDB Cluster

As atualizações do MySQL e do Kubernetes são lançadas regularmente. Siga as práticas recomendadas operacionais para atualizar regularmente o seu ambiente de software. Por predefinição, o GKE gere as atualizações de clusters e node pools por si. O Kubernetes e o GKE também oferecem funcionalidades adicionais para facilitar as atualizações de software do MySQL.

Planeie as atualizações do GKE

Pode tomar medidas proativas e definir configurações para mitigar o risco e facilitar uma atualização mais suave do cluster quando estiver a executar serviços com estado, incluindo:

  • Clusters padrão: siga as práticas recomendadas do GKE para atualizar clusters. Escolha uma estratégia de atualização adequada para garantir que as atualizações ocorrem durante o período de manutenção:

    • Escolha atualizações de picos se a otimização de custos for importante e se as suas cargas de trabalho puderem tolerar um encerramento normal em menos de 60 minutos.
    • Escolha atualizações azul-verde se as suas cargas de trabalho forem menos tolerantes a interrupções e um aumento temporário do custo devido a uma maior utilização de recursos for aceitável.

    Para saber mais, consulte o artigo Atualize um cluster que execute uma carga de trabalho com estado. Os clusters do Autopilot são atualizados automaticamente com base no canal de lançamento que selecionou.

  • Use janelas de manutenção para garantir que as atualizações ocorrem quando pretende. Antes da janela de manutenção, certifique-se de que as cópias de segurança da base de dados são bem-sucedidas.

  • Antes de permitir o tráfego para os nós do MySQL atualizados, use Readiness Probes e Liveness Probes para garantir que estão prontos para o tráfego.

  • Crie sondagens que avaliem se a replicação está sincronizada antes de aceitar tráfego. Isto pode ser feito através de scripts personalizados, consoante a complexidade e a escala da sua base de dados.

Defina uma política de orçamento de interrupção de pods (PDB)

Quando um cluster InnoDB do MySQL está a ser executado no GKE, tem de haver um número suficiente de instâncias em execução em qualquer altura para cumprir o requisito de quórum.

Neste tutorial, dado um cluster do MySQL de três instâncias, duas instâncias têm de estar disponíveis para formar um quórum. Uma política de PodDisruptionBudget permite-lhe limitar o número de agrupamentos que podem ser terminados em qualquer altura. Isto é útil para as operações de estado estável dos seus serviços com estado e para as atualizações de clusters.

Para garantir que um número limitado de pods são interrompidos em simultâneo, defina o PDB para a sua carga de trabalho como maxUnavailable: 1. Isto garante que, em qualquer ponto da operação do serviço, não há mais do que um pod em execução.

O manifesto da política PodDisruptionBudget seguinte define o número máximo de pods indisponíveis como um para a sua aplicação 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 seu cluster, siga estes passos:

  1. Aplique a política de PDB através do kubectl.

    kubectl apply -n mysql1 -f mysql-pdb-maxunavailable.yaml
    
  2. Veja o estado da PDB.

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

    Na secção status do resultado, consulte as quantidades de currentHealthy e desiredHealthy pods. O resultado é semelhante ao seguinte:

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

Planeie as atualizações binárias do MySQL

O Kubernetes e o GKE oferecem funcionalidades para facilitar as atualizações do binário do MySQL. No entanto, tem de realizar algumas operações para se preparar para as atualizações.

Tenha em atenção as seguintes considerações antes de iniciar o processo de atualização:

  • As atualizações devem ser realizadas primeiro num ambiente de teste. Para sistemas de produção, deve realizar mais testes num ambiente de pré-produção.
  • Para algumas versões binárias, não pode reverter a versão depois de fazer uma atualização. Dedique tempo a compreender as implicações de uma atualização.
  • As origens de replicação podem ser replicadas para uma versão mais recente. No entanto, a cópia de uma versão mais recente para uma versão mais antiga não é normalmente suportada.
  • Certifique-se de que tem uma cópia de segurança completa da base de dados antes de implementar a versão atualizada.
  • Tenha em atenção a natureza efémera dos pods do Kubernetes. Qualquer estado de configuração armazenado pelo pod que não esteja no volume persistente é perdido quando o pod é reimplementado.
  • Para atualizações binárias do MySQL, use o mesmo PDB, estratégia de atualização do conjunto de nós e sondas, conforme descrito anteriormente.

Num ambiente de produção, deve seguir estas práticas recomendadas:

  • Crie uma imagem do contentor com a nova versão do MySQL.
  • Persista as instruções de compilação de imagens num repositório de controlo de origem.
  • Use um pipeline de compilação e testes de imagens automatizado, como o Cloud Build, e armazene o ficheiro binário da imagem num registo de imagens, como o Artifact Registry.

Para manter este tutorial simples, não vai criar nem persistir uma imagem de contentor. Em alternativa, vai usar as imagens públicas do MySQL.

Implemente o ficheiro binário do MySQL atualizado

Para fazer a atualização binária do MySQL, emite um comando declarativo que modifica a versão da imagem do recurso StatefulSet. O GKE executa os passos necessários para parar o pod atual, implementar um novo pod com o binário atualizado e anexar o disco persistente ao novo pod.

  1. Verifique se o PDB foi criado.

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

    kubectl get statefulsets -n mysql1
    
  3. Obtenha a lista de Pods em execução com a etiqueta app.

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

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

    O resultado é semelhante ao seguinte:

    statefulset.apps/mysql image updated
    
  5. Verifique o estado dos pods de terminação e dos novos pods.

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

Valide a atualização binária do MySQL

Durante a atualização, pode verificar o estado da implementação, os novos pods e o serviço existente.

  1. Confirme a atualização executando o comando rollout status.

    kubectl rollout status statefulset/dbc1 -n mysql1
    

    O resultado é semelhante ao seguinte:

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

    kubectl get statefulsets -o wide -n mysql1
    

    O resultado é semelhante ao seguinte:

    NAME   READY   AGE   CONTAINERS   IMAGES
    dbc1   3/3     37m   mysql        mysql/mysql-server:8.0.30
    
  3. Verifique o estado 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 estado e versão na saída. O resultado é semelhante ao seguinte:

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

Reverter a implementação do lançamento da última app

Quando reverte a implementação de uma versão binária atualizada, o processo de implementação é invertido e é implementado um novo conjunto de pods com a versão da imagem anterior.

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

kubectl rollout undo statefulset/dbc1 -n mysql1

O resultado é semelhante ao seguinte:

statefulset.apps/dbc1 rolled back

Expanda o cluster da base de dados horizontalmente

Para dimensionar o cluster do MySQL InnoDB horizontalmente, adicione nós adicionais ao conjunto de nós do cluster do GKE (apenas necessário se estiver a usar o Standard), implemente instâncias adicionais do MySQL e, em seguida, adicione cada instância ao cluster do MySQL InnoDB existente.

Adicione nós ao seu cluster padrão

Esta operação não é necessária se estiver a usar um cluster do Autopilot.

Para adicionar nós ao seu cluster Standard, siga as instruções abaixo para o Cloud Shell ou a Google Cloud consola. Para ver passos detalhados, consulte o artigo Redimensione um conjunto de nós

gcloud

No Cloud Shell, redimensione o node pool predefinido para oito instâncias em cada grupo de instâncias gerido.

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

Consola

Para adicionar nós ao cluster padrão:

  1. Abra a gkemulti-west1página Cluster na Google Cloud consola.
  2. Selecione Nodes e clique em default pool.
  3. Desloque a página para baixo até Grupos de instâncias.
  4. Para cada grupo de instâncias, redimensione o valor de Number of nodes de 5 para 8 nós.

Adicione pods do MySQL ao cluster principal

Para implementar pods MySQL adicionais para dimensionar o cluster horizontalmente, siga estes passos:

  1. No Cloud Shell, atualize o número de réplicas na implementaçã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 progresso da implementação.

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

    Para determinar se os pods estão prontos, use a flag --watch para monitorizar a implementação. Se estiver a usar clusters do Autopilot e vir erros Pod Unschedulable, isto pode indicar que o GKE está a aprovisionar nós para acomodar os pods adicionais.

  3. Configure as definições de replicação de grupos para as novas instâncias do MySQL a adicionar ao cluster

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

    O script envia os comandos para as instâncias em execução nos pods com os 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. Configure 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 a utilização do MySQL InnoDB Cluster e fazem 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 nova instância ao cluster principal.

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

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

    O resultado é semelhante ao seguinte:

    "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