How To Set Up A Load-Balanced MySQL Cluster
How To Set Up A Load-Balanced MySQL Cluster
Version 1.0 Author: Falko Timme <ft [at] falkotimme [dot] com> Last edited 03/27/2006 This tutorial shows how to configure a MySQL 5 cluster with three nodes: two storage nodes and one management node. This cluster is load-balanced by a high-availability load balancer that in fact has two nodes that use the Ultra Monkey package which provides heartbeat (for checking if the other node is still alive) and ldirectord (to split up the requests to the nodes of the MySQL cluster). In this document I use Debian Sarge for all nodes. Therefore the setup might differ a bit for other distributions. The MySQL version I use in this setup is 5.0.19. If you do not want to use MySQL 5, you can use MySQL 4.1 as well, although I haven't tested it. This howto is meant as a practical guide; it does not cover the theoretical backgrounds. They are treated in a lot of other documents in the web. This document comes without warranty of any kind! I want to say that this is not the only way of setting up such a system. There are many ways of achieving this goal but this is the way I take. I do not issue any guarantee that this will work for you!
1 My Servers
I use the following Debian servers that are all in the same network (192.168.0.x in this example):
sql1.example.com: 192.168.0.101 MySQL cluster node 1 sql2.example.com: 192.168.0.102 MySQL cluster node 2 loadb1.example.com: 192.168.0.103 Load Balancer 1 / MySQL cluster management server loadb2.example.com: 192.168.0.104 Load Balancer 2 In addition to that we need a virtual IP address : 192.168.0.105. It will be assigned to the MySQL cluster by the load balancer so that applications have a single IP address to access the cluster. Although we want to have two MySQL cluster nodes in our MySQL cluster, we still need a third node, the MySQL cluster management server, for mainly one reason: if one of the two MySQL cluster nodes fails, and the management server is not running, then the data on the two cluster nodes will become inconsistent ("split brain"). We also need it for configuring the MySQL cluster. So normally we would need five machines for our setup: 2 MySQL cluster nodes + 1 cluster management server + 2 Load Balancers = 5 As the MySQL cluster management server does not use many resources, and the system would just sit there doing nothing, we can put our first load balancer on the same machine, which saves us one machine, so we end up with four machines.
mkdir /usr/src/mysql-mgm cd /usr/src/mysql-mgm wget http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-max-5.0.19-linux-i686-\ glibc23.tar.gz/from/http://www.mirrorservice.org/sites/ftp.mysql.com/ tar xvfz mysql-max-5.0.19-linux-i686-glibc23.tar.gz cd mysql-max-5.0.19-linux-i686-glibc23
mv bin/ndb_mgm /usr/bin mv bin/ndb_mgmd /usr/bin chmod 755 /usr/bin/ndb_mg* cd /usr/src rm -rf /usr/src/mysql-mgm Next, we must create the cluster configuration file, /var/lib/mysql-cluster/config.ini: loadb1.example.com:
[NDBD DEFAULT] NoOfReplicas=2 [MYSQLD DEFAULT] [NDB_MGMD DEFAULT] [TCP DEFAULT] # Section for the cluster management node [NDB_MGMD] # IP address of the management node (this system) HostName=192.168.0.103 # Section for the storage nodes [NDBD] # IP address of the first storage node HostName=192.168.0.101 DataDir= /var/lib/mysql-cluster [NDBD] # IP address of the second storage node HostName=192.168.0.102 DataDir=/var/lib/mysql-cluster # one [MYSQLD] per storage node [MYSQLD] [MYSQLD]
Please replace the IP addresses in the file appropriately. Then we start the cluster management server: loadb1.example.com:
ndb_mgmd -f /var/lib/mysql-cluster/config.ini
It makes sense to automatically start the management server at system boot time, so we create a very simple init script and the appropriate startup links:
loadb1.example.com:
echo 'ndb_mgmd -f /var/lib/mysql-cluster/config.ini' > /etc/init.d/ndb_mgmd chmod 755 /etc/init.d/ndb_mgmd update-rc.d ndb_mgmd defaults
[mysqld] ndbcluster # IP address of the cluster management node ndb-connectstring=192.168.0.103 [mysql_cluster] # IP address of the cluster management node ndb-connectstring=192.168.0.103
Make sure you fill in the correct IP address of the MySQL cluster management server. Next we create the data directories and start the MySQL server on both cluster nodes:
sql1.example.com / sql2.example.com: mkdir /var/lib/mysql-cluster cd /var/lib/mysql-cluster ndbd --initial /etc/init.d/mysql.server start (Please note: we have to run ndbd --initial only when the start MySQL for the first time, and if /var/lib/mysqlcluster/config.ini onloadb1.example.com changes.) Now is a good time to set a password for the MySQL root user: sql1.example.com / sql2.example.com: mysqladmin -u root password yourrootsqlpassword
We want to start the cluster nodes at boot time, so we create an ndbd init script and the appropriate system startup links: sql1.example.com / sql2.example.com: echo 'ndbd' > /etc/init.d/ndbd chmod 755 /etc/init.d/ndbd update-rc.d ndbd defaults
ndb_mgm> show; Connected to Management Server at: localhost:1186 Cluster Configuration --------------------[ndbd(NDB)] 2 node(s) id=2 @192.168.0.101 (Version: 5.0.19, Nodegroup: 0, Master) id=3 @192.168.0.102 (Version: 5.0.19, Nodegroup: 0) [ndb_mgmd(MGM)] 1 node(s)
id=1 @192.168.0.103 (Version: 5.0.19) [mysqld(API)] 2 node(s) id=4 @192.168.0.101 (Version: 5.0.19) id=5 @192.168.0.102 (Version: 5.0.19) ndb_mgm>
If you see that your nodes are connected, then everything's ok! Type
quit;
to leave the ndb_mgm client console. Now we create a test database with a test table and some data on sql1.example.com: sql1.example.com: mysql -u root -p CREATE DATABASE mysqlclustertest; USE mysqlclustertest; CREATE TABLE testtable (i INT) ENGINE=NDBCLUSTER; INSERT INTO testtable () VALUES (1); SELECT * FROM testtable; quit; (Have a look at the CREATE statment: We must use ENGINE=NDBCLUSTER for all database tables that we want to get clustered! If you use another engine, then clustering will not work!) The result of the SELECT statement should be:
mysql> SELECT * FROM testtable; +------+ | i | +------+ | 1 | +------+ 1 row in set (0.03 sec)
Now we create the same database on sql2.example.com (yes, we still have to create it, but afterwards testtable and its data should be replicated tosql2.example.com because testtable uses ENGINE=NDBCLUSTER): sql2.example.com: mysql -u root -p CREATE DATABASE mysqlclustertest; USE mysqlclustertest; SELECT * FROM testtable; The SELECT statement should deliver you the same result as before on sql1.example.com:
mysql> SELECT * FROM testtable; +------+ | i | +------+ | 1 | | 2 | +------+ 2 rows in set (0.05 sec)
So both MySQL cluster nodes alwas have the same data! Now let's see what happens if we stop node 1 (sql1.example.com): Run sql1.example.com: killall ndbd
that all ndbd processes have terminated. If you still see ndbd processes, run another killall ndbd
until all ndbd processes are gone. Now let's check the cluster status on our management server (loadb1.example.com): loadb1.example.com: ndb_mgm
show;
ndb_mgm> show; Connected to Management Server at: localhost:1186 Cluster Configuration --------------------[ndbd(NDB)] 2 node(s) id=2 (not connected, accepting connect from 192.168.0.101) id=3 @192.168.0.102 (Version: 5.0.19, Nodegroup: 0, Master) [ndb_mgmd(MGM)] 1 node(s) id=1 @192.168.0.103 (Version: 5.0.19) [mysqld(API)] 2 node(s) id=4 @192.168.0.101 (Version: 5.0.19) id=5 @192.168.0.102 (Version: 5.0.19) ndb_mgm>
You see, sql1.example.com is not connected anymore. Type
quit;
to leave the ndb_mgm console. Let's check sql2.example.com: sql2.example.com: mysql -u root -p USE mysqlclustertest; SELECT * FROM testtable; quit; The result of the SELECT query should still be
mysql> SELECT * FROM testtable; +------+ | i | +------+ | 1 | | 2 | +------+ 2 rows in set (0.17 sec)
Ok, all tests went fine, so let's start our sql1.example.com node again: sql1.example.com: ndbd
ndb_mgm> shutdown; Node 3: Cluster shutdown initiated Node 2: Node shutdown completed. 2 NDB Cluster node(s) have shutdown. NDB Cluster management server shutdown. ndb_mgm>
This means that the cluster nodes sql1.example.com and sql2.example.com and also the cluster management server have shut down. Run
quit;
to leave the ndb_mgm console. To start the cluster management server, do this on loadb1.example.com: loadb1.example.com: ndb_mgmd -f /var/lib/mysql-cluster/config.ini
Afterwards, you can check on loadb1.example.com if the cluster has restarted: loadb1.example.com:
ndb_mgm
to see the current status of the cluster. It might take a few seconds after a restart until all nodes are reported as connected. Type
quit;
6.1 Install Ultra Monkey Ok, let's start: first we enable IPVS on loadb1.example.com and loadb2.example.com: loadb1.example.com / loadb2.example.com: modprobe ip_vs_dh modprobe ip_vs_ftp modprobe ip_vs modprobe ip_vs_lblc modprobe ip_vs_lblcr modprobe ip_vs_lc
modprobe ip_vs_nq modprobe ip_vs_rr modprobe ip_vs_sed modprobe ip_vs_sh modprobe ip_vs_wlc modprobe ip_vs_wrr In order to load the IPVS kernel modules at boot time, we list the modules in /etc/modules: loadb1.example.com / loadb2.example.com: vi /etc/modules
ip_vs_dh ip_vs_ftp ip_vs ip_vs_lblc ip_vs_lblcr ip_vs_lc ip_vs_nq ip_vs_rr ip_vs_sed ip_vs_sh ip_vs_wlc ip_vs_wrr
Now we edit /etc/apt/sources.list and add the Ultra Monkey repositories (don't remove the other repositories), and then we install Ultra Monkey: loadb1.example.com / loadb2.example.com: vi /etc/apt/sources.list
libsensors3 not functional It appears that your kernel is not compiled with sensors support. As a result, libsensors3 will not be functional on your system. If you want to enable it, have a look at "I2C Hardware Sensors Chip support" in your kernel configuration.
10
you can ignore it. Answer the following questions: Do you want to automatically load IPVS rules on boot? <-- No Select a daemon method. <-- none The libdbd-mysql-perl package we've just installed does not work with MySQL 5 (we use MySQL 5 on our MySQL cluster...), so we install the newestDBD::mysql Perl package: loadb1.example.com / loadb2.example.com: cd /tmp wget http://search.cpan.org/CPAN/authors/id/C/CA/CAPTTOFU/DBD-mysql-3.0002.tar.gz tar xvfz DBD-mysql-3.0002.tar.gz cd DBD-mysql-3.0002 perl Makefile.PL make make install We must enable packet forwarding: loadb1.example.com / loadb2.example.com: vi /etc/sysctl.conf
6.2 Configure heartbeat Next we configure heartbeat by creating three files (all three files must be identical on loadb1.example.comand loadb2.example.com): loadb1.example.com / loadb2.example.com: vi /etc/ha.d/ha.cf
logfacility local0 bcast eth0 mcast eth0 225.0.0.1 694 1 0 auto_failback off node loadb1 node loadb2 respawn hacluster /usr/lib/heartbeat/ipfail apiauth ipfail gid=haclient uid=hacluster
Please note: you must list the node names (in this case loadb1 and loadb2) as shown by
11
uname -n
Other than that, you don't have to change anything in the file.
vi /etc/ha.d/haresources
loadb1
You must list one of the load balancer node names (here: loadb1) and list the virtual IP address (192.168.0.105) together with the correct netmask (24) and broadcast address (192.168.0.255). If you are unsure about the correct settings, http://www.subnetmask.info/ might help you. vi /etc/ha.d/authkeys
6.3 Configure ldirectord Now we create the configuration file for ldirectord, the load balancer: loadb1.example.com / loadb2.example.com: vi /etc/ha.d/ldirectord.cf
# Global Directives checktimeout=10 checkinterval=2 autoreload=no logfile="local0" quiescent=yes virtual = 192.168.0.105:3306 service = mysql real = 192.168.0.101:3306 gate real = 192.168.0.102:3306 gate
12
checktype = negotiate login = "ldirector" passwd = "ldirectorpassword" database = "ldirectordb" request = "SELECT * FROM connectioncheck" scheduler = wrr
Please fill in the correct virtual IP address (192.168.0.105) and the correct IP addresses of your MySQL cluster nodes (192.168.0.101 and 192.168.0.102).3306 is the port that MySQL runs on by default. We also specify a MySQL user (ldirector) and password (ldirectorpassword), a database (ldirectordb) and an SQL query. ldirectord uses this information to make test requests to the MySQL cluster nodes to check if they are still available. We are going to create the ldirector database with the ldirector user in the next step. Now we create the necessary system startup links for heartbeat and remove those of ldirectord (bacause ldirectord will be started by heartbeat): loadb1.example.com / loadb2.example.com: update-rc.d -f heartbeat remove update-rc.d heartbeat start 75 2 3 4 5 . stop 05 0 1 6 . update-rc.d -f ldirectord remove
6.4 Create A Database Called ldirector Next we create the ldirector database on our MySQL cluster nodes sql1.example.com andsql2.example.com. This database will be used by our load balancers to check the availability of the MySQL cluster nodes. sql1.example.com: mysql -u root -p GRANT ALL ON ldirectordb.* TO 'ldirector'@'%' IDENTIFIED BY 'ldirectorpassword'; FLUSH PRIVILEGES; CREATE DATABASE ldirectordb; USE ldirectordb; CREATE TABLE connectioncheck (i INT) ENGINE=NDBCLUSTER; INSERT INTO connectioncheck () VALUES (1); quit; sql2.example.com: mysql -u root -p GRANT ALL ON ldirectordb.* TO 'ldirector'@'%' IDENTIFIED BY 'ldirectorpassword'; FLUSH PRIVILEGES; CREATE DATABASE ldirectordb; quit;
6.5 Prepare The MySQL Cluster Nodes For Load Balancing Finally we must configure our MySQL cluster nodes sql1.example.com and sql2.example.com to accept requests on the virtual IP address 192.168.0.105. sql1.example.com / sql2.example.com: apt-get install iproute
13
# Enable configuration of arp_ignore option net.ipv4.conf.all.arp_ignore = 1 # When an arp request is received on eth0, only respond if that address is # configured on eth0. In particular, do not respond if the address is # configured on lo net.ipv4.conf.eth0.arp_ignore = 1 # Ditto for eth1, add for all ARPing interfaces #net.ipv4.conf.eth1.arp_ignore = 1 # Enable configuration of arp_announce option net.ipv4.conf.all.arp_announce = 2 # When making an ARP request sent through eth0 Always use an address that # is configured on eth0 as the source address of the ARP request. If this # is not set, and packets are being sent out eth0 for an address that is on # lo, and an arp request is required, then the address on lo will be used. # As the source IP address of arp requests is entered into the ARP cache on # the destination, it has the effect of announcing this address. This is # not desirable in this case as adresses on lo on the real-servers should # be announced only by the linux-director. net.ipv4.conf.eth0.arp_announce = 2 # Ditto for eth1, add for all ARPing interfaces #net.ipv4.conf.eth1.arp_announce = 2
sysctl -p
Add this section for the virtual IP address to /etc/network/interfaces: sql1.example.com / sql2.example.com: vi /etc/network/interfaces
14
/etc/init.d/ldirectord stop /etc/init.d/heartbeat start If you don't see errors, you should now reboot both load balancers: loadb1.example.com / loadb2.example.com: shutdown -r now
After the reboot we can check if both load balancers work as expected : loadb1.example.com / loadb2.example.com: ip addr sh eth0
The active load balancer should list the virtual IP address (192.168.0.105):
2: eth0: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:16:3e:45:fc:f8 brd ff:ff:ff:ff:ff:ff inet 192.168.0.103/24 brd 192.168.0.255 scope global eth0 inet 192.168.0.105/24 brd 192.168.0.255 scope global secondary eth0
The hot-standby should show this:
2: eth0: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:16:3e:16:c1:4e brd ff:ff:ff:ff:ff:ff inet 192.168.0.104/24 brd 192.168.0.255 scope global eth0
loadb1.example.com / loadb2.example.com: ldirectord ldirectord.cf status
15
IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.0.105:3306 wrr -> 192.168.0.101:3306 Route 1 0 0 -> 192.168.0.102:3306 Route 1 0 0
Output on the hot-standby:
IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn
loadb1.example.com / loadb2.example.com: /etc/ha.d/resource.d/LVSSyncDaemonSwap master status
(Please note: your MySQL client must at least be of version 4.1; older versions do not work with MySQL 5.) You can now switch off one of the MySQL cluster nodes for test purposes; you should then still be able to connect to the MySQL database.
8 Annotations
There are some important things to keep in mind when running a MySQL cluster: - All data is stored in RAM! Therefore you need lots of RAM on your cluster nodes. The formula how much RAM you need on ech node goes like this: (SizeofDatabase NumberOfReplicas 1.1 ) / NumberOfDataNodes
16
So if you have a database that is 1 GB of size, you would need 1.1 GB RAM on each node! - The cluster management node listens on port 1186, and anyone can connect. So that's definitely not secure, and therefore you should run your cluster in an isolated private network! It's a good idea to have a look at the MySQL Cluster FAQ: http://dev.mysql.com/doc/refman/5.0/en/mysql-clusterfaq.html and also at the MySQL Cluster documentation: http://dev.mysql.com/doc/refman/5.0/en/ndbcluster.html
Links
MySQL: http://www.mysql.com/ MySQL Cluster documentation: http://dev.mysql.com/doc/refman/5.0/en/ndbcluster.html MySQL Cluster FAQ: http://dev.mysql.com/doc/refman/5.0/en/mysql-cluster-faq.html Ultra Monkey: http://www.ultramonkey.org/ The High-Availability Linux Project: http://www.linux-ha.org/
17