If you deploy a rails app forgetting to configure logs automatic rotation, few weeks later won't be difficult to find something like this:

$ ls -lh log/production.log
  -rw-rw-r-- 1 www-data www-data 93,2M apr 10 17:49 production.log

Think if you have to find some error log inside a 100MB file, not easy... :)

Setting log rotation isn't difficult at all. I know two main ways.

Use syslog

This is a really easy solution. Rails will use standard syslog as logger, which means the logs will rotate automatically.

Open config/environments/production.rb and add this line:

config.logger = SyslogLogger.new  

If you want to avoid your logs to be mixed with system logs you need to add some parameters:

config.logger = SyslogLogger.new('/var/log/<APP_NAME>.log')  

Use logrotate

This is the cleaner way, but requires to create a file in the server, inside the /etc/logrotate.d/ folder. This is a possible content of the /etc/logrotate.d/rails_apps file:

/path/to/rails/app/log/*.log {
    weekly
    missingok
    rotate 28
    compress
    delaycompress
    notifempty
    copytruncate
}

The copytruncate option is required unless you want to restart the rails app after log rotation. Otherwise the app will continue to use the old log file, if it exists, or will stop logging (or, worse, will crash) if the file is deleted.
Below the copytruncate details from the logrotate man page:

copytruncate  
      Truncate  the  original log file in place after creating a copy,
      instead of moving the old log file and optionally creating a new
      one,  It  can be used when some program can not be told to close
      its logfile and thus might continue writing (appending)  to  the
      previous log file forever.  Note that there is a very small time
      slice between copying the file and truncating it, so  some  log-
      ging  data  might be lost.  When this option is used, the create
      option will have no effect, as the old log file stays in  place.

To check the logrotate script you can use the logrotate command with the debug (-d) option, which executes a dry-run:

sudo logrotate -d /etc/logrotate.d/rails_apps  

If everything seems ok you can wait until the next day or manually launch the rotation with:

sudo logrotate -v /etc/logrotate.d/rails_apps  

If you, like me, have a lot of ruby apps and want to check if the code is vulnerable, Codesake::Dawn could be a useful gem.

This gem supports Rails, Sinatra and Padrino apps. To install it in a Rails app, add the gem to the development group in Gemfile:

group :development do  
  gem 'codesake-dawn', require: false
end  

then run bundle install.
Now add this line in the Rakefile:

require 'codesake/dawn/tasks'  

Install finished. To check the app you just have to run rake dawn:run:

~$ rake dawn:run
15:27:03 [*] dawn v1.1.0 is starting up  
15:27:04 [$] dawn: scanning .  
15:27:04 [$] dawn: rails v4.0.3 detected  
15:27:04 [$] dawn: applying all security checks  
15:27:04 [$] dawn: 171 security checks applied - 0 security checks skipped  
15:27:04 [$] dawn: 1 vulnerability found  
15:27:04 [!] dawn: Owasp Ror CheatSheet: Session management check failed  
15:27:04 [$] dawn: Severity: info  
15:27:04 [$] dawn: Priority: unknown  
15:27:04 [$] dawn: Description: By default, Ruby on Rails uses a Cookie based session store. What that means is that unless you change something, the session will not expire on the server. That means that some default applications may be vulnerable to replay attacks. It also means that sensitive information should never be put in the session.  
15:27:04 [$] dawn: Solution: Use ActiveRecord or the ORM you love most to handle your code session_store. Add "Application.config.session_store :active_record_store" to your session_store.rb file.  
15:27:04 [$] dawn: Evidence:  
15:27:04 [$] dawn:     In your session_store.rb file you are not using ActiveRercord to store session data. This will let rails to use a cookie based session and it can expose your web application to a session replay attack.  
15:27:04 [$] dawn:     {:filename=>"./config/initializers/session_store.rb", :matches=>[]}  
15:27:04 [*] dawn is leaving  

Since MySQL v.5.5.29 the mysqldump command can generate the following error:

-- Warning: Skipping the data of table mysql.event. Specify the --events option explicitly.

An example of mysqldump for full dumps is this:

mysqldump --opt -u <USERNAME> -p<PASSWORD> --all-databases | gzip > full_dump.sql.gz  

In a case of a server which executes it during periodical backups, this means a warning email from Cron Daemon, very annoying.

If you add, as suggested, the --events option you can receive this error:

mysqldump: Couldn't execute 'show events':  
  Access denied for user '<USERNAME>'@'localhost' to database '<DATABASE>' (1044)

The solution is to grant the EVENT permission to the user:

GRANT EVENT ON <DATABASE>.* to '<USERNAME>'@'localhost' with grant option;  

Apparently if you don't care about events there's no way to suppress the error message with some --no-events option, as far as I know.
There's an interesting discussion about this (maybe-not-a-)bug here


Waiting for a sitemap generator inside the core of Ghost (planned as "future implementation") I decided to implement a way to generate an up-to-date sitemap.xml during deployment.
As you can read in the previous post I'm deploying this blog with Capistrano and capistrano-node-deploy.
So I added a deploy:generate_sitemap task which is executed at the end of the deployment process.

This is the Capfile extract:

namespace :deploy do  
  task :generate_sitemap do
    run "cd #{latest_release} && ./ghost_sitemap.sh #{latest_release}"
  end
end  
after "node:restart", "deploy:generate_sitemap"  

So at the end of the deployment the ghost_sitemap.sh script is executed. The script is placed in the blog root and is a personalized version of the code you can find here: http://ghost.centminmod.com/ghost-sitemap-generator/

It essentially does 3 things:

  • Puts the sitemap.xml link in the robots.txt file
  • Scans (using wget) the website and generates the sitemap.xml file in the content folder
  • Notifies Google Webmaster Tools

What I changed of the original script is:

url="www.tommyblue.it"  
webroot="${1}/content"  
path="${webroot}/sitemap.xml"  
user='<USER>'  
group='<GROUP>'  

user and group will be used to chmod the sitemap.xml file, so check that the web user (probably www-data) can read that file.

This process has a big problem: the sitemap is generated only during deploy, not when I publish a new post. A workaround is to run cap deploy:generate_sitemap after a new post is published.

It works but I need an automatic way. Any idea?


I just moved this blog from Jekyll to Ghost (v.0.4.2 while writing this post) and I had to find a fast way to deploy new changes to the server.
I'm pretty confident with Capistrano so, although Ghost doesn't use Ruby, I decided to use it to manage deployments.
A cool gem allow node apps to be deployed with Capistrano: capistrano-node-deploy

This is the Gemfile:

source 'https://rubygems.org'  
gem 'capistrano', '~> 2.15.5'  
gem 'capistrano-node-deploy', '~> 1.2.14'  
gem 'capistrano-shared_file', '~> 0.1.3'  
gem 'capistrano-rbenv', '~> 1.0.5'  

If you don't use rbenv just remove the related line in the Gemfile and change the Capfile accordingly.

This configuration works well, but it has some problem if you use nvm instead of a system-wide installation of node and npm.

To fix them I had to add some variables (nvm_path, node_binary and npm_binary) and totally override the node:install_packages task. Whithout this changes the deploy task ends with messages like:

/usr/bin/env: node
No such file or directory  

or:

node: not found  

This isn't really a good way, because you must change the nvm_path every time you upgrade nvm, but it's the only way I actually found :)

I also changed the app_command variable to launch node ~/apps/tommyblue.it/current/index instead of node ~/apps/tommyblue.it/current/core/index in the upstart script. The second command doesn't actually works although is the gem's default.

This is the full content of the Capfile (remember to change to your own):

require "capistrano/node-deploy"  
require "capistrano/shared_file"  
require "capistrano-rbenv"  
set :rbenv_ruby_version, "2.1.1"

set :application, "tommyblue.it"  
set :user, "<USERNAME>"  
set :deploy_to, "/home/#{user}/apps/#{application}"

set :app_command, "index"

set :node_user, "<USERNAME>"  
set :node_env, "production"  
set :nvm_path, "/home/<USERNAME>/.nvm/v0.10.26/bin"  
set :node_binary, "#{nvm_path}/node"  
set :npm_binary, "#{nvm_path}/npm"

set :use_sudo, false  
set :scm, :git  
set :repository,  "<GIT REPO URL>"

default_run_options[:pty] = true  
set :ssh_options, { forward_agent: true }

server "<SERVER HOSTNAME OR IP>", :web, :app, :db, primary: true

set :shared_files,    ["config.js"]  
set :shared_children, ["content/data", "content/images"]

set :keep_releases, 3

namespace :deploy do  
  task :mkdir_shared do
    run "cd #{shared_path} && mkdir -p data images files"
  end

  task :generate_sitemap do
    run "cd #{latest_release} && ./ghost_sitemap.sh #{latest_release}"
  end
end

namespace :node do  
  desc "Check required packages and install if packages are not installed"
  task :install_packages do
    run "mkdir -p #{previous_release}/node_modules ; cp -r #{previous_release}/node_modules #{release_path}" if previous_release
    run "cd #{release_path} && PATH=#{nvm_path}:$PATH #{npm_binary} install --loglevel warn"
  end
end

after "deploy:create_symlink", "deploy:mkdir_shared"  
after "node:restart", "deploy:generate_sitemap"  
after "deploy:generate_sitemap", "deploy:cleanup"  

Recentemente non sono riuscito ad aggiornare molto questo blog. Spesso la causa non è stata la mancanza di contenuti, ma la non immediatezza della piattaforma. È vero, con Jekyll mi sono divertito e l'idea di servire un sito statico secondo me è grandiosa, ma l'implementazione è effettivamente molto scarna e questo mi ha spesso fermato quando avrei voluto iniziare a scrivere un articolo e basta.

Da un po' di tempo mi sto quindi guardando intorno alla ricerca di una nuova piattaforma, nella convinzione che comunque non voglio usare Wordpress.

Per l'appunto nelle ultime settimane ho iniziato a riscrivere l'interfaccia di Rubyfatt utilizzando Ember e, quasi contemporaneamente, ho iniziato a sentir parlare di Ghost che ha appunto deciso di riscrivere l'interfaccia di amministrazione con Ember. Mi è subito sembrata un'occasione da cogliere al volo.

Sto lavorando sulla migrazione da Jekyll a Ghost, molto presto le pagine del blog potrebbero quindi cambiare aspetto per l'ennesima volta



Recently I decided to stop bothering me with "standard" system administration and I switched to a more devops-oriented administration using Chef.

After a few days I found a very useful gem, knife-esx which is a Knife plugin to create and bootstrap new VMware virtual machines on the fly. I patched it and its dependency gem esx because it wasn't possible to set the number of cores per virtual CPU, but now it is so, to create a new virtual machine and bootstrap it you just need to type:

 knife esx vm create \
       --free-license \
       --esx-host <MY_ESXI_HOST> \
       --esx-templates-dir /vmfs/volumes/datastore1/esx-gem/templates \
       --vm-name <NEW_VM_NAME> \
       --guest-id ubuntu64Guest \
       --use-template ubuntu-12.04-x64_template.vmdk \
       --distro ubuntu12.04-gems \
       --vm-memory 512 \
       --vm-cpus 8 \
       --vm-cpu-cores 4 \
       -x <SSH_USERNAME> \
       -i ~/.ssh/id_rsa \
       --node-name <CHEF_NEW_NODE_NAME> \
       -r 'role[base],role[esx-vm]'

In various mailing lists I read a lot of threads about deploying a Rails app. I want to contribute to the topic with this post, where I'll describe how I'm now deploying my rails apps in a VPS (actually it's not a virtual but a physical server, but it's the same..).

In the past I used Pushion Passenger but it was a very young project and when Unicorn showed up, I felt in love :)
I wrote a similar post some years ago, the idea is the same, but the structure is now more solid.

The tools I'm now using are:

  • Unicorn as Rack HTTP server
  • Nginx as proxy server
  • Supervise (part of Daemontools) to monitor the unicorn app
  • Capistrano to manage the deploy
  • Rbenv to manage the ruby environment

The server's o.s. is Ubuntu 12.04 LTS.

Rbenv

To install rbenv and ruby-build:

sudo apt-get install build-essential zlib1g-dev openssl libopenssl-ruby1.9.1 libssl-dev libruby1.9.1 libreadline-dev git-core
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
exec $SHELL -l
mkdir -p ~/.rbenv/plugins
cd ~/.rbenv/plugins
git clone git://github.com/sstephenson/ruby-build.git
rbenv install 2.0.0-p247
rbenv rehash
rbenv global 2.0.0-p247
rbenv local 2.0.0-p247

Just check if everything went ok:

$ ruby -v
ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-linux]

Read this post to switch to Rbenv if you're using RVM

Capistrano

Create the required folder in the server:

mkdir ~/apps

Now configure your app to be deployed:

cd ~/my_app_path
echo "gem 'capistrano'" >> Gemfile
bundle install
capify .

edit the Capfile file if you need, then edit config/deploy.rb. This is a working example:

require "bundler/capistrano"  
require "capistrano-rbenv"  
set :rbenv_ruby_version, "2.0.0-p247"

set :user, "server_username"  
set :application, "my_app"  
set :deploy_to, "/home/#{user}/apps/#{application}"  
set :deploy_via, :remote_cache

set :use_sudo, false  
set :scm, :git  
set :repository,  "your_app_git_repo"

default_run_options[:pty] = true  
set :ssh_options, { forward_agent: true }

server "my_server.my_domain", :web, :app, :db, primary: true

set :branch, "master"  
set :rails_env, "production"

after "deploy", "deploy:cleanup" # keep only the last 5 releases

# Daemontools start/stop
namespace :deploy do  
  %w[start stop restart].each do |command|
    desc "#{command} unicorn server"
    task command, roles: :app, except: {no_release: true} do
      if command == "start"
        sudo "/usr/bin/svc -u /etc/service/my_app"
      elsif command == "stop"
        sudo "/usr/bin/svc -d /etc/service/my_app"
      else
        sudo "/usr/bin/svc -t /etc/service/my_app"
      end
    end
  end

  task :setup_config, roles: :app do
    run "mkdir -p #{shared_path}/config"
  end
  after "deploy:setup", "deploy:setup_config"

  task :symlink_config, roles: :app do
    run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
  end
  after "deploy:finalize_update", "deploy:symlink_config"

  desc "Make sure local git is in sync with remote."
  task :check_revision, roles: :web do
    unless `git rev-parse HEAD` == `git rev-parse origin/#{branch}`
      puts "WARNING: HEAD is not the same as origin/#{branch}"
      puts "Run `git push` to sync changes."
      exit
    end
  end
  before "deploy", "deploy:check_revision"
end

You can create the required folders with:

cap deploy:setup

Log in to the server and check the ~/apps/my_app/shared folder. Add these folders if they don't exist:

cd ~/apps/my_app/shared
mkdir config logs pids sockets

in the config folder create a database.yml file with the rails production environment configurations.

Unicorn

Add the unicorn gem to the rails app:

cd ~/my_app_path
echo "gem 'unicorn'" >> Gemfile
bundle install

Add the unicorn configuration in the shared/config/unicorn.rb file (in the server):

worker_processes 2  
working_directory "/home/my_user/apps/my_app/current" # available in 0.94.0+  
listen "/home/my_user/apps/my_app/shared/sockets/my_app.sock", :backlog => 64  
timeout 30  
pid "/home/my_user/apps/my_app/shared/pids/unicorn.pid"  
stderr_path "/home/my_user/apps/my_app/shared/log/unicorn.stderr.log"  
stdout_path "/home/my_user/apps/my_app/shared/log/unicorn.stdout.log"

preload_app true  
GC.respond_to?(:copy_on_write_friendly=) and  
  GC.copy_on_write_friendly = true

before_fork do |server, worker|  
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|  
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

To launch unicorn I create the ~/service folder. There I create a folder for each project. So:

mkdir -p ~/service/my_app

Then the required files.

~/service/my_app/run (must be executable)

#!/bin/bash

exec su - my_user -c '/home/my_user/service/load_my_app.sh bundle exec unicorn_rails -E production -c /home/my_user/apps/my_app/shared/config/unicorn.rb'  
# If you want to launch unicorn manually use te line below instead of the line above (use sudo!). Useful for debugging
# exec su - my_user -c '/home/my_user/service/load_my_app.sh bundle exec unicorn_rails -E production -l /home/my_user/apps/my_app/shared/sockets/my_app.sock'

~/service/loadmyapp.sh

#!/bin/bash

export RAILS_ENV="production"  
export PATH="$HOME/.rbenv/bin:$PATH"  
eval "$(rbenv init -)"  
cd /home/my_user/apps/my_app/current/  
exec $@

As pointed in the comment, you can use the run file to test the app, just modify the file then launch it as root:

cd ~/service/my_app
sudo ./run

You'll see the familiar unicorn startup process, then it will listen for connections in the given socket.

That's it, now jump to supervise

Daemontools

Install the required packages:

sudo apt-get install daemontools daemontools-run

After this command you'll have the svc executable. Before using it, create the symbolic link in the /etc/service folder:

cd /etc/service
sudo ln -s /home/my_user/service/my_app

Supervise automatically launches, at server sturtup, the run executable in the folders present in /etc/service/

To manually startup the app, use svc:

sudo svc -u /etc/service/my_app

This is the same command used by capistrano during deploy (se configuration above).

Nginx

If everything went as expected, the rails app is running and listening for connections in the unix socket at /home/my_user/apps/my_app/shared/sockets/my_app.sock. Now configure Nginx to use that socket.

/etc/nginx/sites-available/www.myapp.mydomain

upstream backend_my_app {  
  server unix:/home/my_user/apps/my_app/shared/sockets/my_app.sock fail_timeout=0;
}

server {  
    listen [::]:80;

  client_max_body_size 4G;
  keepalive_timeout 5;

  try_files $uri/index.html $uri.html $uri @app;

    root /home/my_user/apps/my_app/current/public/;
    index index.html index.htm;

    server_name my_app.my_domain www.my_app.my_domain;

  location @app {
    gzip_static on;
    proxy_pass http://backend_my_app;
    proxy_redirect off;

    proxy_set_header        Host    $host;
    proxy_set_header        X-Real-IP       $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;

    root /home/my_user/apps/my_app/current/public/;
    index  index.html index.htm;
  }

  location ~* ^/font.+\.(svg|ttf|woff|eot)$ {
    root /home/my_user/apps/my_app/current/public/;
  }

  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /var/www/nginx-default;
  }

  access_log  /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;
}

Symlink this file in /etc/nginx/sites-enabled/ and restart nginx, your app should be online.

When you'll deploy a new version of the app, Capistrano will require the sudo password to send a TERM signal to supervise, which will restart the rails app.

That's it, it seems a lot of configuration (and maybe is) but it works great and there are very little differences between the projects, so CTRL-C+CTRL-V works great! :)


Ho avuto il piacere di vedere questo interessante video del talk di Pat Shaughnessy al Goruko 2013, intitolato Functional Programming and Ruby in cui, appunto, Pat introduce alla programmazione funzionale e mostra come Ruby abbia moltissime di queste caratteristiche.

Eccolo qua, ve lo consiglio vivamente!