Deploying Multiple Merb Apps Behind Nginx
The easiest way I’ve found to deploy multiple Merb apps to the same server.
Are you using Merb to build smaller application that don’t need they’re own server? Same here. Merb has replaced Rails for all my internal tools, and below is the simplest method I’d found for deploying them onto a single server.
Nginx
On the front-end I have everyone’s favorite Russian export, Nginx. If you aren’t running Nginx already, you’re obviously going to need to install it. The list of options is pretty overwhelming, but I’ve found the following works well
./configure --prefix=/usr/local/etc/nginx --user=nginx --group=deploy --sbin-path=/usr/local/sbin/nginx --conf-path=/usr/local/etc/nginx/base.conf --pid-path=master.pid --http-client-body-temp-path=/tmp/nginx/client_body --http-proxy-temp-path=/tmp/nginx/proxy --http-fastcgi-temp-path=/tmp/nginx/fastcgi --with-http_ssl_module --error-log-path=/www/logs/nginx/error.log --http-log-path=/www/logs/nginx/access.log --pid-path=master.pid --lock-path=master.lock
make
make install
A lot of the above config options can be left out, but pay particular attention to the default config file (—conf-path). You can override the default when launching Nginx w/ an option (-c if you’re wondering), but it soo much easier to just type `sudo nginx`.
If you get any errors, you’re most likely missing the pcre or zlib libraries. You can placate the Russian beast by installing the development version of the library you need, but I’ve found it easier to just download the source and add the following options during configuration
--with-pcre=path_to_pcre_src --with-zlib=path_to_zlib_src
If you’re expecting a decent amount of traffic, you should also check out the fair upstream module
Base Nginx Configuration
A default config file is created for us during installation, but it has to be perfected. Instead of one file, I break config out into the following files:
- base.conf – general settings
- gzip.conf – how and what to gzip
- logs.conf – log format and paths
- mime.types – endless list of mime types
You can grab them at Github
base.conf
# user and group to run as
user nginx deploy;
# number of nginx workers
worker_processes 3;
# pid of nginx master process
pid master.pid;
# Number of worker connections. 1024 is a good default
events {
worker_connections 1024;
}
# start the http module where we config http access.
http {
include mime.types;
include logs.conf;
# no sendfile on OSX
sendfile on;
# fixes timeouts while processing large files
proxy_read_timeout 600;
# These are good default values.
tcp_nopush on;
tcp_nodelay off;
include gzip.conf;
# Finds all Merb/Rails apps in /www/apps
# Each app has to have nginx.conf file in config directory
include /www/apps/*/current/config/nginx.conf;
}
Most of the above is taken directly from Ezra’s config for Rails. The last line was inspired by rubypond and let’s nginx automatically discover new apps added to the server. If they have the magic conf file that is…
nginx.conf for your app
Every app that is deployed to your server needs to have nginx.conf in its config directory.
# replace app_name w/ your app's name
# replace app.com w/ your app's domain
upstream app_name {
# replace 4000-4002 w/ the ports merb will be on
server 127.0.0.1:4000;
server 127.0.0.1:4001;
server 127.0.0.1:4002;
}
server {
listen 80;
server_name app.com;
# write app specific log
# make sure you create this file in your log directory before running behind nginx
access_log /www/apps/app_name/shared/log/nginx.log main;
# let nginx serve static files directly
# images
location ^~ /images {
root /www/apps/app_name/current/public;
}
# javascript
location ^~/javascripts {
root /www/apps/app_name/current/public;
}
# css
location ^~/stylesheets {
root /www/apps/app_name/current/public;
}
# Push all other requests to Merb
location / {
# needed to forward user's IP address to merb
proxy_set_header X-Real-IP $remote_addr;
if (!-f $request_filename) {
proxy_pass http://app_name;
break;
}
}
}
I used to keep these files in /etc/nginx/hosts/ but having them in the config directory means they get deployed with the rest of the app.
Less clicks = more GTA!
Enter Capistrano
Capistrano is a lazy developer’s best friend. Unfortunately, it doesn’t play nicely with Ngnix or Merb. Let’s change that by editing deploy.rb…
For Nginx
First, we have to add a couple Nginx specific variables:
set :nginx_bin, 'user/local/sbin'
set :nginx_pid, '/usr/local/etc/nginx/master.pid'
Now some tasks:
namespace :nginx do
desc "Reload nginx config files without restarting"
task :reload, :roles => :web do
sudo "kill -HUP `cat #{nginx_pid}`"
end
desc "Start"
task :start, :roles => :web do
sudo "#{nginx_bin}/nginx"
end
desc "Kill nginx"
task :stop, :roles => :web do
sudo "kill `cat #{nginx_pid}`"
end
desc "Restart nginx"
task :restart, :roles => :web do
nginx.stop
nginx.start
end
end
For Merb
The variables:
set :number_of_instances, 3
set :first_port, 5000
set :app_server, 'mongrel'
The tasks:
namespace :merb do
desc "Start merb cluster"
task :start, :roles => :app do
run "nohup merb -a #{app_server} -c #{number_of_instances} -m #{deploy_to}/current -p #{first_port} -e production"#" -h 127.0.0.1" -u #{user} -G #{group}
end
desc "Stop merb cluster"
task :stop, :roles => :app do
run "merb -m #{deploy_to}/current -k all"
end
end
Make nice
Setup MERB_ENV
set :migrate_env, “MERB_ENV=production”
Finally, we have to overwrite a few default tasks to get everything to work together:
# Overwrite main tasks
namespace :deploy do
desc "Custom restart task for merb appserver"
task :restart do
merb.stop
merb.start
end
desc "Custom start task for merb"
task :start, :roles => :app do
merb.start
end
desc "Custom stop task for merb cluster"
task :stop, :roles => :app do
merb.stop
end
end
Now you’re ready to roll. Thank pluesch0r the next time your in #merb for the Merb tasks.
Deploy
Ready? Then…
cap deploy:setup
cap deploy
cap nginx:reload
Remember to use nginx:reload anytime you push a new version of nginx.conf for your app. Otherwise, Nginx will continue to use the old version.
Do note, I’ve only been using the above Cap tasks for a couple of weeks. They do the job intended but could definitely use some love.
I’d like to:
- move the Cap tasks to a gem
- create a Cap task that generates the app specific nginx.conf
- make the Cap tasks work w/ the normal Cap workflow, i.e. deploy:cold, etc
Comments
On May 22, 2008 at 01:33 mlangenberg says:
You really don’t need to override the migrate task, it’s enough to add the following: set :migrate_env, “MERB_ENV=production”
On May 22, 2008 at 07:01 glasner says:
Thanks for the tip; I just updated the tutorial. Much simpler!
On May 28, 2008 at 07:04 mtodd says:
Well, might need to override the migrate task if using something like Sequel which provides a sequel:db:migrate task instead of db:migrate to run migrations.
On June 03, 2008 at 19:49 hipertracker says:
There is no need for these ugly rules for images, javascripts, etc. This can be solved in more general way:
# serve all static files if (-f $request_filename) { access_log off expires 30d; break; } # server index files if (-f $request_filename/index.html) { rewrite (.*) $1/index.html break; } # server cached files if (-f $request_filename.html) { rewrite (.*) $1.html break; } # all other request send to proxy if (!-f $request_filename) { proxy_pass http://app_name; break; }BTW, can anybody add preview button for comments?
On June 03, 2008 at 19:51 hipertracker says:
Again:
Sign in to add your comment