In my prior article, “Running a relational database with Docker”, I showed how to spin up a Docker container with an instance of MySQL running in it. I also showed how you can persist data even when you blow away the container and recreate it by connecting to a Volume or Bind Mount.
In this article I am going to extend this to show you how to use Replication with MySQL instances in Docker Containers.
Replication Basics
For a relational database like MySQL, you
can achieve high availability even when using it inside of Docker containers. While this is mostly common in production environments like standalone servers and virtual machines, you can do the same with Docker, however with a different setup.
For standalone servers or virtual machines you will typically set MySQL replication by manually configuring the my.cnf file; here’s my article on the SimpleTalk blog that describes this in detail. But MySQL replication for Docker is done using environment variables, Docker networks and volumes to persist data.
Replication involves syncing data between a primary database which is also referred to as master and one or more secondary databases (replicas) also referred to as slaves. For the master and slave(s) to communicate, the master logs all changes to the binary log and the slave(s) reads and applies those changes.
This means that read operations are distributed across replicas while only the master handles all write operations. Each MySQL instance will have a unique identifier, server-id=1
and server-id=2
, server-id=3
and the master must have binary logging enabled to allow all records or changes to the database in a binary format so that slaves read from these logs and apply the changes.
In our use case we will spin up the master MySQL container first and then configure it for replication. After that, we’ll start a slave MySQL container that will sync data from the master. Keep in mind that, the master and its replicas should communicate over the same Docker network and then use a shared replication user for authentication and secure data transfer.
Configuring Replication in Containers
To begin, remove your already existing MySQL container:
1 |
docker rm <container-id> -f |
We already have a Docker network mysql-net
created; use it to start the MySQL master container:
1 2 3 4 5 6 7 8 9 10 11 12 |
docker run -d --name mysql-master \ --network=mysql-net \ -e MYSQL_ROOT_PASSWORD=superadminuser \ -e MYSQL_USER=admin \ -e MYSQL_PASSWORD=admin123 \ -e MYSQL_DATABASE=testdb \ -v mysql-master-data:/var/lib/mysql \ mysql \ --server-id=1 \ --log-bin=mysql-bin \ --binlog-do-db=testdb \ --binlog-format=row |
From the command above we are running the master container with the following configuration settings:
- –network=mysql-net: Connect the master container to an already existing Docker network named mysql-net.
- -e MYSQL_ROOT_PASSWORD=superadminuser: Set the root password for master to superadminpassword.
- -e MYSQL_USER=admin: Create a new user named admin.
- -e MYSQL_PASSWORD=admin123: Set the password for admin to admin123.
- -e MYSQL_DATABASE=testdb: Initialize a database with the name testdb if the MySQL data directory is empty when it starts for the first time.
- -v mysql-master-data:/var/lib/mysql: Create and mount a Docker volume named mysql-master-data on the go to persist data.
- mysql: Specify the MySQL image to use, which is the latest version in this case.
And then, pass several MySQL specific configuration options for replication:
- –server-id=1: Assigns a unique server ID to the master.
- –log-bin=mysql-bin: Enable binary logging which is necessary for replication. With binary logs enabled, changes to the specifiied databases can be recorded in binary logs which can be sent to replica containers.
- –binlog-do-db=testdb: Configures the MySQL master container to only include changes to the testdb database in the binary logs, thus recording only changes made to the testdb by the replicated slave containers or servers.
- –binlog-format=row: Sets the binary log to record changes at the row level rather than at the statement level. This format is useful for reliable replication, especially when dealing with complex transactions.
Next, confirm that the MySQL container is running;
1 |
docker ps |
Once it is running, we must create a replication user with REPLICATION SLAVE privileges that the slave containers will use to connect and sync data from the master’s binary logs.
Connect to the MySQL shell inside the master container and sign in as root:
1 |
docker exec -it mysql-master mysql -u root -p |
Execute the following SQL commands one by one to create a replication user:
1 2 3 |
CREATE USER 'jane'@'%' IDENTIFIED BY 'jane123' REQUIRE SSL; GRANT REPLICATION SLAVE ON *.* TO 'jane'@'%'; |
From the above command, the replication user is jane, and ‘%’ allows connections from any host/IP address, unlike a very restrictive user definition jane’@’%.example.com which restricts access to hosts within the example.com domain. The follow up command - IDENTIFIED BY 'jane123';
means jane123
is the assigned password for the user jane.
For the other statement – GRANT REPLICATION SLAVE ON *.* TO 'jane'@'%';
we are simply telling MySQL to grant the REPLICATION SLAVE
privilege to the user jane
; which applies immediately.
Display the status of the MySQL binary log with the below command:
1 |
SHOW BINARY LOG STATUS; |
You should see the following output:
1 2 3 4 5 6 |
+------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +------------------+----------+--------------+------------------+-------------------+ | mysql-bin.000003 | 3172 | testdb | | | +------------------+----------+--------------+------------------+-------------------+ 1 row in set (0.00 sec) |
From the output above the position is 3172
and the Binary_Do_DB
is set to testdb
which is what we want. The position 3172
is a binary position within the binary log where the next event should be read by the slave(s). This implies that the slave containers should start reading the binary log file mysql-bin.000003
from position 3172
.
The other parameters Binlog_Ignore_DB
and Executed_Gtid_Set specifies which database should not be logged in the binary log and tracks the GTID
s of executed transactions respectively. Since these parameters were not set, they are empty, no databases are explicitly ignored, and no GTID
transactions have been recorded.
Now start a MySQL slave container:
1 2 3 4 5 6 7 8 9 10 |
docker run -d --name mysql-slave \ --network=mysql-net \ -e MYSQL_ROOT_PASSWORD=superadminuser \ -e MYSQL_USER=john \ -e MYSQL_PASSWORD=john123 \ -e MYSQL_DATABASE=mydb \ -v mysql-slave-data:/var/lib/mysql \ mysql \ --server-id=2 \ --relay-log=mysql-relay-bin |
This will do the following:
- Create a MySQL container with the name
mysql-slave
. - Use the docker network
mysql-net
. - Use
superadminpassword
as theMYSQL_ROOT_PASSWORD
, this can be anything and doesn’t need to be the same as the master. - Create a user (
MYSQL_USER
), password (MYSQL_PASSWORD
) and database (MYSQL_DATABASE
) with the following values –john, john123
andmydb
respectively. Again, the values do not have to be the same as the master. - Create a Docker volume on the go called
mysql-slave-data
and mount it on/var/lib/mysql
which is where database files are stored in MySQL. - Assign the server ID of 2.
- Enable relay logs for storing changes received from the master in the file
mysql-relay-bin
before applying them.
Confirm the slave container is running:
1 |
docker ps |
Open a shell in the slave container as the root user:
1 |
docker exec -it mysql-slave mysql -u root -p |
Configure the replica with the following command:
1 2 3 4 |
CHANGE REPLICATION SOURCE TO SOURCE_HOST='mysql-master', SOURCE_LOG_FILE='mysql-bin.000003', SOURCE_LOG_POS=3172, |
Here’s what each part of the statement above does:
CHANGE REPLICATION SOURCE
TO configures the replica with information about where to start the replication from, which master to connect to, which log file to use, and at which position it should begin reading the binary log events.SOURCE_HOST
specifies the MySQL master container using the container namemysql-master
.SOURCE_LOG_FILE
defines the binary log file on the master which the slave (replica) container should start reading from.SOURCE_LOG_POS
specifies the exact position within the master’s binary file where replication should begin. The byte offset `3172
` is usually the starting point for replication.
Next, initiate the replication process by allowing the slave (replica) container to start reading and applying changes from the master’s binary logs:
1 |
START REPLICA USER='jane' PASSWORD='jane123'; |
Verify that the replication is done successfully and that it is working correctly:
1 |
SHOW REPLICA STATUS\G |
If you have an output similar to the one below, the replication is set:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
*************************** 1. row *************************** Replica_IO_State: Connecting to source Source_Host: mysql-master Source_User: jane Source_Port: 3306 Connect_Retry: 60 Source_Log_File: mysql-bin.000003 Read_Source_Log_Pos: 3172 Relay_Log_File: mysql-relay-bin.000001 Relay_Log_Pos: 4 Relay_Source_Log_File: mysql-bin.000003 Replica_IO_Running: Connecting Replica_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Source_Log_Pos: 3172 Relay_Log_Space: 158 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Source_SSL_Allowed: No Source_SSL_CA_File: Source_SSL_CA_Path: Source_SSL_Cert: Source_SSL_Cipher: Source_SSL_Key: Seconds_Behind_Source: NULL Source_SSL_Verify_Server_Cert: No Last_IO_Errno: 2061 Last_IO_Error: Error connecting to source 'jane@mysql-master:3306'. This was attempt 2/10, with a delay of 60 seconds between attempts. Message: Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection. Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Source_Server_Id: 0 Source_UUID: Source_Info_File: mysql.slave_master_info SQL_Delay: 0 SQL_Remaining_Delay: NULL Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates Source_Retry_Count: 10 Source_Bind: Last_IO_Error_Timestamp: 250318 15:03:31 Last_SQL_Error_Timestamp: Source_SSL_Crl: Source_SSL_Crlpath: Retrieved_Gtid_Set: Executed_Gtid_Set: Auto_Position: 0 Replicate_Rewrite_DB: Channel_Name: Source_TLS_Version: Source_public_key_path: Get_Source_public_key: 0 Network_Namespace: 1 row in set (0.00 sec) |
From the above output:
Replica_IO_State
means the replica is caught up with the master and is waiting to receive new changes.Replica_IO_Running
means the replica is successfully reading changes from the master.Seconds_Behind_Source
means there is no lagging and the replica is in sync fully with the master.Last_IO_Error
andLast_SQL_Error
means there are no errors in the replication process.
Testing the Replication Configuration
We have successfully set replication to apply changes from the master MySQL container. To test out our setup, we’ll need to insert a test record into the testdb
database on the mysql-master container
. We are using the testdb database because it is the one explicitly included in the master’s binary logs via the binlog-do-db setting.
Access the MySQL shell in the master container:
1 |
docker exec -it mysql-master mysql -u root -p |
Switch to the testdb
database:
1 |
USE testdb; |
Create a table users
and insert a record:
1 2 3 4 5 |
CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL UNIQUE ); |
1 |
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'); |
Now confirm that the data has been replicated on the mysql-slave container:
1 2 3 |
USE testdb; SELECT * FROM users; |
If the replication is working correctly you should have the new row outputted to you like so:
1 2 3 4 5 6 |
+----+-------+-------------------+ | id | name | email | +----+-------+-------------------+ | 1 | Alice | alice@example.com | +----+-------+-------------------+ 1 row in set (0.00 sec) |
Conclusion
Through this article, you now have an understanding of how you can configure MySQL replication between Docker containers. Whether you want to run MySQL using Docker for testing purposes or preparing for production you now have all the necessary configuration steps at your fingertips.
Load comments