Sunday, August 14, 2011

Running a Rack app (Ramaze) with Nginx/Unicorn on Ubuntu (11.04)

I want to setup a Rack app on a virtual host on my Ubuntu 11.04 for my tiny intranet. I am already running multiple virtual hosts (www.domain1.com and www.domain2.com) on my Ubuntu. This time, I want to add another virtual host (www.domain3.com) that invokes a Rack application.



1. Requirements.

Ubuntu - a Linux distribution
Ruby - a scripting language
Nginx - a web server that handles multiple virtual hosts
Unicorn - web server to be used to runs behind Nginx
Ramaze - a Rack web framework ("Mini Rails")


2. Resources.

Nginx: http://wiki.nginx.org/
Unicorn: http://unicorn.bogomips.org/
Ramaze: http://ramaze.net/
Silpsen: Setting up Unicorn with Nginx
slicehost: Ubuntu Intrepid - Nginx, rails and mongrels
slicehost: Ubuntu Intrepid - Nginx, rails and thin
Tom Kersten: Setting up Ubuntu with Nginx, Unicorn, ree, rv
Maxime Rousseaux-Bridle blog: Running Ramaze with Unicorn, Nginx and God


3. Create a virtual host.
My ultimate goal is to create a virtual host (www.domain3.com) to run my Rack app (Ramaze). But first I create a normal virtual host (www.domain3.com), without running any web app. Later I will create a web app, which will take over the control of incoming requests.

The first step to create a virtual host is to crate a directory structure and place an index.html file in its public folder.
$ mkdir ~/public_html/domain3.com
$ mkdir ~/public_html/domain3.com/{public,private,log,backup}
$ gedit ~/public_html/domain3.com/public/index.html   # create home index.html file
It's content looks like this:
<html>
  <head>
    <title>domain3.com</title>
  </head>
  <body>
    <h1>domain3.com</h1>
  </body>
</html>
Then I create a normal virtual host file for Nginx. Later I will modify it to redirect its requests to my Rack (Ramaze) app.
$ sudo gedit /etc/nginx/sites-available/domain3.com
Here is it's content.
server {
            listen   80;
            server_name  www.domain3.com;
            rewrite ^/(.*) http://domain3.com/$1 permanent;
           }

server {
            listen   80;
            server_name domain3.com;

            access_log /home/socrateos/public_html/domain3.com/log/access.log;
            error_log /home/socrateos/public_html/domain3.com/log/error.log;

            location / {
                        root   /home/socrateos/public_html/domain3.com/public/;
                        index  index.html;
                        }
            }
Now enable the host, by creating a symlink file into /etc/nginx/sites-enabled directory.
$ sudo ln -s /etc/nginx/sites-available/domain3.com /etc/nginx/sites-enabled/domain3.com
Finally update the /etc/hosts file by adding a new domain name. (My tiny intranet does not need/use DNS.) I just manually edit hosts file in each PC.
127.0.1.1 domain1.com
127.0.1.1 www.domain1.com
127.0.1.1 domain2.com
127.0.1.1 www.domain2.com
127.0.1.1 domain3.com          # new domain added to be used for Ramaze app
127.0.1.1 www.domain3.com      # new domain added to be used for Ramaze app
I added similar entries to C:\Windows\System32\drivers\etc\hosts on my Windows Visita.
192.168.12.13 domain1.com
192.168.12.13 www.domain1.com
192.168.12.13 domain2.com
192.168.12.13 www.domain2.com  
192.168.12.13 domain3.com      # new domain added to be used for Ramaze app
192.168.12.13 www.domain3.com  # new domain added to be used for Ramaze app
Restart my Nginx server.
$ sudo /etc/init.d/nginx stop
Stopping nginx: nginx.
$ sudo /etc/init.d/nginx start
Starting nginx: nginx.
Now test http://www.domain3.com with my browser.
The result looks like this as expected. So my new virtual host is working.
















4. Create a Rack App (Ramaze)

I now build a Rack (Ramaze) app. Then do a quick test run with Unicorn server, without going through Nginx.
$ cd ~/public_html/      
$ mkdir apps/          # a parent directory for all my web apps
$ cd apps/
$ ramaze create app1   # create a Ramaze app
The application has been generated and saved in app1
$ cd app1/
$ mkdir tmp            
$ mkdir tmp/pids       # for unicorn master pid
$ mkdir log            # for nginx and unicorn logs
$ unicorn              # a quick test run with unicorn server
I, [2011-08-08T00:08:11.149051 #3759]  INFO -- : listening on addr=0.0.0.0:8080 fd=3
I, [2011-08-08T00:08:11.149263 #3759]  INFO -- : worker=0 spawning...
I, [2011-08-08T00:08:11.149838 #3759]  INFO -- : master process ready
I, [2011-08-08T00:08:11.150249 #3761]  INFO -- : worker=0 spawned pid=3761
I, [2011-08-08T00:08:11.150372 #3761]  INFO -- : Refreshing Gem list
I, [2011-08-08T00:08:11.480209 #3761]  INFO -- : worker=0 ready
Here is the result from http://localhost:8080, directly accessing Unicorn server. Later, after I setup Unicorn to run behind Nginx, I will access our app from http://www.domain3.com, where my nginx server transfers control to Unicorn server, which actually serves our web app.


















5. Make Unicorn run behind Nginx

At this point, I have two web servers running independently: Nginx (for a few virtual domains) at port 80 and Unicorn (for Ramaze app) at port 8080. I am now going to modify the configuration of one of nginx's virtual domains (domain3.com) so that Ngix will transfer control to Unicorn if web requests are for this the domain. In other words, Unicorn will be working as a server behind the Nginx server.
sudo gedit /etc/nginx/sites-available/domain3.com    # open the domain3.com (conf) file for editing.
Here is the modified contents. This is based on slicehost's "Ubuntu Intrepid - Nginx, rails and thin".
upstream backend {
        server 127.0.0.1:8080;
    }

server {
            listen   80;
            server_name  www.domain3.com;
            rewrite ^/(.*) http://domain3.com/$1 permanent;
           }


server {
            listen   80;
            server_name domain3.com;

            access_log /home/socrateos/public_html/apps/app1/log/access.log;
            error_log /home/socrateos/public_html/apps/app1/log/error.log;

            root   /home/socrateos/public_html/apps/app1/public/;
            index  index.html;

            location / {
                          proxy_set_header  X-Real-IP  $remote_addr;
                          proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
                          proxy_set_header Host $http_host;
                          proxy_redirect off;

                          if (-f $request_filename/index.html) {
                                           rewrite (.*) $1/index.html break;
                          }

                          if (-f $request_filename.html) {
                                           rewrite (.*) $1.html break;
                          }

                          if (!-f $request_filename) {
                                           proxy_pass http://backend;
                                           break;
                          }
            }

}

Restart Nginx.
$ sudo /etc/init.d/nginx stop
Stopping nginx: nginx.
$ sudo /etc/init.d/nginx status
 * could not access PID file for nginx
$ sudo /etc/init.d/nginx start
Starting nginx: nginx.
Then go to http://www.domain3.com. And Voila! We are now seeing a Rack app running under Unicorn server behind a Nginx web server.

















6. Daemonize Unicorn


The last step is to run Unicorn as a daemon and start it when Ubuntu starts up. To do this, first, I create a new conf file for Unicorn. I will name it unicorn.conf and place it in app's root directory.
gedit ~/public_html/apps/app1/unicorn.conf
The following is its contents. It is based on http://unicorn.bogomips.org/examples/unicorn.conf.rb and "Running Ramaze with Unicorn, Nginx and God" by Maxime Rousseaux-Bridle. For a detailed information, see Unicorn::Configurator.

# unicorn.conf
# (ruby code)
# Based on the following examples: 
# http://unicorn.bogomips.org/examples/unicorn.conf.rb
# http://blog.xambr.com/2010/01/16/running-ramaze-with-unicorn-nginx-and-god/


APP_ROOT = "/home/socrateos/public_html/apps/app1"

worker_processes 4
working_directory APP_ROOT

# listen on both a Unix domain socket and a TCP port
listen "{APP_ROOT}/tmp/unicorn.sock", :backlog => 64
listen 8080, :tcp_nopush => true

# Location of master process PID file
pid "#{APP_ROOT}/tmp/pids/unicorn-master.pid"

# Location of stderr/stdout logs
stderr_path "#{APP_ROOT}/log/unicorn.stderr.log"
stdout_path "#{APP_ROOT}/log/unicorn.stdout.log"

# combine REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
GC.copy_on_write_friendly = true

Next, I create an init script file unicorn and placed it in /etx/init.d/ directory to launch Unicorn as a daemon.
#! /bin/sh

# File: /etc/init.d/unicorn

### BEGIN INIT INFO
# Provides:          unicorn
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the unicorn web server
# Description:       starts unicorn
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/var/lib/gems/1.9.1/bin/unicorn
DAEMON_OPTS="-c /home/socrateos/public_html/apps/app1/unicorn.conf -E production -D"
NAME=unicorn
DESC="Unicorn app for app1"
PID=/home/socrateos/public_html/apps/app1/tmp/pids/unicorn-master.pid

case "$1" in
  start)
 echo -n "Starting $DESC: "
 $DAEMON $DAEMON_OPTS
 echo "$NAME."
 ;;
  stop)
 echo -n "Stopping $DESC: "
        kill -QUIT `cat $PID`
 echo "$NAME."
 ;;
  restart)
 echo -n "Restarting $DESC: "
        kill -QUIT `cat $PID`
 sleep 1
 $DAEMON $DAEMON_OPTS
 echo "$NAME."
 ;;
  reload)
        echo -n "Reloading $DESC configuration: "
        kill -HUP `cat $PID`
        echo "$NAME."
        ;;
  *)
 echo "Usage: $NAME {start|stop|restart|reload}" >&2
 exit 1
 ;;
esac

exit 0

Then make sure to start it on start up.
$ sudo update-rc.d -f unicorn defaults
 Adding system startup for /etc/init.d/unicorn ...
   /etc/rc0.d/K20unicorn -> ../init.d/unicorn
   /etc/rc1.d/K20unicorn -> ../init.d/unicorn
   /etc/rc6.d/K20unicorn -> ../init.d/unicorn
   /etc/rc2.d/S20unicorn -> ../init.d/unicorn
   /etc/rc3.d/S20unicorn -> ../init.d/unicorn
   /etc/rc4.d/S20unicorn -> ../init.d/unicorn
   /etc/rc5.d/S20unicorn -> ../init.d/unicorn

7. Test

I rebooted the machine to check if Unicorn started automatically.
$ ps aux | grep unicorn
root      1151  0.6  0.2  14604 10900 ?        Sl   23:47   0:00 unicorn master -c /home/socrateos/public_html/apps/app1/unicorn.conf -E production -D                                    
root      1192  0.0  0.2  14604  9088 ?        Sl   23:47   0:00 unicorn worker[0] -c /home/socrateos/public_html/apps/app1/unicorn.conf -E production -D                                 
root      1195  0.0  0.2  14604  9088 ?        Sl   23:47   0:00 unicorn worker[1] -c /home/socrateos/public_html/apps/app1/unicorn.conf -E production -D                                 
root      1198  0.0  0.2  14604  9088 ?        Sl   23:47   0:00 unicorn worker[2] -c /home/socrateos/public_html/apps/app1/unicorn.conf -E production -D                                 
root      1201  0.0  0.2  14604  9092 ?        Sl   23:47   0:00 unicorn worker[3] -c /home/socrateos/public_html/apps/app1/unicorn.conf -E production -D                                 
1001      1820  0.0  0.0   5128   860 pts/0    S+   23:48   0:00 grep --color=auto unicorn
Yes!
Finally, go to http:\\www.domain3.com to see if I can see my Ramaze app.
The answer is YES.





2 comments:

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. Thanks for your tutorial, it help me a lot today :)

    ReplyDelete