Thursday, January 31, 2008

Monitoring Swiftiply & Merb with Monit

Swiftiply combined with Merb is a pretty nice way to serve up your application. And while we hope that both are stable, it would be nice to know that just in case anything went wrong they would restart. It would also be nice for the application to automatically start upon reboot.

That is what we are going to attempt to setup using Monit.

Prerequisites:
We assume you already have Swiftiply and Merb setup and functioning properly. We are also assuming that you have Monit installed and functioning properly.

Versions:

  • Ubuntu 7.10 (Gutsy)
  • Ruby: 1.8.6
  • Merb: 0.5.2
  • Swiftiply: 0.6.1.1
  • Monit 4.8.1
  • Assumes your Merb app is "frozen" (rake merb:freeze)
Those are the version I was using at the time of the article.  Different versions may lead to varying results.

Setup Monit To Auto-Start
> sudo vim /etc/default/monit
This is a pre-existing file.  Edit as follows:
startup=1
Create init script for Swiftiply
The big problem with trying to monitor Swiftiply was that it does not write a pidfile for Monit to track.  Fortunately someone solved the problem, and Google found it.  Big credit for the script: http://pastie.textmate.org/107044.txt
> sudo vim /etc/init.d/swiftiply
This will be a new blank file.  Edit as follows:
#!/bin/bash

function generatepid(){
  current_pid=`pgrep -f 'swiftiply -c /etc/swiftiply.conf'`
  pidfile_pid=`cat /var/run/swiftiply.pid`

  if [[ $current_pid = '' ]]; then
    rm /var/run/swiftiply.pid
  elif [[ $pidfile_pid != $current_pid ]]; then
    echo $current_pid > /var/run/swiftiply.pid
  fi
}


case "$1" in

  start)
    echo -n "Starting swiftiply:"
    swiftiply -c /etc/swiftiply.conf
    generatepid
    echo
    ;;

  stop)
   echo -n "Stopping swiftiply:"
   killall -9 swiftiply
   generatepid
   echo
   ;;

  restart)
    $0 stop
    $0 start
    ;;

  pid)
    generatepid
    ;;

  *)
  echo "usage: $0 [start|stop|restart|pid]"
esac

exit 0
This does assume that you Swiftiply configuration file is location at /etc/swiftiply.conf, if not change to the correct location.

Create init Script For Your Merb App
I will use "app1" but you can replace that with whatever name you would like as an identifier for this specific Merb application.
> sudo vim /etc/init.d/merb_app1
This will be a new blank file.  Edit as follows:
#!/bin/bash

case "$1" in

  start)
    echo -n "Starting Merb for App1:"
    export SWIFT=1
    export HOME="/home/admin" #Assuming you have a user admin
    cd /full/path/to/your/app/current/
    sudo ./script/merb -d -h 127.0.0.1 -p 30000 -e production
    echo
    ;;

  stop)
    echo -n "Stopping Merb for App1:"
    cd /full/path/to/your/app/current/
    sudo ./script/stop_merb
    echo
    ;;

  restart)
    $0 stop
    $0 start
    ;;

  *)
  echo "usage: $0 [start|stop|restart]"
esac

exit 0
The -p directive should point to the same port that you have configured Swiftiply to listen to for this specific app. Also, this is assuming that you have Merb frozen. If not, then you will need to change "./script/merb" and "./script/stop_merb" to their unfrozen counterparts.

Set Permissions
Without these permissions set to execute the init scripts will not work.
> sudo chmod 755 /etc/init.d/swiftiply
> sudo chmod 755 /etc/init.d/merb_app1
Configure Your Main Monit File
> sudo vim /etc/monit/monitrc
Add the following in to the pre-existing configuration:
check process swiftiply with pidfile /var/run/swiftiply.pid
  group root
  start program = "/etc/init.d/swiftiply start"
  stop program = "/etc/init.d/swiftiply stop"
  if 5 restarts within 5 cycles then timeout

include /etc/monit/app1
Configure App1 Monit File
You could have just added the following in the main file, but I prefer to use the include statement and keep them in separate files.
> sudo vim /etc/monit/app1
This will be a new blank file. Edit as follows:
check process merb_app1  
  with pidfile /full/path/to/your/app/current/log/merb.30000.pid  
  start program = "/etc/init.d/merb_app1 start"
  stop program = "/etc/init.d/merb_app1 stop"
  if 5 restarts within 5 cycles then timeout
depends on swiftiply
30000 should be replaced with the specific port that you have Swiftiply listening on for this application. The "depends on swiftiply" will make sure that swiftiply is started first.

Does it work?
It'd take a miracle...
> sudo monit -t
> sudo /etc/init.d/monit force-reload
> sudo monit validate
> sudo monit summary
Caveats
This setup only checks to make sure the processes are up and running.
Right now Merb overwrites log/merb.port#.pid with the last mongrel process started.  

Unfortunately that means if you start 5 mongrels, you will only have the pid of the last one, and thus will only be able to monitor that one process with Monit.

On the up side, it still means you can guarantee at least one process to be up and responding to requests - which is better than none.

Update
Merb 0.9 is supposed to add the ability to specify a pidfile which will allow us to monitor multiple Merb processes even on the same port.

Useful?

Wednesday, January 30, 2008

Book Review: Programming Erlang

Author: Joe Armstrong

Erlang really appears to be an interesting language, and the author's enthusiasm for the subject shows - which is good. It helped me to keep going, when at times the code was a little hard to follow. 

The book does a good job of introducing the language. In particular later chapters give emphasis to the topics of concurrency through multiple processes, multi-cores and distributed programs. All very timely subjects for a world that is connected to the Internet, and where even laptops have multi-core processors 

Unfortunately, I feel the author somewhat forgot his audience. 

On page 5 he starts out with a description that fit me almost perfectly, and probably many other readers: "Once upon a time a programmer came across a book describing a funny programming language. It had an unfamiliar syntax [...] it wasn't even object-oriented. The programs were, well, different....Not only were the programs different, but the whole approach to programming was different." 

After reading the book I don't feel like the "Erlang Master" that the road map described (pg 9). While the syntax is now familiar, I still look at the Erlang code and it feels foreign. I still have to "decipher" the code instead of read it. 

I would have liked the book to more fully address the items from the "Once upon a time" paragraphs. Being that "the whole approach to programming" is different than the OO that many readers are used to, I would have liked to have seen a chapter (or three) on how to best get into that mode of thinking. 

I do think the book is a good jumping off point. It gives you more than enough to get started. 

However, if you are unfamiliar with languages where functions accept functions which also accept functions as parameters and in turn returns another function as a result, you may end up feeling (as I did) that you only have half the puzzle.

Sunday, January 27, 2008

Serving PHP5 with Nginx on Ubuntu 7.10 (Gutsy)

There are lots of tutorials out there to setup PHP with Nginx. I'm probably just retarded, but none of them got me to a working system. A detail left out of one, a configuration file left out of another, etc.

What follows is exactly what I did to get Nginx to serve PHP5 on a fresh Slicehost VPS slice running Ubuntu 7.10. Hopefully it will help someone else.

NOTE: The following commands assume that you have already setup a user account with sudo privileges and are executing the commands as that user, NOT as root.

Update your system and install basic tools required

> sudo aptitude update -y
> sudo locale-gen en_GB.UTF-8
> sudo /usr/sbin/update-locale LANG=en_GB.UTF-8
> sudo aptitude safe-upgrade -y
> sudo aptitude full-upgrade -y
> sudo aptitude install build-essential -y
Install MySQL
> sudo aptitude install mysql-server mysql-client libmysqlclient15-dev -y
Install PHP5
> sudo aptitude install php5-cli php5-cgi php5-mysql php5-xcache -y
Note: Xcache is installed at this point and available for you to setup, but by default is not turned on.

Install Nginx
> sudo aptitude install nginx -y
Go to your IP address and you should now recieve the message "Welcome to nginx!"

FastCGI Parameter Configuration

We will place all of our fastcgi parameters in a single file which we can include in as necessary.
> sudo vim /etc/nginx/fastcgi_params
This will be a new empty file, add the following and save:
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
# fastcgi_param  REDIRECT_STATUS    200;
Nginx Configuration
> sudo vim /etc/nginx/sites-available/default
This is a pre-existing file. Find the part that looks similar to the following and edit it as so and save:
location ~ \.php$ {
  fastcgi_pass 127.0.0.1:9000;
  fastcgi_index index.php;
  fastcgi_param SCRIPT_FILENAME /var/www/nginx-default$fastcgi_script_name;
  include /etc/nginx/fastcgi_params;
}
We need to remember to restart Nginx so that it picks up our configuration changes.
> sudo /etc/init.d/nginx stop
> sudo /etc/init.d/nginx start
Spawn-fcgi

We still need a script to start our fast cgi processes. We will extract one from Lighttpd.
> mkdir ~/sources
> cd ~/sources
> wget http://www.lighttpd.net/download/lighttpd-1.4.18.tar.bz2
> tar -xvjf lighttpd-1.4.18.tar.bz2 
> cd lighttpd-1.4.18
> ./configure
> make
> sudo cp src/spawn-fcgi /usr/bin/spawn-fcgi
Let's get automated!
> sudo touch /usr/bin/php-fastcgi
> sudo vim /usr/bin/php-fastcgi
This is a new empty file, add the following and save:
#!/bin/sh
/usr/bin/spawn-fcgi -a 127.0.0.1 -p 9000 -u www-data -f /usr/bin/php5-cgi
Next...
> sudo touch /etc/init.d/init-fastcgi
> sudo vim /etc/init.d/init-fastcgi
This is also a new empty file, add the following and save:
#!/bin/bash
PHP_SCRIPT=/usr/bin/php-fastcgi
RETVAL=0
case "$1" in
start)
$PHP_SCRIPT
RETVAL=$?
;;
stop)
killall -9 php
RETVAL=$?
;;
restart)
killall -9 php
$PHP_SCRIPT
RETVAL=$?
;;
*)
echo "Usage: php-fastcgi {start|stop|restart}"
exit 1
;;
esac
exit $RETVAL
We need to change some permissions to make this all work.
> sudo chmod 755 /usr/bin/php-fastcgi
> sudo chmod 755 /etc/init.d/init-fastcgi
Create a Test File
> sudo vim /var/www/nginx-default/index.php
Let's just print out the information page for our PHP installation.
<?php echo phpinfo(); ?>
Start It Up
> /etc/init.d/init-fastcgi start
Now go to your IP address/index.php and you should see the PHP info page displayed.  

Set To Startup Automatically Upon Reboot
> sudo update-rc.d init-fastcgi defaults
You might want to test and make sure that it actually starts up upon reboot...
> sudo reboot
Final Words
If you think I have done anything stupid in the way this is setup, you may very well be right! Please leave a comment with a suggestion on how to improve the setup.

You probably will want to look into configuring Nginx for VirtualHosts, which is not considered in this post.

Credits
Much of this information was taken from the excellent post: How To Install A Complete LEMP (Linux - EnginxX (Nginx HTTP SERVER) - Mysql - PHP) Server (Not LAMP...) On Ubuntu/Debian, which got me about 90% there.

Friday, January 18, 2008

Merb: Multiple CSS files, one server request.

In my previous post Continued Education: Performance Yahoo I referred to Steve Souder' book "High Performance Web Sites".

Rule #1 is "Make fewer HTTP requests."

Now we already know that Merb is "built for speed", but generally that is considered within the context of the server side of things.

I was happy to see that Merb also allows us to follow Souder's #1 Rule for making the client side snappy too. So how does Merb allow us to make fewer HTTP requests? By dynamically combining multiple stylesheet or script files into one.

Let's see some code!
It's this simple:


# app/views/layout/application.html.erb
<% require_css :reset %>
<% require_css :master %>
<%= include_required_css :bundle => true %>
We define the various CSS files we wish to be included using require_css (in this example reset.css and master.css), then by setting the :bundle => true paramenter everything will be served up in a single HTTP request in a file called all.css! Now that's pretty cool.

But wait there's more!
The same technique works for Javascripts too.
You can have the same functionality for your javascripts using this syntax:
<% require_js :foo $>
<%= include_required_js :bundle => true %>
But wait there's even more!
Now what is really cool is that you don't have to place your require_css or require_js statements only in the layout. You can place those in your views or even partials. That means that for any given request, the bundled file will only include the files that are needed for that specific view. Edit: It looks like you may need to manually define and uniquely name each possible combination of files. It also appears that a CSS file required in a view will appear first in the generated combined CSS file and thus be over ridden by the (likely) more general file defined in the layout - which probably isn't the desired behavior.

Getting it to work
By default the bundling action should only occur when in production mode. At the moment (Merb 0.5.2) it appears there is a bug and the bundling action doesn't realize when it is in production mode - so nothing happens.

What you need to do is edit your config/merb.yml file and uncomment the line that reads:
:bundle_assets: true
That's it! Should work. Enjoy.

One Gottcha
By forcing :bundle_assets to true in the merb.yml file, we are enabling this functionality for all environments instead of just for production. This isn't really ideal

Credits
Thanks to dudleyf in #merb for helping me figure this out. Thanks to codahale for writing the code for :bundle.

Continued Education: Performance Yahoo

In this edition of "Continued Education" I'm linking up Steve Souders' (Chief Performance Yahoo!) Google Talk "High Performance Web Sites: 14 rules for faster pages".

I've recently been reading his book that covers the same topic, but sometimes it's nice to be able to just veg-out and watch.

Good practical information that is fairly easy to implement and can greatly improve our users' experiences.