Adopte un… cluster MySQL Group Replication

avril 10, 2017

Autant le dire tout de suite, rien avoir avec un site internet de rencontre en ligne! 🙂
C’est bel et bien un nouvel article dans la série, Haute Disponibilité avec MySQL.

Au menu d’aujourd’hui : comment passer de l’administration « manuelle » de votre solution HA MySQL Group Replication à une administration plus simple, plus fun mais surtout facilement automatisable avec le pack MySQL InnoDB Cluster. En clair, on va voir comment utiliser MySQL Shell pour l’administration et l’orchestration du cluster et MySQL Router pour rediriger automatiquement les transactions de l’application vers le noeud primaire du cluster.

Quelques pré-requis sont nécessaire pour optimiser ta compréhension de cet article, je te conseille donc la lecture préalable des articles suivants:

 

Note: 
L’article traite de MySQL InnoDB Cluster, HA natif de MySQL Server, solution à ne pas confondre avec MySQL NDB Cluster.

 

Le contexte

Pour ce PoC, j’ai un cluster MySQL Group Replication de 3 nœuds, fonctionnel, en mode « Single Primary » (déployé avec Docker Compose):

  • Instance 1 : mysql_node1 (172.19.0.2)
  • Instance 2 : mysql_node2 (172.19.0.4)
  • Instance 3 : mysql_node3 (172.19.0.3)
$ docker inspect mysql_node1 | grep IPAddress | tail -1
                    "IPAddress": "172.19.0.2",
$ docker inspect mysql_node2 | grep IPAddress | tail -1
                    "IPAddress": "172.19.0.4",
$ docker inspect mysql_node3 | grep IPAddress | tail -1
                    "IPAddress": "172.19.0.3",

 

MySQL Router et mon application (simulée avec le client texte mysql) sont sur la machine host (par commodité). C’est également le cas de MySQL Shell.

En ce qui concerne les versions des softs:

  • MySQL Server 5.7.17
  • MySQL Router 2.1.2 rc
  • MySQL Shell 1.0.8-rc

Docker 1.12.6 & Docker-compose 1.11.2. Docker est hors du cadre de cet article, mais tu trouveras à la fin de cet article le fichier docker-compose.yml utilisé.

 

Ah oui, j’ai failli oublier :

TL;DR
Tu as un cluster MySQL Group Replication configuré/administré manuellement et qui tourne. Tu peux l’administrer / le configurer avec MySQL Shell et gérer le routage des requêtes applicatives avec MySQL Router, ces 3 composants forment MySQL InnoDB Cluster.

MySQL InnoDB Cluster Overview

 

MySQL Group Replication

Les étapes de déploiement du cluster Group Replication ont déjà été traitées ici.

Voici mes 3 instances MySQL 5.7

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                     NAMES
f4c4aa2d3726        mysql:5.7           "docker-entrypoint.sh"   About a minute ago   Up About a minute   0.0.0.0:14002->3306/tcp   mysql_node2
2304f0e44d4c        mysql:5.7           "docker-entrypoint.sh"   About a minute ago   Up About a minute   0.0.0.0:14003->3306/tcp   mysql_node3
fb6ae3c76a06        mysql:5.7           "docker-entrypoint.sh"   About a minute ago   Up About a minute   0.0.0.0:14001->3306/tcp   mysql_node1

MySQL 5.7.17 plus précisément.

 $ docker exec -it mysql_node1 mysql -uroot -p -e"SELECT version();"
Enter password: 
+------------+
| version()  |
+------------+
| 5.7.17-log |
+------------+

 

A quoi ressemble mon cluster Group Replication ?

Je peux avoir la description de l’architecture avec la table performance_schema.replication_group_members :

docker exec -it mysql_node1 mysql -uroot -p -e"SELECT * FROM performance_schema.replication_group_members\G" 
Enter password: 
*************************** 1. row ***************************
CHANNEL_NAME: group_replication_applier
   MEMBER_ID: d300be14-1797-11e7-a22e-0242ac130002
 MEMBER_HOST: mysql_node1
 MEMBER_PORT: 3306
MEMBER_STATE: ONLINE
*************************** 2. row ***************************
CHANNEL_NAME: group_replication_applier
   MEMBER_ID: d3100569-1797-11e7-a278-0242ac130004
 MEMBER_HOST: mysql_node2
 MEMBER_PORT: 3306
MEMBER_STATE: ONLINE
*************************** 3. row ***************************
CHANNEL_NAME: group_replication_applier
   MEMBER_ID: d321b2db-1797-11e7-a16f-0242ac130003
 MEMBER_HOST: mysql_node3
 MEMBER_PORT: 3306
MEMBER_STATE: ONLINE

L’identification du noeud primaire peut se faire de la manière suivante :

$ docker exec -it mysql_node1 mysql -uroot -p -e"SELECT MEMBER_ID, MEMBER_HOST, MEMBER_STATE FROM performance_schema.replication_group_members INNER JOIN performance_schema.global_status ON (MEMBER_ID = VARIABLE_VALUE) WHERE VARIABLE_NAME='group_replication_primary_member'\G" 
Enter password: 
*************************** 1. row ***************************
   MEMBER_ID: 8b5bad71-1720-11e7-94f0-0242ac130002
 MEMBER_HOST: mysql_node1
MEMBER_STATE: ONLINE

Le noeud mysql_node1 est donc en mode lecture écriture aka le noeud primaire (cette info nous sera utile pour la suite) et les 2 autres en lecture seule (super read only activé):

$ docker exec -it mysql_node2 mysql -uroot -p -e"CREATE SCHEMA gr_test"
Enter password: 
ERROR 1290 (HY000) at line 1: The MySQL server is running with the --super-read-only option so it cannot execute this statement
$ docker exec -it mysql_node1 mysql -uroot -p -e"CREATE SCHEMA gr_test;"
Enter password: 

$ docker exec -it mysql_node2 mysql -uroot -p -e"SHOW SCHEMAS;"
Enter password: 
+--------------------+
| Database           |
+--------------------+
| information_schema |
| gr_test            |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

 

On a donc un cluster MySQL Group Replication avec 3 nœuds online.
Le membre mysql_node1 est (pour le moment) le primaire, mysql_node2 et mysql_node3 sont les secondaires.

 

L’étape suivant consistera à gérer le cluster avec MySQL Shell.

 

MySQL Shell, interface pour gérer son cluster

On va se connecter avec le client MySQL Shell au noeud primaire :

$ # Connect to the primary node : mysql_node1
$ mysqlsh --uri=root@mysql_node1
Creating a Session to 'root@mysql_node1'super read only
Enter password: 
Classic Session successfully established. No default schema selected.
Welcome to MySQL Shell 1.0.8-rc

Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type '\help', '\h' or '\?' for help, type '\quit' or '\q' to exit.

Currently in JavaScript mode. Use \sql to switch to SQL mode and execute queries.
mysql-js>

Ensuite, je « crée » mon cluster, en fait je vais rendre persistante les informations relatives à l’architecture du groupe dans mon cluster (plus d’info sur ce sujet plus bas).

mysql-js> var cluster=dba.createCluster('pocCluster', {adoptFromGR: true, ipWhitelist:'172.19.0.0/16'})
A new InnoDB cluster will be created on instance 'root@mysql_node1:3306'.

Creating InnoDB cluster 'pocCluster' on 'root@mysql_node1: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.

La méthode createCluster() prends comme paramètres, le nom du cluster (pocCluster) ainsi que des paramètres optionnels comme ipWhitelist (172.19.0.0/16)…

Pour plus de détails connecte toi à MySQL Shell (mysqlsh) et tape : dba.help(‘createCluster’)

$ mysqlsh
*Welcome to MySQL Shell 1.0.8-rc

Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type '\help', '\h' or '\?' for help, type '\quit' or '\q' to exit.

Currently in JavaScript mode. Use \sql to switch to SQL mode and execute queries.

mysql-js> dba.help('createCluster')

 

Vérifions l’état du cluster

mysql-js> /* Check cluster status */
mysql-js> cluster.status()
{
    "clusterName": "pocCluster", 
    "defaultReplicaSet": {
        "name": "default", 
        "primary": "mysql_node1:3306", 
        "status": "OK", 
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", 
        "topology": {
            "mysql_node1:3306": {
                "address": "mysql_node1:3306", 
                "mode": "R/W", 
                "readReplicas": {}, 
                "role": "HA", 
                "status": "ONLINE"
            }, 
            "mysql_node2:3306": {
                "address": "mysql_node2:3306", 
                "mode": "R/O", 
                "readReplicas": {}, 
                "role": "HA", 
                "status": "ONLINE"
            }, 
            "mysql_node3:3306": {
                "address": "mysql_node3:3306", 
                "mode": "R/O", 
                "readReplicas": {}, 
                "role": "HA", 
                "status": "ONLINE"
            }
        }
    }
}

MySQL Shell nous confirme ce que nous savions déjà:

je m’auto cite; un grand (1m89) DBA à dit un jour :

« On a donc un cluster MySQL Group Replication déployé avec 3 nœuds online. Le membre mysql_node1 est (pour le moment) le primaire, mysql_node2 et mysql_node3 sont les secondaires. »

 

En zoomant dans les entrailles du groupe, on constate que la méthode createCluster() a écrit des données dans le cluster :

$ docker exec -it mysql_node1 mysql -uroot -p -e"SHOW SCHEMAS; SHOW TABLES IN mysql_innodb_cluster_metadata;"
Enter password: 
+-------------------------------+
| Database                      |
+-------------------------------+
| information_schema            |
| gr_test                       |
| mysql                         |
| mysql_innodb_cluster_metadata |
| performance_schema            |
| sys                           |
+-------------------------------+

+-----------------------------------------+
| Tables_in_mysql_innodb_cluster_metadata |
+-----------------------------------------+
| clusters                                |
| hosts                                   |
| instances                               |
| replicasets                             |
| routers                                 |
| schema_version                          |
+-----------------------------------------+

Le schéma mysql_innodb_cluster_metadata a donc été créé pour contenir les informations relatives au cluster.

Le nom des tables est assez explicite :

hosts

$ docker exec -it mysql_node1 mysql -uroot -p -e" SELECT * FROM mysql_innodb_cluster_metadata.hosts\G"
Enter password: 
*************************** 1. row ***************************
           host_id: 6
         host_name: mysql_node1
        ip_address: 
 public_ip_address: NULL
          location: 
        attributes: NULL
admin_user_account: NULL
*************************** 2. row ***************************
           host_id: 13
         host_name: mysql_node2
        ip_address: 
 public_ip_address: NULL
          location: 
        attributes: NULL
admin_user_account: NULL
*************************** 3. row ***************************
           host_id: 20
         host_name: mysql_node3
        ip_address: 
 public_ip_address: NULL
          location: 
        attributes: NULL
admin_user_account: NULL

 

clusters

 $ docker exec -it mysql_node1 mysql -uroot -p -e" SELECT * FROM mysql_innodb_cluster_metadata.clusters\G"
Enter password: 
*************************** 1. row ***************************
         cluster_id: 6
       cluster_name: pocCluster
 default_replicaset: 6
        description: Default Cluster
mysql_user_accounts: NULL
            options: {"adminType": "local"}
         attributes: {"default": true}

 

replicasets

$ docker exec -it mysql_node1 mysql -uroot -p -e" SELECT * FROM mysql_innodb_cluster_metadata.replicasets\G"
Enter password: 
*************************** 1. row ***************************
  replicaset_id: 6
     cluster_id: 6
replicaset_type: gr
  topology_type: pm
replicaset_name: default
         active: 1
     attributes: {"adopted": "true", "group_replication_group_name": "4e0f05b7-d9d0-11e6-87cf-002710cccc64"}
    description: NULL

 

instances

$ docker exec -it mysql_node1 mysql -uroot -p -e" SELECT * FROM mysql_innodb_cluster_metadata.instances\G"
Enter password: 
*************************** 1. row ***************************
      instance_id: 6
          host_id: 6
    replicaset_id: 6
mysql_server_uuid: d300be14-1797-11e7-a22e-0242ac130002
    instance_name: mysql_node1:3306
             role: HA
           weight: NULL
        addresses: {"mysqlX": "mysql_node1:33060", "grLocal": "mysql_node1:4999", "mysqlClassic": "mysql_node1:3306"}
       attributes: NULL
    version_token: NULL
      description: NULL
*************************** 2. row ***************************
      instance_id: 13
          host_id: 13
    replicaset_id: 6
mysql_server_uuid: d3100569-1797-11e7-a278-0242ac130004
    instance_name: mysql_node2:3306
             role: HA
           weight: NULL
        addresses: {"mysqlX": "mysql_node2:33060", "grLocal": "mysql_node2:4999", "mysqlClassic": "mysql_node2:3306"}
       attributes: NULL
    version_token: NULL
      description: NULL
*************************** 3. row ***************************
      instance_id: 20
          host_id: 20
    replicaset_id: 6
mysql_server_uuid: d321b2db-1797-11e7-a16f-0242ac130003
    instance_name: mysql_node3:3306
             role: HA
           weight: NULL
        addresses: {"mysqlX": "mysql_node3:33060", "grLocal": "mysql_node3:4999", "mysqlClassic": "mysql_node3:3306"}
       attributes: NULL
    version_token: NULL
      description: NULL

 

 

Déploiement de MySQL Router

Le déploiement du router est trivial, il faut pour commencer le bootstrapper au cluster, c’est à dire le lier au cluster en le connectant aux méta-données :

$ mysqlrouter --bootstrap root@mysql_node1:3306 --directory routerDocker --name routerDocker
[sudo] password for daz: 
Please enter MySQL password for root: 

Bootstrapping MySQL Router instance at /home/daz/routerDocker...
MySQL Router 'routerDocker' has now been configured for the InnoDB cluster 'pocCluster'.

The following connection information can be used to connect to the cluster.

Classic MySQL protocol connections to cluster 'pocCluster':
- Read/Write Connections: localhost:6446
- Read/Only Connections: localhost:6447

X protocol connections to cluster 'pocCluster':
- Read/Write Connections: localhost:64460
- Read/Only Connections: localhost:64470

Les paramètres directory et name sont optionnels.

 

Lancer MySQL Router :

$ mysqlrouter -c ~/routerDocker/mysqlrouter.conf &

L’application doit se connecter (par défaut) au port 6446 (écritures et lectures vers le noeud primaire). En cas de besoin de read scalability, les lectures peuvent être dirigées vers le port 6447.

 

Inspectons de nouveau les méta données :

routers

$ docker exec -it mysql_node1 mysql -uroot -p -e" SELECT * FROM mysql_innodb_cluster_metadata.routers\G"
Enter password: 
*************************** 1. row ***************************
  router_id: 6
router_name: routerDocker
    host_id: 27
 attributes: NULL

 

hosts

$ docker exec -it mysql_node1 mysql -uroot -p -e" SELECT * FROM mysql_innodb_cluster_metadata.hosts\G"
Enter password: 
*************************** 1. row ***************************
           host_id: 6
         host_name: mysql_node1
        ip_address: 
 public_ip_address: NULL
          location: 
        attributes: NULL
admin_user_account: NULL
*************************** 2. row ***************************
           host_id: 13
         host_name: mysql_node2
        ip_address: 
 public_ip_address: NULL
          location: 
        attributes: NULL
admin_user_account: NULL
*************************** 3. row ***************************
           host_id: 20
         host_name: mysql_node3
        ip_address: 
 public_ip_address: NULL
          location: 
        attributes: NULL
admin_user_account: NULL
*************************** 4. row ***************************
           host_id: 27
         host_name: 
        ip_address: NULL
 public_ip_address: NULL
          location: 
        attributes: {"registeredFrom": "mysql-router"}
admin_user_account: NULL

 

Voilà, mon cluster Group Replication paramétré « à la main » fait maintenant partie intégrante de mon InnoDB Cluster, je peux donc l’administrer avec MySQL Shell et je peux vous assurer que c’est vraiment pratique.

Mais ceci est une autre histoire et fera l’objet d’un autre article 🙂

 

Annexe

Le fichier docker-compose est le suivant :

version: '2'
services:
  node1:
    container_name: mysql_node1
    image: "mysql:5.7"
    volumes:
      - ~/Documents/Docker/confdir/mysql1:/etc/mysql/conf.d
    ports:
      - "14001:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
    networks:
      app_net:
        ipv4_address: 172.19.0.2
  node2:
    container_name: mysql_node2
    image: "mysql:5.7"
    volumes:
      - ~/Documents/Docker/confdir/mysql2:/etc/mysql/conf.d
    ports:
      - "14002:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
    networks:
      app_net:
        ipv4_address: 172.19.0.4
  node3:
    container_name: mysql_node3
    image: "mysql:5.7"
    volumes:
      - ~/Documents/Docker/confdir/mysql3:/etc/mysql/conf.d
    ports:
      - "14003:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
    networks:
      app_net:
        ipv4_address: 172.19.0.3

networks:
  app_net:
    driver: bridge
    ipam:
      driver: default
      config:
      -
        subnet: 172.19.0.0/24

 

Thanks for using MySQL!

6 Responses to “Adopte un… cluster MySQL Group Replication”

  1. Bonjour Olivier,

    Tout d’abord merci pour ce post très bien détaillé !
    Je viens de mettre en place un Group Replication, et ça fonctionne très bien.
    Par contre, je n’arrive pas à le transformer en un InnoDB cluster… ma commande de createCluster (avec option adoptFromGR )échoue :
    Dba.createCluster: Dba.checkInstanceConfiguration: The instance ‘root@localhost:3331’ is already part of a Replication Group (RuntimeError)

    Effectivement, l’instance fait partie d’un Group Replication mais il me semblait qu’il fallait que ce soit justement le cas pour ajouter l’option « adoptFromGR » ?
    Je n’ai rien dans le mysql.err, j’avoue que je sèche un peu. Aurais tu une idée d’expert sur la question ?

    J’aurais bien aimé administrer mon cluster avec MySQL Shell, je trouve les commandes bien pratiques…

    J’oubliais, ma config est la suivante : MySQL 5.7.19 sur SLES12sp1 ; Group replication sur 3 noeuds avec un seul maitre ; j’effectue la création du cluster en me connectant sur le noeud maitre de mon archi.

    D’avance merci pour ton retour.

    Cordialement
    Karl

  2. Bonjour Olivier,

    Je reviens vers toi avec une solution à mon problème… en fait, j’avais démarré le Group Replication avant de lancer la creation du Cluster.
    Ceci bloque la création, il faut que le Group Replication soit configuré mais non actif pour que le cluster soit créé.

    Encore merci pour tes posts !

    Cordialement
    Karl

  3. Bonjour,

    Je ne comprends pas pourquoi il faut (re) »créé » un cluster avec MySQL Shell alors que le but de l’article est de gérer un cluster déjà existant.
    Question complémentaire, si je suis votre méthode, est-ce que je vais « casser » le cluster déjà existant.

    Pour approfondir, mise à part l’automatisation facilitée, quelles sont les différences à gérer mon cluster via MySQL Shell plutôt que manuellement (comme ici http://dasini.net/blog/2016/11/08/deployer-un-cluster-mysql-group-replication/) ?

    Il y a sûrement des subtilités qui m’échappe entre « group replication » et « innodb cluster ». Pour moi, innodb cluster c’est la combinaison de group_replication et mysql router.

    Encore merci pour vos articles.

  4. Bonjour,

    MySQL ne me semble vraiment pas prêt pour un usage en production. Je n’arrive pas à suivre ce tutoriel alors que j’ai quelque chose de très simple.

    Point de départ: un group_replication créé à la main en suivant votre article « deployer-un-cluster-mysql-group-replication ».

    Je rencontre 2 erreurs (ou bugs)

    – Erreur de port
    Si l’URI rentré ne contient pas le port, la fonction createCluster renvoie une erreur

    ——————–
    [root@mysql-test-01 ~]# mysqlsh –uri=mysqlshell_user@mysql-test-02
    Creating a Session to ‘mysqlshell_user@mysql-test-02’
    Enter password:
    Your MySQL connection id is 41
    Server version: 5.7.19-log MySQL Community Server (GPL)
    No default schema selected; type \use to set one.
    MySQL Shell 1.0.10

    Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.

    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.

    Type ‘\help’ or ‘\?’ for help; ‘\quit’ to exit.

    Currently in JavaScript mode. Use \sql to switch to SQL mode and execute queries.
    mysql-js> var cluster=dba.createCluster(‘testCluster’, {adoptFromGR: true})
    A new InnoDB cluster will be created on instance ‘mysqlshell_user@mysql-test-02:’.

    Dba.createCluster: Missing port number (ArgumentError)
    ——————–

    le contre exemple est plus bas, l’uri utilisé comprend cette fois-ci le port. Mais l’erreur cette fois-ci.

    – Erreur, « the instance is already part of group replication » alors que j’ai bien spécifié l’option « adoptFromGR »

    ——————–
    [root@mysql-test-01 ~]# mysqlsh –uri=mysqlshell_user@mysql-test-02:3306
    Creating a Session to ‘mysqlshell_user@mysql-test-02:3306’
    Enter password:
    Your MySQL connection id is 43
    Server version: 5.7.19-log MySQL Community Server (GPL)
    No default schema selected; type \use to set one.
    MySQL Shell 1.0.10

    Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.

    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.

    Type ‘\help’ or ‘\?’ for help; ‘\quit’ to exit.

    Currently in JavaScript mode. Use \sql to switch to SQL mode and execute queries.
    mysql-js>
    mysql-js> A new InnoDB cluster will be created on instance ‘mysqlshell_user@mysql-test-02:’.
    SyntaxError: Unexpected token new
    mysql-js> var cluster=dba.createCluster(‘testCluster’, {adoptFromGR: true})
    A new InnoDB cluster will be created on instance ‘mysqlshell_user@mysql-test-02:3306’.

    Dba.createCluster: Dba.checkInstanceConfiguration: The instance ‘mysqlshell_user@mysql-test-02:3306’ is already part of a Replication Group (RuntimeError)
    ——————–

    Je ne peux donc pas convertir mon « group_replication » déjà existant en un « innodb cluster », je ne comprends donc pas comment cela peut fonctionner chez vous.

    Info:
    centos 7 à jour
    mysql-community-server 5.7.19
    mysql-shell 1.0.10

  5. Cela semble être un bug
    https://bugs.mysql.com/bug.php?id=87599
    J’ai confirmé le bug en expliquant mon setup.
    Cdt,

  6. […] MySQL Group Replication,  comprendre et tester MySQL InnoDB Cluster  ainsi que comment  gérer aisément un cluster Group Replication déja déployé avec MySQL […]