Friday, 11 April 2008

Sponsor me! - Manchester 10k

I wouldn't normally post OT on my blog but this is for a good cause.

I'm running the Manchester 10k on the 18th of May and am trying to raise £200 for the Rainbow Family Trust (Francis House) who support and improve the quality of life for terminally ill children diagnosed with 'no cure'.

So dig deep and help me reach my target by going to this site and making a donation: http://www.justgiving.com/roseandrew

Many thanks!
Andrew

Tuesday, 5 February 2008

Roll your own N-server sandbox

Have you ever wanted to play around with(test ;) MySQL replication/clustering techniques, LVS/Apache load balancing etc but didn't have the hardware available and where smart enough not to use a production environment?

Well an easy way to be able to do this is by creating a sandbox environment using something like qemu, xen, vmware or UML which allow you to create virtual machines running inside a protected environment on your own desktop machine or whatever hardware you have spare, just be sure you have enough memory. This article will cover setting up a sandbox using UML.

My GNU distro of choice is Archlinux and this article will be based around it, but you should be able to take the information provided here and apply it relatively easily to which ever distro you use. Please also note that I am not going to go into depth about UML as there are plenty of resources just a search away.


Preparing the host.

So first things first we need to have a system capable of running UML, and luckily Archlinux is ready, all we need to do is install a few packages:

[root@ishell ~]# pacman -S user-mode-linux uml_utilities

Then setup our play area. In this example I am setting up a 4 server playground in roots home directory.

[root@ishell ~]# mkdir servers
[root@ishell ~]# cd servers
[root@ishell servers]# mkdir 1 2 3 4
[root@ishell servers]# ls
1 2 3 4
[root@ishell servers]#

Setup the host (the host is the machine "hosting" the UML instances) for networking the UMLs. There are all kinds of ways to do this but I use the uml_switch which is a virtual network switch that is a breeze to use. To allow the UMLs to communicate to and from the host and the outside world we need to create a tunnel and setup IP forwarding (We will start the actual uml_switch at the end in a startup script):

[root@ishell ~]# modprobe tun
[root@ishell ~]# tunctl -u root -t tap0
[root@ishell ~]# ifconfig tap0 192.168.0.100 up
[root@ishell ~]# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
[root@ishell ~]# echo 1 > /proc/sys/net/ipv4/ip_forward


Setup of the UML filesystem

The UML instances require a file to hold their filesystem and a base GNU install. So we create one with dd. As ever salt to your tastes:

[root@ishell servers]# dd if=/dev/zero of=root.fs bs=1MB count=1000
[root@ishell servers]# mkfs.ext3 root.fs

Now we have formatted image we can loopback mount it ready for setting up the base install.

[root@ishell servers]# mount -o loop root.fs /mnt/cd/
[root@ishell servers]# mkdir -p /mnt/cd/var/lib/pacman

To install the base packages with Archlinux I wget the packages.txt from the main package download area which has a list of all base packages prefixed with base/. To install the base packages to the disk image which we have mounted to /mnt/cd we need to first initialize pacman with the -Sy --dbpath options then instruct pacman to install packages to our /mnt/cd root with the --dbpath and --root options. In this case I am also including apache, php, mysql and openssh.


[root@ishell servers]# wget ftp://ftp.archlinux.org/current/os/x86_64/packages.txt
[root@ishell servers]# pacman -Sy --dbpath /mnt/cd/var/lib/pacman
[root@ishell servers]# pacman -S --dbpath /mnt/cd/var/lib/pacman --root /mnt/cd/ \
`cat packages.txt | egrep '^base/' | sed 's|^.*/||;s/-[0-9]\+.*//' | grep -v kernel` \
apache mysql php openssh


Once we have the base install we need to modify some files. These changes apply to all servers we use this image for so it will save us some time if we make the changes now before making copies of the image.

  • add "/dev/ubd0 / ext3 defaults 0 0" to etc/fstab
  • edit /etc/hosts.deny and comment out ALL:ALL so we can gain ssh access
  • edit /etc/innittab and comment out all but the first virtual console
  • add sshd to daemons in rc.conf
  • change default gateway to 192.168.0.100 (or what you set the tunnel IP to)
  • chroot to /mnt/cd and change the root password other wise you will not be able to gain ssh access.

Now umount the image and copy to all servers:

[root@ishell servers]# umount /mnt/cd
[root@ishell servers]# cp root.fs 1/ &
[root@ishell servers]# cp root.fs 2/ &
[root@ishell servers]# cp root.fs 3/ &
[root@ishell servers]# cp root.fs 4/ &

Mount each root image in turn and make configuration changes to the eth0 ip address and hostname in rc.conf, these have to be unique for each server and are the IPs you will use to access the servers via ssh:

[root@ishell servers]# mount -o loop 1/root.fs /mnt/cd/
[root@ishell servers]# nano /mnt/cd/etc/rc.conf
[root@ishell servers]# umount /mnt/cd


And the finishing touch

Create a startup script (i.e. run.sh) and modify the mem parameter to suit your needs. ubd0 specifies the file that contains the filesystem the UML will use and con=null tell the UML not to spawn any consoles or direct any output from the kernel to the console as we will be using ssh as the main form of access, however you can change this if you wish.

The uml_switch is also started here and is passed the name of the tunnel device we setup earlier and told to fork off into a daemon.

#!/bin/bash
exec uml_switch -tap tap0 -daemon &
exec linux ubd0=1/root.fs con=null eth0=daemon mem=128M &
exec linux ubd0=2/root.fs con=null eth0=daemon mem=128M &
exec linux ubd0=3/root.fs con=null eth0=daemon mem=128M &
exec linux ubd0=4/root.fs con=null eth0=daemon mem=128M &


Final thoughts

After running the above script you should have 4 servers ready to go within a few minutes. Simply ssh to them as you would any other server and have fun!

Friday, 1 February 2008

PHP: Getting signals through to blocking socket_accept

Have you ever written a sockets server in PHP and wanted to use the blocking option but found you where unable to kill the process off cleanly and instead had to resort to kill -9 or even worse set blocking to false and set a delay in the main event loop?

Well there is a way to use blocking and be able to shutdown the process cleanly. socket_accept() blocking may ignore general signals, but it does not ignore pcntl alarms. An alarm fired every second or so will break you out of a blocking call to socket_accept() (with a warning) and allow your normal flow of logic to detect a shutdown signal i.e. sigterm.


Example code



<?php
declare(ticks = 1);

class test
{
private $shutdown = FALSE;

function sigTerm()
{
$this->shutdown = TRUE;
}

function sigAlarm()
{
pcntl_alarm(1);
}

public function doit()
{
$socket = socket_create(AF_INET, SOCK_STREAM, 0);
socket_bind($socket,'127.0.0.1', 5555);
socket_listen($socket);

socket_set_block($socket);

pcntl_signal(SIGTERM, array(&$this, "sigTerm"));
pcntl_signal(SIGALRM, array(&$this, "sigAlarm"));
pcntl_alarm(1);

while(1)
{
if($c = @socket_accept($socket))
{
echo "Got a connection\n";
socket_close($c);
}
else
{
echo "Alarm has broken us out of socket_accept blocking, ";

if($this->shutdown)
{
echo "and it is time to shutdown now Dave.\n";
return;
}

echo "but it is not time to shutdown yet Dave.\n";
}
}
}
}

$test = new test;
$test->doit();



And Finally

To test the code simply telnet 127.0.0.1 5555 to see it handling connections. Then when you're done a simple killall php should do the trick (just be sure you don't kill any other php process).

I should point out that socket_stream_accept() will respond to signals without the need for an alarm. So if you can, it may be worth going with the stream functions over the cardinal socket functions. (Edit: 04/02/2008) However the stream function response to signals seems a little erratic so I would use an alarm anyway.


UPDATE - 5th March 2008

I was stung by a nasty little oversight that you should know. When reading from a socket, any interrupt (read: alarm) will cause the read to abort. To solve this, after successfully establishing a connection with socket_accept() be sure to run pcntl_alarm(0) then socket_read() and then pcntl_alarm(1) again.

Wednesday, 30 January 2008

LAMP: The mailing lists you *SHOULD* be subscribed to.

The LAMP stack consists of some complicated software, and this software from time to time will develop faults and security flaws. How do you keep yourself informed? Hope that the issues crop up on Digg, Slashdot? Well the best way is to join the Announce lists for each of the LAMP stack components.

The Announce lists are used by the developers of the different components of the LAMP stack to keep users informed of important events, like when a security flaw emerges or a new version of there software is released, etc.


The lists

The first one is dependent on the GNU/Linux distribution your running, in my case it is Archlinux, so I've subscribed to:


Next in the LAMP stack is Apache, and you can find its Announce list here:


For MySQL:


And PHP:


And lastly, considering that SSH is the primary form of entry to my systems (and most likely yours) I am subscribed to the OpenSSL and OpenSSH mailing lists:



Final points

Now, subscribing to these lists in no way secures you from attack, but it should give you a fighting chance. When bugs / security issues do crop up you will need to take the necessary action, which should be as simple as upgrading the relevant software. But be prepared to build replacement packages from source if your GNU/Linux distribution is slow to release an updated package.

Tuesday, 8 January 2008

MySQL (5.0): Recovering failed circular replication

There is a lot of information out there about how to setup circular replication but nothing about how to recover it when all else fails. This article will cover a quick and easy method I use. Depending on the size of your database and the interconnects between servers this method may not be suitable due to the need to copy all replicated databases from one good server to all other servers in the replication circle which requires a certain amount of down time respectively.

OK, so one server or more is showing Slave_IO_Running and/or Slave_SQL_Running as No and there is some error about a failed query when you run "show slave status;" and no amount of effort to fix it is working. First DO NOT PANIC. It is broken, OK, tell yourself that and realise that trying to fix something when you are in a panicked state is only liable to make the situation worse, hell I'd guarantee it, so relax.


The checklist

Below is our checklist of things to do, with more detailed information about each step afterward.
  1. Make sure ALL connections to MySQL are closed
  2. Back everything up (/var/lib/mysql)
  3. Pick the most up-to-date database
  4. Run stop slave; on all servers
  5. Run reset master; and reset slave; on all servers
  6. Shutdown all servers (e.g. /etc/rc.d/mysqld stop)
  7. Copy database decided on from step 3; files/ibdata etc to other servers
  8. Start servers (e.g. /etc/rc.d/mysqld start)
  9. Run start slave; on all servers

Step 1 - Make sure ALL connections to MySQL are closed

If you have anything still connected to MySQL when your fixing replication your going to find your worse off than where you started. In an ideal world you would know EXACTLY what is connected to your MySQL server. But if in doubt, or even better if you like to second check then fire up the MySQL client on all servers and run show full processlist; all you want to see there is rows with system user and one for your connected client which should show show full processlist in the Info column. If there is anything else, get rid before proceeding to Step 2.

Don't forget things like Apache running PHP scripts and PHP scripts under cron control for example which may be waiting to start a new connection without your knowledge.

An easy way to be sure nothing has modified your database is to run show master status; and note down the values of the File and Position columns. As you progress though the recovery check these values now and then to be sure they have not changed. The only time they should change is after step 8.


Step 2 - Back everything up (/var/lib/mysql)

This should be obvious, but as a reminder I've put it in. Before I fix anything in the way that is laid out in this article I make a copy of the MySQL data directory by simply doing (You may want to stop the MySQL server before doing this.):

cp -a /var/lib/mysql /var/lib/mysql-[yyyymmdd].

This will allow me to move the old directory back into play (after the whole procedure obviously) by:
  1. Stopping the MySQL server
  2. mv /var/lib/mysql /var/lib/mysql-tmp
  3. mv /var/lib/mysql-[yyyymmdd] /var/lib/mysql
  4. Starting MySQL server

You may need to do this to check data in the old version of the database. I would NOT start slave; if you do this it will most likely break replication in the other servers and you won't be able to switch back to the new database and your have to start all over again :(


Step 3 - Pick the most up-to-date database

When replication fails beyond repair you need to select a server who's database to use. This is completely your call. However if it is one server that has hiccuped use its masters database as every node after the failed server would have it's events replicated all the way around to its master.

At this point it may be a good idea to get a list of the databases that you are replicating. I use a quick call to show master status; to get a list of databases in usable format. I.e. db0,db1,db2 in the Binlog_Do_Db column. We will use this in step 7.


Step 4 - Run stop slave; on all servers

This step is pretty self explanatory. We stop the slave process from running in preparation for the next step. We "should" get away without stopping the slave process I'll admit, but it's better to be safe than sorry.


Step 5 - Run reset master; and reset slave; on all servers

This step purges bin logs and resets bin log position counters (relay-log.info and master.info) for the master and slave. This way when we copy our best database to all other servers they will all be at a common starting point.


Step 6 - Shutdown all servers (e.g. /etc/rc.d/mysqld stop)

Pretty simply stop all servers. We do this because the next step involves copying from one server to all other servers, so we don't want MySQL modifying anything mid copy.


Step 7 - Copy database decided on from step 3; files/ibdata etc to other servers

Now we need to copy the database you chose in step 3 to all other servers.

I use scp for this like so (salt to your taste):

scp -r /var/lib/mysql/{db0,db1,db2} /var/lib/mysql/ib* server1:/var/lib/mysql &

Note, if you do not replicate any InnoDB tables remove the /var/lib/mysql/ib* bit. I also background the process just in case I lose my ssh connection (i.e. to the dreaded felis silvestris catus bug), it also means you can run scp for each server from the same console. To check up on the scp processes I simply use watch "ps aux | grep scp". When all processes are gone from the list your good to go to step 8.


Step 8 - Start servers (e.g. /etc/rc.d/mysqld start)

Start the servers. This can be done in any order you feel like doing it in.


Step 9 - Run start slave; on all servers

You may have MySQL setup to start the slave automatically, if not however now is the time to start the slave processes on all servers. Give the servers time to get going as they have by default 60 second timeouts so may not connect to there master straight away, especially if you haven't actually started it before hand. If your impatient however simple run stop slave; and start slave; on each server going from master to slave around the circle.


Final thoughts

OK so that may have seemed a bit long winded, but I'll guarantee that after doing it once successfully your see it for the relatively simple process it is. Off course it doesn't help when your expected to keep 100% up time.

I never explained how to restore around a failed node which is a common problem. I thought it would simply confuse things to much even though it is a pretty simple thing to slot in around step 6. I will follow up with another blog entry to cover it another day. If you want it sooner feel free to poke me via email.

Well I hope all that helps someone, especially the lads in my team which should now have no excuse for dodging the night watch ;)