Load balanced and High Availability cluster for your web site under USD 60 pm – Part 2

Update 2009-09-02: Now I’m using a single Linode and a Xen VPS from my very own hosting service. This means the VPSes have one less thing in common; hosting company.

As I promised, here is the post that will discuss in detail how I configured my cluster of 2 nodes to host my sites.

Setting up SSH tunnels

You have to setup a SSH tunnel between the nodes. In order to do that you need to allow restricted root logins into your nodes. Using your favourite text editor edit /etc/ssh/sshd_config and change the line PermitRootLogin to PermitRootLogin forced-commands-only.

Then generate SSH authentication keys for all your nodes and add the public keys to /root/.ssh/authorized_keys on other nodes. Keys can be generated by running ssh-keygen. By default your private key is stored in /root/.ssh/id_rsa and public key in /root/.ssh/id_rsa.pub. Your public key will look similar to bellow (Key shortened for brevity)

ssh-rsa AAAA...w== [email protected]

To enable tunnel only access via root you need to add tunnel="0",command="/sbin/ifdown tun0;/sbin/ifup tun0" before your public key in /root/.ssh/authorized_keys. Your /root/.ssh/authorized_keys will look something like bellow.

tunnel='0',command='/sbin/ifdown tun0;/sbin/ifup tun0' ssh-rsa AAAA...w== [email protected]

Now setup the actual tunnel. Add following lines to /etc/network/interfaces in the “server”

auto tun0
iface tun0 inet static
address 10.100.2.1
netmask 255.255.255.0
pointopoint 10.100.2.2

and the following in the “client”

auto tun0
iface tun0 inet static
pre-up ssh -S /var/run/ssh-myvpn-tunnel-control -M -f -w 0:0 example.com true
pre-up sleep 5
address 10.100.2.2
pointopoint 10.100.2.1
netmask 255.255.255.0
up route add -net 10.100.2.0 netmask 255.255.255.0 gw 10.100.2.0 tun0
post-down ssh -S /var/run/ssh-myvpn-tunnel-control -O exit example.com

Now you only have to restart networking to enable the tunnel. Now your nodes will be in their own VPN.

Setting up document root replication (rsync)

Share /var/www via rsync. You need to install rsync and add following to /etc/rsyncd.conf if they are not already there.

max connections = 2
log file = /var/log/rsync.log
timeout = 300

[www]
comment = DOC Root
path = /var/www
read only = yes
list = yes
uid = www-data
gid = www-data
auth users = replicator
secrets file = /etc/rsyncd.secrets

Add following cron jobs to www-data crontab (crontab -e)

1/10 *  *   *   *    test -r /tmp/rsync.docroot.lock || touch /tmp/rsync.docroot.lock && rsync -aP rsync://[email protected]/www/ /var/www/ --password-file=/etc/rsync.secrets  --contimeout=30  > /dev/null 2>1 && rm /tmp/rsync.docroot.lock

1/10 *  *   *   *    test -r /tmp/rsync.docroot.lock || touch /tmp/rsync.docroot.lock && rsync -aP rsync://[email protected]/www/ /var/www/ --password-file=/etc/rsync.secrets  --contimeout=30 > /dev/null 2>1 && rm /tmp/rsync.docroot.lock

Setting up session_mysql

Next let us setup session_mysql such that we can forget about replicating PHP session 🙂 .

Install php5-dev and libmysql++-dev, download session_mysql and extract it, running following commands as root within the extracted location.

export PHP_PREFIX='/usr'
$PHP_PREFIX/bin/phpize
./configure --enable-session-mysql --with-php-config=$PHP_PREFIX/bin/php-config --with-mysql=$PHP_PREFIX
make
make install

Create the database to store the session data with the following SQL

create database phpsession;
grant all privileges on phpsession.* to phpsession identified by 'phpsession'; -- CHANGE DEFAULT PASSWORD
create table phpsession(
sess_key char(64) not null,
sess_mtime int(10) unsigned not null,
sess_host char(64) not null,
sess_val mediumblob not null,

index i_key(sess_key(6)),
index i_mtime(sess_mtime),
index i_host(sess_host)
);

Add the following to your php.ini (or /etc/php5/conf.d/session_mysql.ini)

session.save_handler = 'mysql'
session_mysql.db='host=localhost db=phpsession user=phpsession pass=phpsession'

Do not forget to change the default password. Restart Apache or Lighttpd (or any other web server you are using).

MySQL asynchronous two way replication

I’m sure some of you are asking why I went for asynchronous replication. Main reasons being flexibility and lack of nodes (My cluster is just 2 nodes).

Stop MySQL from listening only to local connections. Remember to review your user table (mysql.user) to make sure you don’t grant wild card access like 'user'@'%'. Comment out bind-address in/etc/mysql/my.cnf in all nodes. Then add following to node1

server-id               = 1
replicate-same-server-id = 0
auto-increment-increment = 2
auto-increment-offset   = 1
log_bin                 = /var/log/mysql/mysql-bin.log
expire_logs_days        = 10
max_binlog_size         = 100M

master-host = 10.100.2.2
master-user = slave_user_0
master-password = your$password
master-connect-retry = 60

and following to node2

server-id               = 2
replicate-same-server-id = 0
auto-increment-increment = 2
auto-increment-offset   = 2
log_bin                 = /var/log/mysql/mysql-bin.log
expire_logs_days        = 10
max_binlog_size         = 100M

master-host = 10.100.2.1
master-user = slave_user_1
master-password = your$password
master-connect-retry = 60

Now create the users only granting them with replication rights. Also make sure you specify the hostname or the IP to make sure someone is not offloading your data 😀 . Following SQL will create the users given in the example. You will have to run the command in both nodes as the data in either node is identical.

CREATE USER 'slave_user_1'@'10.100.2.1' IDENTIFIED BY 'your$password';

GRANT REPLICATION SLAVE ON * . * TO 'slave_user_1'@'10.100.2.1' IDENTIFIED BY 'your$password' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0 ;

CREATE USER 'slave_user_2'@'10.100.2.2' IDENTIFIED BY 'your$password';

GRANT REPLICATION SLAVE ON * . * TO 'slave_user_2'@'10.100.2.2' IDENTIFIED BY 'your$password' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0 ;

Now start MySQL and run following in mysql prompt on each of the nodes.

reset master;
stop slave;
start slave;

Finally

Now you have a cluster of 2 nodes where you can run your PHP site. Your databases are replicated, your user session data is replicated and your document root is replicated. Have fun, if you have issues please post it as a comment.

Using GNOME remotely via SSH

Have you ever wished that you had a GUI on a remote Linux server without using VNC? Actually you can use GNOME or any other GDM on a remote server via SSH, yep I’m not joking.

You need to have SSH and X11 running on both the client and the server. In addition on the server GNOME should be installed and SSH daemon should be running.

Step 1 – Turn on X11 forwarding on the server:

Add the following if it doesn’t exist or just change no to yes in /etc/ssh/ssh_config and save it.

ForwardAgent yes
ForwardX11 yes
ForwardX11Trusted yes

Add the following if it doesn’t exist or just change to yes in /etc/ssh/sshd_config and save it.

X11Forwarding yes

Step 2 – Connect to the remote server viw SSH with X11 forwarding

In order to enable X11 forwarding when you connect to a remote server via SSH you need to provide the commandline option -X. See the example bellow.

 $ ssh -X [email protected]

Step 3 – Start GNOME Session

You need to start the GNOME session for the GUI to show. By default GNOME session is not started for remote connections. It might take a while for any change to appear, you should notice GNOME startup sequence appearing in the client and couple of messages in your terminal.

However I do not recommend running X11 or GNOME on a production server, but this should be handy if you want to connect to your home computer from office for example.