Setup a Ruby on Rails, Passenger, Nginx running on an AWS EC2 with RDS.
Here is a slimline walkthrough for getting a complete setup and working AWS EC2 Ruby on Rails instance. For this walkthrough we will be using rails version 2.3.15 and using bundler for the gems. We will also be using an RDS instance which requires knowing your root user’s password.
Setup:
First add user for our deployments (via capistrano)
sudo adduser deploy
Now we work out what we need to be installed:
nginx, passenger, ruby 1.8.17, rails 2.3.15, bundler, mysql (as a client), capistrano
I am using a quite ‘outdated’ rails version compared to what is now available. But the installation procedure should be similar if not exactly the same.
Make sure your system is up to date:
apt-get update
apt-get upgrade
# configured hostname
sudo hostname railsweb01.somecompany.com
For a Localsystem (Non RDS):
# grant privileges to user running capistrano
GRANT ALL PRIVILEGES, GRANT OPTION ON *.* TO ‘deploy’@’localhost’ IDENTIFIED BY ‘<pass>’;
GRANT GRANT OPTION ON *.* TO ‘deploy’@’localhost’ IDENTIFIED BY ‘<pass>’;
For RDS
mysql> grant all on '%'.* to deploy@'%' IDENTIFIED BY '<pass>';
Query OK, 0 rows affected (0.01 sec)
mysql> grant grant option on '%'.* TO deploy@'%';
Query OK, 0 rows affected (0.00 sec)
Now we need to install some dependencies / some nice tools for when we actually want to use the system.
sudo apt-get -y install build-essential zlib1g-dev libssl-dev libreadline-dev libyaml-dev libcurl4-openssl-dev curl git-core python-software-properties sqlite3 libsqlite3-dev imagemagick libmagickwand-dev libmysqlclient-dev mysql-client zsh
Now comes the very daunting task of installing rvm. The ruby version management system. This makes it seriously easy to configure multiple ruby versions!
RVM:
curl -L https://get.rvm.io | sudo bash -s stable
# add users to use ruby to rvm group
usermod -a -G rvm deploy
# logout and log back into for new group to take effect.
rvm pkg install openssl
rvm reinstall all --force
# rvm will install patches for this version of ruby
# here is where you'd change your version numbers for more up-to-date versions
rvm install ruby-1.8.7-p371
# use specific gemset with set rails version
rvm use ruby-1.8.7-p371
rvm gemset create rails2.3.15
rvm gemset use rails2.3.15 --default
gem list
# Add to .bashrc/.zshrc so RVM enviornment variables get loaded in a non interactive environment
# must be above “case $- in”
[[ -s "/usr/local/rvm/scripts/rvm" ]] && . "/usr/local/rvm/scripts/rvm" # This loads RVM into a shell session.
Slowbro used tinker, Ruby install was successful!
Nginx with passenger:
# install passenger & use passenger-nginx module to install nginx for us (compiled from source).
gem install passenger
sudo mkdir /opt/nginx/
sudo chown ubuntu /opt/nginx
passenger-install-nginx-module
# install the init scripts to start nginx
wget -O init-deb.sh http://library.linode.com/assets/660-init-deb.sh
sudo mv 660-init-deb.sh /etc/init.d/nginx
sudo chmod +x /etc/init.d/nginx
sudo /usr/sbin/update-rc.d -f nginx defaults
/etc/init.d/nginx start
Slowbro used defend. Nginx with passenger module install successful!
Install the required Gems:
# install the gems
(run rake gems:install to install ryanb-acts-as-list)
gem install sqlite3
gem install rmagick
# Set our default ruby&rails combo
rvm --default use ruby-1.8.7@rails2.3.15
# Capitstrano installation
gem install capistrano
# Now bundler
gem install bundler
# These are the only two gems I install globally, as they will be required for all of my ruby applications
# As in my opinion, its better to use bundler to download the required gems per app instead of having every gem under the sun installed on the server.
# Trust me on that, as we had to migrate from that exact scenario... wasnt fun...
PHP-FPM: Optional but highly recommended:
# Install of php
sudo apt-get install php5-cli php5-common php5-suhosin
# after the above install
sudo apt-get install php5-fpm php5-cgi
You are now finished. Congratulate yourself on installing Nginx with passenger, Ruby On Rails, on an EC2 instance. But there’s always the configuration step! Woooot!
Configuring:
Configure Nginx:
cat /opt/nginx/nginx.conf:
user deploy;
worker_processes 4;
error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
# Enable below if you have more than 128 hostnames on the server.
#server_names_hash_bucket_size 128;
# Enable below if your hostnames are longer than 100 characters.
#server_names_hash_max_size 100;
passenger_max_pool_size 200;
#passenger_max_instances_per_app 7;
rails_app_spawner_idle_time 600;
passenger_pool_idle_time 300;
passenger_debug_log_file /opt/nginx/logs/passenger-error.log;
passenger_log_level 2;
# Add your own gems, and wrapper locations.
passenger_root /usr/local/rvm/gems/ruby-1.8.7-p371@rails2.3.15/gems/passenger-3.0.19;
passenger_ruby /usr/local/rvm/wrappers/ruby-1.8.7-p371@rails2.3.15/ruby;
client_max_body_size 15M;
include mime.types;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
default_type application/octet-stream;
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 20;
gzip on;
server {
listen 80;
server_name localhost;
error_page 403 /403.html;
error_page 404 /404.html;
#access_log logs/host.access.log main;
location / {
# If is bad, you shouldn't use this
if (-f $document_root/system/maintenance.html) {
return 503;
}
root html;
index index.html index.htm index.php;
}
# Parse all .php file in the /var/www directory
location ~ .php$ {
fastcgi_split_path_info ^(.+.php)(.*)$;
# This "backend" corresponds to the upstream "backend" below
fastcgi_pass backend;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www$fastcgi_script_name;
include fastcgi_params;
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_intercept_errors on;
fastcgi_ignore_client_abort off;
fastcgi_connect_timeout 60;
fastcgi_send_timeout 180;
fastcgi_read_timeout 180;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
}
# Disable viewing .htaccess and .htpassword
location ~ /.ht {
deny all;
}
}
upstream backend {
# This socket location is corresponding to the php-pool configuration below
server unix:/var/run/php5-fpm.sock;
}
Configure PHP-FPM:
cat /etc/php5/fpm/pool.d/www.conf
[www]
user = deploy
group = deploy
# Listen socket must equal the same as nginx
listen = /var/run/php5-fpm.sock
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir =/
Second last add your nginx virtual host configuration. Remember that every configuration change that you make needs to be reloaded into nginx. But quick a word from the wise:
Everytime you reload nginx, every passenger application gets restarted. Which means every site goes down. In a virtual sites hosting environment this can be bad, so make sure you reload with caution!
Example from /opt/nginx/site-enabled/domain.com:
server {
server_name domain.com.au;
rewrite ^(.*) http://www.domain.com.au$1 permanent;
}
server {
listen 80;
server_name www.domain.com.au;
root "/home/deploy/apps/domain_com_au/current/public";
passenger_enabled on;
rails_env production;
# Set custom access/error logs for awstats
access_log /opt/nginx/logs/domain.com.au-access.log;
error_log /opt/nginx/logs/domain.com.au-error.log;
# Configure /cgi-bin/scripts to go through php-fastcgi
location ~ ^/cgi-bin/.*.(cgi|pl|py|rb) {
gzip off;
fastcgi_pass backend;
fastcgi_index cgi-bin.php;
fastcgi_param SCRIPT_FILENAME /opt/nginx/cgi-bin.php;
fastcgi_param SCRIPT_NAME /cgi-bin/cgi-bin.php;
fastcgi_param X_SCRIPT_FILENAME /usr/lib$fastcgi_script_name;
fastcgi_param X_SCRIPT_NAME $fastcgi_script_name;
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 GATEWAY_INTERFACE CGI/1.1;
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 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;
fastcgi_param REMOTE_USER $remote_user;
}
}
(of course you would/should set this up through a Capistrano recipe, so it automates it for you)
Now we increase our file descriptors. This is a work with passenger and nginx. As they use sockets, every new process creates a new socket, thus increasing our load on file descriptors. This is only really needed if you host around 100+ sites, and have an average of 50 active at one time. But if you don’t change your file descriptors you will get a lot of 502 nginx errors and 500 errors.
vim /etc/security/limits.conf
deploy soft nofile 4096
deploy hard nofile 10240
Now all you have to do is push your site live and your good to go! If enough people want / I get time, I’ll go though changing a rails application to one that can use AWS in a Production and Staging environment.