MariaDB Galera Cluster in Docker Containers
Introduction
We are going to build a 3 node Galera Cluster, 1 Primary and 2 Nodes, using Docker containers in a Google Cloud Platform virtual machine. Previously we have installed and configured a MariaDB Galera Cluster in VM Instances, this time we are going to build it with containers.
Prerequisites
A running Docker host in any OS. We are using a virtual machine instance in Google Cloud Platform with Container Optimized OS
as it already includes Docker 19.03
.
Galera Cluster Containers
Create Docker Network
In order to ease the reference between the cluster nodes we are going to create our own docker network
of driver type bridge
so each container can find the others by name:
pato@patocontainer ~ $ docker network create --driver bridge pato-net
522c4b821356e3b574f9150653adc325d5cb680b6d4fc7f64121d8fc9aa97530
pato@patocontainer ~ $ docker network ls
NETWORK ID NAME DRIVER SCOPE
...
522c4b821356 pato-net bridge local
Build Primary Container from Customized image
Let’s create our Primary container with docker run
using the customized image patomx/patomariadb
. For more information on this image visit My MariaDB Docker Image and Volume.
We will be using the network previously created --network pato-net
and we will assign a particular name for the datadir volume -v patovolgm:/var/lib/mysql
.
pato@patocontainer ~ $ docker run -d --name patomariagm --network pato-net -v patovolgm:/var/lib/mysql patomx/patomariadb
7cb4db2c49a927404ebd26ed0a7c9cbbdae8aa07374bcc4de222249bbaa288c9
pato@patocontainer ~ $ docker stop patomariagm
patomariagm
Once created we replicate the data from our customized database to our new container volume:
pato@patocontainer ~ $ docker run --rm -i -t -v patovolmariadb:/origen -v patovolgm:/destino alpine sh -c "cp -avr /origen/* /destino"
'/origen/aria_log.00000001' -> '/destino/aria_log.00000001'
'/origen/aria_log_control' -> '/destino/aria_log_control'
...
Start the container and validate it’s running:
pato@patocontainer ~ $ docker start patomariagm
patomariagm
pato@patocontainer ~ $ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7cb4db2c49a9 patomx/patomariadb "docker-entrypoint.s…" 12 seconds ago Up 10 seconds 3306/tcp patomariagm
pato@patocontainer ~ $ docker volume ls
DRIVER VOLUME NAME
local patovolgm
local patovolmariadb
Create Objects and Data
The Primary node is expected to be the source of the data we want to replicate in the cluster so let’s create some objects and data for our testing.
Connect to our new created container with a shell session docker exec -it patomariagm bash
.
pato@patocontainer ~ $ docker exec -it patomariagm bash
and then connect to MariaDB and create a database, a table and insert a row identifying the hostname and time:
root@7cb4db2c49a9:/# mysql
MariaDB [(none)]> create database taller;
Query OK, 1 row affected (0.000 sec)
MariaDB [(none)]> use taller
Database changed
MariaDB [taller]> create table tg1 (i1 int auto_increment primary key, c2 varchar(20), d3 datetime) ;
Query OK, 0 rows affected (0.056 sec)
MariaDB [taller]> insert into tg1 (c2,d3) values (@@hostname,now());
Query OK, 1 row affected (0.005 sec)
Build Secondary Nodes for the Cluster
Create another two containers from the same image and validate they are running and volumes were created:
pato@patocontainer ~ $ docker run -d --name patomariag1 --network pato-net -v patovolg1:/var/lib/mysql patomx/patomariadb
5f9ce8bc16d24f59bc25633faf538d5fe5d4886824b51ced1fdc55b87f0c4c2a
pato@patocontainer ~ $ docker run -d --name patomariag2 --network pato-net -v patovolg2:/var/lib/mysql patomx/patomariadb
18dda3dabdf9d867ee0d0289edc8417518c9171ee4890f19f285aef56cc0dbc6
pato@patocontainer ~ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
18dda3dabdf9 patomx/patomariadb "docker-entrypoint.s…" 12 seconds ago Up 7 seconds 3306/tcp patomariag2
5f9ce8bc16d2 patomx/patomariadb "docker-entrypoint.s…" 22 seconds ago Up 18 seconds 3306/tcp patomariag1
7cb4db2c49a9 patomx/patomariadb "docker-entrypoint.s…" 59 minutes ago Up 59 minutes 3306/tcp patomariagm
pato@patocontainer ~ $ docker volume ls
DRIVER VOLUME NAME
local patovolg1
local patovolg2
local patovolgm
Test Connectivity
Access the Primary container and ping the others using the assigned container name:
pato@patocontainer ~ $ docker exec -it patomariagm bash
root@7cb4db2c49a9:/# ping patomariag1
PING patomariag1 (172.19.0.3) 56(84) bytes of data.
64 bytes from patomariag1.pato-net (172.19.0.3): icmp_seq=1 ttl=64 time=0.052 ms
...
root@7cb4db2c49a9:/# ping patomariag2
PING patomariag2 (172.19.0.4) 56(84) bytes of data.
64 bytes from patomariag2.pato-net (172.19.0.4): icmp_seq=1 ttl=64 time=0.079 ms
Stop the Containers
Issue docker stop
to shutdown the databases and validate none is running:
pato@patocontainer ~ $ docker stop patomariagm patomariag1 patomariag2
patomariagm
patomariag1
patomariag2
pato@patocontainer ~ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
pato@patocontainer ~ $
Galera Cluster Configuration
As we have our 3 containers ready we need to configure them for Galera replication.
Create Configuration Files
We need to create a local new Galera configuration file for the Primary:
pato@patocontainer ~/galera $ vi galera-init.cnf
and add the following options:
- Turn on replication and setup library
[mariadb]
# Galera Replication Configuration
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
- Define Cluster name and list of member addresses
# Galera Cluster Name and Members
wsrep_cluster_name="patocluster"
wsrep_cluster_address="gcomm://"
# for primary node the list must be empty to initialize a new cluster
- Identify this member of the Cluster
# Galera Cluster Member
wsrep_node_name="patomain"
wsrep_node_address="patomariagm"
- Define the method for the initial copy of data
# Galera State Snapshot Transfer Full Data Copy
wsrep_sst_method=rsync
- Enable Global Transaction ID
# Galera GTID Configuration
wsrep_gtid_mode=ON
wsrep_gtid_domain_id=1
- Binary log and Storage Engine configuration
# MariaDB Configuration
binlog_format=ROW
default-storage-engine=InnoDB
innodb_autoinc_lock_mode=2
log_slave_updates=ON
log_bin=galera-bin
Then create another 2 configuration files for the other nodes, with same options as previous one but changing the identification parameters:
pato@patocontainer ~/galera $ vi galera-node1.cnf
wsrep_cluster_address="gcomm://patomariagm,patomariag1,patomariag2"
# Galera Cluster Member
wsrep_node_name="patonode1"
wsrep_node_address="patomariag1"
pato@patocontainer ~/galera $ vi galera-node2.cnf
wsrep_cluster_address="gcomm://patomariagm,patomariag1,patomariag2"
# Galera Cluster Member
wsrep_node_name="patonode2"
wsrep_node_address="patomariag2"
Copy Configuration Files to the Containers
Even when containers are not running we can move files inside them from the Docker host using docker cp
command.
We are going to copy the created configuration files to a directory valid for configuration lookup for mysqld in this image, in our case we will use !includedir /etc/mysql/mariadb.conf.d/
:
pato@patocontainer ~/galera $ docker cp galera-init.cnf patomariagm:/etc/mysql/mariadb.conf.d/galera.cnf
pato@patocontainer ~/galera $ docker cp galera-node1.cnf patomariag1:/etc/mysql/mariadb.conf.d/galera.cnf
pato@patocontainer ~/galera $ docker cp galera-node2.cnf patomariag2:/etc/mysql/mariadb.conf.d/galera.cnf
Start Containers
In a Galera Cluster the Primary node should be the first one to start in order to initialize the cluster. So we are going to start each node in order and validate how many members are registered in the cluster:
pato@patocontainer ~ $ docker start patomariagm
patomariagm
pato@patocontainer ~ $ docker exec -it patomariagm mysql -e "SHOW STATUS LIKE 'wsrep_cluster_size'"
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| wsrep_cluster_size | 1 |
+--------------------+-------+
pato@patocontainer ~ $ docker start patomariag1
patomariag1
pato@patocontainer ~ $ docker exec -it patomariag1 mysql -e "SHOW STATUS LIKE 'wsrep_cluster_size'"
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| wsrep_cluster_size | 2 |
+--------------------+-------+
pato@patocontainer ~ $ docker start patomariag2
patomariag2
pato@patocontainer ~ $ docker exec -it patomariag2 mysql -e "SHOW STATUS LIKE 'wsrep_cluster_size'"
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| wsrep_cluster_size | 3 |
+--------------------+-------+
pato@patocontainer ~ $
OK our Galera cluster is setup and running and all members has joined!
Modify Configuration for Primary Node
Once the Galera cluster has been initiated we need to modifiy the parameter wsrep_cluster_address
from empty (bootstrap) to the list of cluster members, the same as configured in the other nodes:
pato@patocontainer ~/galera $ docker exec -it patomariagm bash
root@7cb4db2c49a9:/# nano /etc/mysql/mariadb.conf.d/galera.cnf
wsrep_cluster_address="gcomm://patomariagm,patomariag1,patomariag2"
Test Replication on Galera Cluster
Now we are going to test that replication is working.
Validate Initial Replication
First we want to validate that the objects and data created before the cluster setup, were replicated to the new nodes:
pato@patocontainer ~ $ docker exec -it patomariag1 bash
root@5f9ce8bc16d2:/# mysql
MariaDB [(none)]> use taller
Database changed
MariaDB [taller]> select * from tg1;
+----+--------------+---------------------+
| i1 | c2 | d3 |
+----+--------------+---------------------+
| 1 | 7cb4db2c49a9 | 2020-05-29 19:44:42 |
+----+--------------+---------------------+
1 row in set (0.000 sec)
pato@patocontainer ~ $ docker exec -it patomariag2 bash
root@18dda3dabdf9:/# mysql
MariaDB [(none)]> use taller
Database changed
MariaDB [taller]> select * from tg1;
+----+--------------+---------------------+
| i1 | c2 | d3 |
+----+--------------+---------------------+
| 1 | 7cb4db2c49a9 | 2020-05-29 19:44:42 |
+----+--------------+---------------------+
1 row in set (0.088 sec)
Our original data was replicated in every node, then let’s test the future data replication.
Validate Replication Node to Node
As a Galera Cluster allows every node to be updated, we are going to insert a row on each node, identifying the container that issues the insertion with @@hostname
and the time of the operation with now()
:
MariaDB [taller]> insert into tg1 (c2,d3) values (@@hostname,now());
Query OK, 1 row affected (0.398 sec)
and check the result also in every node:
pato@patocontainer ~/galera $ docker exec -it patomariagm mysql -e "select * from taller.tg1"
+----+--------------+---------------------+
| i1 | c2 | d3 |
+----+--------------+---------------------+
| 1 | 7cb4db2c49a9 | 2020-05-29 19:44:42 |
| 4 | 7cb4db2c49a9 | 2020-05-29 20:35:19 |
| 5 | 5f9ce8bc16d2 | 2020-05-29 20:36:25 |
| 6 | 18dda3dabdf9 | 2020-05-29 20:37:01 |
+----+--------------+---------------------+
pato@patocontainer ~/galera $ docker exec -it patomariag1 mysql -e "select * from taller.tg1"
+----+--------------+---------------------+
| i1 | c2 | d3 |
+----+--------------+---------------------+
| 1 | 7cb4db2c49a9 | 2020-05-29 19:44:42 |
| 4 | 7cb4db2c49a9 | 2020-05-29 20:35:19 |
| 5 | 5f9ce8bc16d2 | 2020-05-29 20:36:25 |
| 6 | 18dda3dabdf9 | 2020-05-29 20:37:01 |
+----+--------------+---------------------+
pato@patocontainer ~/galera $ docker exec -it patomariag2 mysql -e "select * from taller.tg1"
+----+--------------+---------------------+
| i1 | c2 | d3 |
+----+--------------+---------------------+
| 1 | 7cb4db2c49a9 | 2020-05-29 19:44:42 |
| 4 | 7cb4db2c49a9 | 2020-05-29 20:35:19 |
| 5 | 5f9ce8bc16d2 | 2020-05-29 20:36:25 |
| 6 | 18dda3dabdf9 | 2020-05-29 20:37:01 |
+----+--------------+---------------------+
As we can see a row was inserted en each node and data was replicated to the other nodes.
Conclusion
We were able to setup a MariaDB Galera Cluster using Docker containers inside a virtual machine from the Google Cloud Platform
and validated we can update any table in any node and those changes are replicated to every node.
This is an easy way to build a multinode cluster when you need to test some configuration or replicate an issue without dealing with a more permanent installation.