Once in a while you, as a Ruby developer, are faced with product
owner’s “Alright, now it’s time to make it live”. And then you probably
think “I’ll be fighting with these stubborn servers for next few days…”.
If you have very simple app or one at the early stages of its lifetime
you can use one of “no hassle deployment” platforms like Heroku or OpenShift.
But chances are you need some custom stuff that is hard to achieve on
these kind of platforms or you just feel better with “root” access.
You have many options for setting up Linux servers. Amongst the most popular ones are Chef and Puppet. Various hosting provider also add their own solutions for provisioning boxes (like Stackscripts on Linode). Or you can do it “the old-school way”, manually. If you don’t need multiple machines and/or you have just a simple Rails site then provisioning tools might be an overkill. Also I believe any Ruby developer should configure production server from scratch at least once to get familiar with this stuff and to learn where to look when troubleshooting server side problems.
Recently I led a workshop about these things here at LLP and we decided to compile this knowledge into a blog post to share it with other Ruby devs and to have a known reference point in the future. So here it goes.
Note: following steps were tested on Ubuntu 12.04 and 12.10. They don’t include any version-specific commands so they should also work without a problem on newer Ubuntu versions when they get relased.
Let’s make it a default editor for future sessions also:
Now run following to install Vim editor (skip it if you prefer to use nano or
other):
Let’s also install ntp daemon that will keep server time up to date, all the time:
Try ssh’ing now:
You shouldn’t be asked for password anymore.
Disable installation of rdoc and ri docs for installed gems to save yourself some time:
Set RAILS_ENV to production so you don’t have to type it when invoking rake:
Switch back to root and follow next steps.
This is handy if you want to have several apps or users on the servers.
Make sure you have curl command installed:
Install stable RVM version by piping installation script to bash:
Source rvm script so we don’t need to re-login:
Let’s ignore RVM prompts about trusting .rvmrc files (we’ll use default gemset
for Passenger anyway)
Most likely it is the following list of packages:
Now compile it:
Just follow the instructions toward to compile and install nginx.
Download the script:
Start nginx:
And check if it works by looking at a response:
“Welcome to Nginx” means that all is fine.
server block with following:
Restart Nginx:
And confirm that it restarted properly:
You should get the 404 page due to the fact that our app is not running yet.
Create project database (you will be asked for mysql root password you set when running previous installation command):
And grant access to luna user:
Note: All of the commands in this section are meant to be run on your local machine inside the Rails project directory (unless otherwise stated).
The last one nicely integrates capistrano with RVM.
You should have Capfile and config/deploy.rb files now.
load ‘deploy/assets’ handles assets compilation in Rails 3. If you’re deploying Rails 2 application just remove this line.
Then make sure you have following lines in the file:
Copy the file:
Now set proper values in database.yml on the server:
And deploy for the first time:
Once you have application code on the server log in there and prepare db structure:
Final deploy just to make sure everything works:
That will tell logrotate to rotate log files daily, compress them,
keep last 30 days and don’t choke when file is missing. copytruncate is
important here as it will make sure log file currently used by Rails app
is not moved but truncated. That way the app can just keep on logging
without reopening log file.
Don’t forget about this one if you manage production box yourself. And do it when you initially setup the box, not “later”. “Later” often means “when app is down due to not enough disk space”. Srsly.
Now set default firewall policy to “deny”:
And allow connections to the services we want to expose to the world:
Finally, enable firewall:
You’re production environment is safer now.
Install it by:
Thanks to firewall rules from previous section you don’t need to
worry about spammers using your server for sending their spam. They
won’t be able to connect to your Postfix daemon from the outside of the
machine.
Open /etc/monit/monitrc in an editor and adjust default config to suit your needs.
By default it monitors CPU usage, memory usage, disk usage and several other system-level components.
If you’ve been using god for monitoring your app processes then you may consider using monit also for this task as it’s a much simpler tool for the job.
You have many options for setting up Linux servers. Amongst the most popular ones are Chef and Puppet. Various hosting provider also add their own solutions for provisioning boxes (like Stackscripts on Linode). Or you can do it “the old-school way”, manually. If you don’t need multiple machines and/or you have just a simple Rails site then provisioning tools might be an overkill. Also I believe any Ruby developer should configure production server from scratch at least once to get familiar with this stuff and to learn where to look when troubleshooting server side problems.
Recently I led a workshop about these things here at LLP and we decided to compile this knowledge into a blog post to share it with other Ruby devs and to have a known reference point in the future. So here it goes.
Note: following steps were tested on Ubuntu 12.04 and 12.10. They don’t include any version-specific commands so they should also work without a problem on newer Ubuntu versions when they get relased.
Preparations
Let’s assume you just created a VPS box and got email with root access. Now, login to the server. If you got access to non-root user with sudo access then switch to root with:
$ sudo -i
Set preferred editor
You’ll be configuring the machine by editing several config files. Make sure you have your preferred editor set:
$ export EDITOR=vim
$ echo "export EDITOR=vim" > /etc/profile.d/editor.sh
Update apt sources and upgrade base packages
You’ll be installing packages from Ubuntu repositories. Make sure apt sources are up to date:
$ apt-get update
other):
$ apt-get install vim
Set server timezone and time
To save yourself (and your app) some trouble set server’s timezone to UTC:
$ echo "Etc/UTC" > /etc/timezone
$ apt-get install ntp
Add user for your app
You don’t want your app to run as root. Let’s assume your app is named “luna” so let’s add “luna” user:
$ adduser luna
Allow sudo
You’ll be logging into the server as the “luna” user from time to time to do some tweaks. Grant the user sudo access:
$ echo "luna ALL=NOPASSWD:ALL" > /etc/sudoers.d/luna
$ chmod 0440 /etc/sudoers.d/luna
$ chmod 0440 /etc/sudoers.d/luna
Copy SSH key
To avoid typing password (for many reasons) when logging in as “luna” copy your public SSH key to server user’s ~/.ssh/authorized_keys file with following command:
$ ssh-copy-id luna@luna.com
$ ssh luna@luna.com
Useful stuff
Switch to the “luna” user:
$ su - luna
$ echo "gem: --no-rdoc --no-ri" > ~/.gemrc
$ echo "export RAILS_ENV=production" >> ~/.bashrc
Ruby
Now, for ruby, we’ll install and use RVM to install ruby 1.9.3.Switch back to root and follow next steps.
Install RVM
Here we’ll install RVM globally (so called “system install” as opposed to “user install”).This is handy if you want to have several apps or users on the servers.
Make sure you have curl command installed:
$ apt-get install curl
$ curl -L get.rvm.io | bash -s stable
$ source /etc/profile.d/rvm.sh
for Passenger anyway)
$ echo "export rvm_trust_rvmrcs_flag=0" >> /etc/rvmrc
RVM access for user “luna”
Add user luna to rvm group:
$ adduser luna rvm
Install requirements
See what are requirements for compiling MRI:
$ rvm requirements
$
apt-get install build-essential openssl libreadline6 libreadline6-dev
curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev
sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake
libtool bison subversion
Install Ruby
Now, install ruby via RVM:
$ rvm install 1.9.3
Make installed ruby a default
Make it a default for all new shells:
$ rvm --default use 1.9.3
Nginx + Passenger
As a webserver is concerned, the combo of Nginx + Passenger works well in most cases.Install passenger gem
$ gem i passenger
Install Nginx via passenger gem
First install dependencies for Nginx/Passenger:
$ apt-get install libcurl4-openssl-dev
$ passenger-install-nginx-module
Create boot service (upstart)
Upstart script for Nginx will be used for starting/stopping nginx from command line and will make sure nginx starts on system boot.Download the script:
$ curl https://raw.github.com/gist/2492523/nginx.conf > /etc/init/nginx.conf
$ start nginx
$ curl localhost
VHost
Now we need to create Virtual Host config for the luna app, replacing defaultserver block with following:
# /opt/nginx/conf/nginx.conf
...
server {
listen 80;
server_name www.luna.com luna.com;
root /home/luna/current/public; # <--- be sure to point to 'public'!
passenger_enabled on;
}
...
...
server {
listen 80;
server_name www.luna.com luna.com;
root /home/luna/current/public; # <--- be sure to point to 'public'!
passenger_enabled on;
}
...
$ restart nginx
$ curl localhost
MySQL
Install MySQL server via apt:
$ apt-get install mysql-server libmysqlclient-dev
$ echo "create database luna_production" | mysql -u root -p
$ echo "grant all on luna_production.* to luna@localhost identified by 'luna123'" | mysql -u root -p
Capistrano
Let’s use Capistrano for deploying new relases of the “luna” app.Note: All of the commands in this section are meant to be run on your local machine inside the Rails project directory (unless otherwise stated).
Add capistrano to the bundle
First add following to your app’s Gemfile:
group :development do
...
gem 'capistrano'
gem 'capistrano-ext'
gem 'rvm-capistrano'
...
end
...
gem 'capistrano'
gem 'capistrano-ext'
gem 'rvm-capistrano'
...
end
Install new gems:
$ bundle
Generate skeleton capistrano config files
$ capify .
Edit Capfile
Make the file contents look like this:
load 'deploy'
load 'deploy/assets'
load 'config/deploy'
load 'deploy/assets'
load 'config/deploy'
Edit config/deploy.rb
First, you should fill the variables with your application name, repository and web server name. Then find commented out block of code that’s related to Passenger. Just uncomment it.Then make sure you have following lines in the file:
require 'rvm/capistrano'
require 'bundler/capistrano'
ssh_options[:forward_agent] = true
set :deploy_via, :remote_cache
set :use_sudo, false
set :user, "luna"
set :deploy_to, "/home/luna"
set :rails_env, "production"
set :rvm_type, :system
set :keep_releases, 3
after "deploy:restart", "deploy:cleanup"
namespace :deploy do
desc "Symlink shared/* files"
task :symlink_shared, :roles => :app do
run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
end
end
after "deploy:update_code", "deploy:symlink_shared"
require 'bundler/capistrano'
ssh_options[:forward_agent] = true
set :deploy_via, :remote_cache
set :use_sudo, false
set :user, "luna"
set :deploy_to, "/home/luna"
set :rails_env, "production"
set :rvm_type, :system
set :keep_releases, 3
after "deploy:restart", "deploy:cleanup"
namespace :deploy do
desc "Symlink shared/* files"
task :symlink_shared, :roles => :app do
run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
end
end
after "deploy:update_code", "deploy:symlink_shared"
Let capistrano prepare directory structure on the server
$ cap deploy:setup
Copy example database config file to the server:
First create a config directory inside the shared directory:
$ ssh luna@luna.com mkdir -p ~/shared/config
$ scp config/database.yml.example luna@luna.com:~/shared/config/database.yml
$ ssh luna@luna.com vim shared/config/database.yml
$ cap deploy
$ ssh luna@luna.com
# following happens in a remote shell
cd current
bundle exec rake db:setup
# following happens in a remote shell
cd current
bundle exec rake db:setup
$ cap deploy
Logrotate
Create /etc/logrotate.d/luna file with following content:
/home/luna/app/shared/log/*.log {
daily
missingok
rotate 30
compress
delaycompress
copytruncate
}
daily
missingok
rotate 30
compress
delaycompress
copytruncate
}
Don’t forget about this one if you manage production box yourself. And do it when you initially setup the box, not “later”. “Later” often means “when app is down due to not enough disk space”. Srsly.
Firewall
Ubuntu comes with a decent firewall management tool called ufw. Install it:
$ apt-get install ufw
$ ufw default deny
$ ufw allow ssh/tcp
$ ufw allow 80/tcp
$ ufw allow 443/tcp
$ ufw allow 80/tcp
$ ufw allow 443/tcp
$ ufw enable
Mail server (MTA)
There are many offerings for SMTP service that also bring some additional features like email opening tracking, link click tracking and whatnot. If you just need the plain ”send message and forget” functionality you may use Postfix MTA.Install it by:
$ apt-get install postfix heirloom-mailx
Monitoring
For basic system monitoring the easiest thing you can do is to install monit:
$ apt-get install monit
By default it monitors CPU usage, memory usage, disk usage and several other system-level components.
If you’ve been using god for monitoring your app processes then you may consider using monit also for this task as it’s a much simpler tool for the job.
0 comments:
Post a Comment