Peter Marklund's Home |
Ruby on Rails Deployment on FreeBSD
I did a Ruby on Rails FreeBSD deployment for a client the other day and I thought I'd share what I learned in an instructional format here. Previously I had mostly deployed on Linux (Suse, CentOS etc.) and I was curious to see what the FreeBSD experience would be like. I googled for instructions and immediately found the RailsOnFreeBSD page in the Rails Wiki. Other than that I couldn't find much Ruby on Rails and FreeBSD specific instruction out there. Note - most of the instructions in this post are not specific to FreeBSD but are generic Ruby on Rails deployment steps for Unix.
We were migrating from Windows to FreeBSD and the goal was to eliminate single points of failure. We settled on two application servers with FreeBSD 6.2 on HP hardware, both running the web and app tiers in the vanilla Rails deployment stack, i.e. Apache 2.2.4, mod_proxy_balancer, Mongrel cluster, and Mongrel. A load balancer external to the Rails system would load balance between the two Apache servers. The database we use is MySQL 5 and it sits on a separate server. The idea is to add another db server with some form of MySQL replication. We have yet to decide which replication to use and recommendations are welcome. For deployment we use Capistrano 2.
The first thing I do on a FreeBSD server is to log in with the root user and change shell from C shell to bash:
cd /usr/ports/shells/bash make install clean chsh -s /usr/local/bin/bash root exit su - echo $SHELL
As a personal preference (or ignorance of vi maybe), I install Emacs. This is a good time to go grab a cafe latte, since the installation takes forever:
cd /usr/ports/editors/emacs make install clean
We then add the user that Capistrano will log in as and that Mongrel will run under - the deploy user:
# Make sure to choose the bash shell. You can keep the defaults for most of the other questions. adduser deploy
To be able to deploy with Capistrano without repeatedly being prompted for a password, we set up public/private key authentication:
# On the production server: ssh-keygen -t rsa # On your development server: ssh-keygen -t rsa scp ~/.ssh/id_rsa.pub deploy@production-server:.ssh/remote_key ssh deploy@production-server cd .ssh cat remote_key >> authorized_keys rm remote_key exit # Now ssh should not prompt for a password: ssh deploy@production-server
We edit ~/.bashrc and setup the environment for the deploy user. I think it's important to set RAILS_ENV to production. I configure the bash prompt and the history size (the number of shell commands listed by the history command) and my preferred editor. I also add some convenient aliases for accessing the log file and mysql:
export RAILS_ENV=production export PS1="[\u@\h:\w] " export HISTSIZE=10000 export EDITOR=emacs export PATH=$PATH:/usr/local/mysql/bin export APP="/var/www/apps/streamio/current" alias cdapp='cd $APP' alias logapp='tail -f $APP/log/production.log' alias restartapp='cdapp; mongrel_rails cluster::restart -C config/mongrel_cluster.yml' alias mysqlapp='mysql -h db.host.name -u db.user -pdb-password database-name'
To make sure the ~/.bashrc file is sourced, edit or create ~/.profile and add the following line to it:
source ~/.bashrc
We install sudo and give the deploy user sudo access. That way we can use sudo from Capistrano to restart the Apache web server that will be running as root:
# Install sudo cd /usr/ports/security/sudo make install clean emacs /usr/local/etc/sudoers # Uncomment wheel group pw user mod deploy -G wheel
Make sure the clock on the server is in sync by invoking "crontab -e" and add this line:
*/30 * * * * /usr/sbin/ntpdate ntp1.sp.se
The above line syncs the clock every half hour with an internet clock - an ntp server. In this case we use ntp1.sp.se, but you may choose a different npt server that is available in your country.
Now, finally, the time has come for us to install Ruby on Rails which is really the heart of our server (or where our hearts are as Rails developers at least). As indicated in the Rails wiki, we can use the rubygem-rails port for this. The port will install both Ruby (the programming language), RubyGems (the package manager for Ruby software), and Ruby on Rails (the web framework). The portinstall command in the Rails wiki didn't work for me, so I used make install instead:
# Update the ports tree - takes a long time... portsnap fetch ; portsnap extract # Install Ruby, RubyGems, and Rails cd /usr/ports/www/rubygem-rails make install clean # Check your version of the installed software # The versions given here are the ones I got, you may find later versions ruby -v => ruby 1.8.5 (2006-08-25) [i386-freebsd6] gem -v => 0.9.2 rails -v => Rails 1.2.3
Now that we have RubyGems at our fingertips, we can install Capistrano, Mongrel Cluster, and Mongrel in a snap:
gem install capistrano -y gem install mongrel_cluster -y cap --version => Capistrano v2.0.0 mongrel_rails --version => ** Ruby version is not up-to-date; loading cgi_multipart_eof_fix => Mongrel Web Server 1.0.1
To be able to control the version and the configuration I chose to install Apache from source, and I followed the instructions in the excellent Mongrel book by Zed Shaw:
mkdir /usr/local/src cd /usr/local/src # Visit http://httpd.apache.org and download httpd-2.2.4.tar.gz tar xzf httpd-2.2.4.tar.gz cd httpd-2.2.4 ./configure --enable-proxy --enable-proxy-balancer --enable-proxy-http --enable-rewrite \ --enable-cache --enable-headers --enable-ssl make make install # You can check the location of the httpd binary: /usr/libexec/locate.updatedb locate httpd|grep bin
Add the Apache startup script:
emacs /etc/rc.conf # Add the following line: httpd_enable="YES" ln -s /usr/local/apache2/bin/apachectl /usr/local/etc/rc.d/httpd # Start Apache /usr/local/etc/rc.d/httpd start # Fetch http://production.host.name in a browser. You should see the text "It Works".
We now configure Apache for our Mongrel server like it says in the Mongrel book:
cd /usr/local/apache2/conf emacs httpd.conf # Add one line: Include /usr/local/apache2/conf/rails.conf
Create the /usr/local/apache2/conf/rails.conf file with contents like this (make sure to query replace $app_name$ with the name of your Rails app, i.e. the basename of your RAILS_ROOT):
NameVirtualHost *:80
# Setup the cluster
BalancerMember http://127.0.0.1:8000
BalancerMember http://127.0.0.1:8001
BalancerMember http://127.0.0.1:8002
At this point it makes sense to restart Apache to make sure that the config file parses. We now finish up on the server by installing Subversion, MySQL client, ImageMagick, and RMagick:
cd /usr/ports/devel/subversion make install clean cd /usr/ports/databases/mysql50-client/ make install clean cd /usr/ports/graphics/ImageMagick make install clean gem install rmagick -y
If you are in a Windows environment you may want to install Samba:
cd /usr/ports/net/samba3 make install clean
You should now have the mount_smbfs command available for mounting Windows disks on your FreeBSD server.
We are now just about ready to deploy to our FreeBSD server using Capistrano 2 from our development machine. Before we do that though, let's create the directory on the FreeBSD server we'll be deploying to:
mkdir /var/www chown deploy /var/www
Now, make sure your database.yml is properly configured. Make sure you can connect to MySQL from the FreeBSD servers. Capify your Rails app if you haven't already:
cd RAILS_ROOT capify .
You now need to edit config/deploy.rb to fit your server. In particular, make sure you have the deploy_to variable set to "/var/www/apps/#{application}" and you have the proper roles set up. Here is a sample:
role :web, rails01, rails02
role :app, rails01, rails02
role :db, rails01, :primary => true
role :scm, rails01
Also, make sure to define the deploy:restart task to restart using Mongrel Cluster. Also, symlink in any shared files in a callback. Here is a sample from my deploy.rb file to get you started (don't copy it in it's entirety, that won't work):
namespace :deploy do
# ===========================================================================
# Mongrel
# ===========================================================================
"cd && " +
"mongrel_rails cluster:: -C /config/mongrel_cluster.yml"
end
%w(restart stop start).each do |command|
task command.to_sym, :roles => :app do
run mongrel_cluster(command)
end
end
# ===========================================================================
# Apache
# ===========================================================================
desc "Restart Apache web server"
task :restart_web do
sudo "/usr/local/etc/rc.d/httpd restart"
end
# ===========================================================================
# Deployment hooks
# ===========================================================================
desc "Copy in server specific configuration files"
task :copy_shared do
proxy_dir = " /vendor/plugins/reverse_proxy_fix/lib"
run <<-CMD
cp /config/database.yml.example /config/database.yml &&
cp /config/directories.rb.example /config/directories.rb &&
cp /config.rb.unix /config.rb
CMD
end
desc "Run pre-symlink tasks"
task :before_symlink, :roles => :web do
copy_shared
backup_db
run_tests
end
desc "Run the full tests on the deployed app."
task :run_tests do
run "cd && RAILS_ENV=production rake && cat /dev/null > log/test.log"
end
desc "Clear out old code trees. Only keep 5 latest releases around"
task :after_deploy do
cleanup
sleep 5
ping_servers
end
If you have your deploy.rb in check, you should now be able to run "cap deploy:setup" to setup the Capistrano directory structure on the servers, and finally the magic command to deploy to both of the FreeBSD servers:
cap deploy
Good luck!