Еще одна инструкция по деплою рельсового приложения.

Настройка окружения сервера

1. Добавляем юзера deploy с правами админа.

# server
sudo adduser deploy
sudo adduser deploy sudo
su deploy

2. Добавляем SSH-ключи на нашу локальную машину и авторизуем этот ключ на сервере с помощью утилиты ssh-copy-id, предварительно установив ее. (IPADDRESS — айпи адрес для доступа по SSH).

# local
brew install ssh-copy-id
ssh-copy-id deploy@IPADDRESS

Далее всё делаем из-под этого нового пользователя deploy.

3. Добавляем SSH-ключ на сервере и авторизуем этот ключ в нашем гит-репозитории, а точнее на гитхабе (http://github.com/USER/REPO/settings/keys).

# server
ssh-keygen -t rsa -C "sergeyshishkalov@gmail.com"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa

После этого убираем авторизацию по паролю (это необходимо для некоторых тасков капистрано). Для этого в /etc/sudoers меняем это:

%sudo ALL=(ALL:ALL) ALL

на это:

%sudo ALL=(ALL) NOPASSWD:ALL

4. Обновляем все пакеты.

# server
sudo apt-get update
sudo apt-get upgrade

5. Ставим гит, node.js, npm и bower.

# server
sudo apt-get install git-core nodejs npm
sudo ln -s /usr/bin/nodejs /usr/bin/node
sudo npm install -g bower

6. Ставим rbenv и последнюю версию руби.

# server 
cd ~
git clone git://github.com/sstephenson/rbenv.git .rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
exec $SHELL
 
git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
exec $SHELL
 
rbenv install 2.1.3
rbenv global 2.1.3
rbenv rehash

Попросим руби не устанавливать документацию для скачиваемых гемов.

# server
echo "gem: --no-ri --no-rdoc" > ~/.gemrc

Пока ставится руби, можно открыть параллельную сессию и продолжить настройку сервера.

7. Устанавливаем bundler.

# server
gem install bundler
rbenv rehash

8. Ставим nginx. На сервере в конце файла /etc/apt/sources.list добавляем следующие строки.

sudo vim /etc/apt/sources.list
 
# Then, inside append to /etc/apt/sources.list:
# Nginx official repository
deb http://nginx.org/packages/ubuntu/ trusty nginx
deb-src http://nginx.org/packages/ubuntu/ trusty nginx

Добавляем ключи.

# server
wget http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key

Ставим.

# server
sudo apt-get update
sudo apt-get install nginx

Удаляем дефолтные конфиги и перезапускаем веб-сервер.

# server
sudo rm /etc/nginx/conf.d/*
sudo service nginx restart

9. Ставим и настраиваем Postgresql. Для этого сначала ставим необходимые пакеты.

# server
sudo apt-get install postgresql postgresql-contrib libpq-dev

Настройка конфигов

Внимание, далее идут настройки, специфичные для вашего проекта. Будьте внимательны при указании имен пользователей, имени приложения, паролей.

1. Для доступа к базе данных создадим нового пользователя s0ber. Зададим ему какой-нибудь новый пароль. И создадим базу данных. Эти данные нужно будет потом ввести в файле database.yml. Потом перезапустим наш постгрес.

# server
sudo su postgres -c psql
 
# then in psql session
create user s0ber with password 'mydbpassword';
alter user s0ber superuser;
create database my_app_production;
grant all privileges on database my_app_production to s0ber;
 
# logging out from psql session with \q
sudo service postgresql restart

2. Будем хранить конфиги в директории приложения в папке /shared/config, при этом занесем ее в гитигнор. Перенесем в нее файлы database.yml и secrets.yml, при этом вместо оригинальных файлов поставим симлинки.

# local
cd ~/projects/my_app
mkdir -p shared/config
mv ./config/database.yml ./shared/config
mv ./config/secrets.yml ./shared/config
ln -fs .././shared/config/database.yml ./config/database.yml
ln -fs .././shared/config/secrets.yml ./config/secrets.yml

Таки образом, все наши конфиги будут храниться в одном месте. Позже мы добавим в эту директорию настройки серверов Nginx и Unicorn.

3. Настройка Nginx. Далее в инструкции. my_app — название rails-приложения, и, соответственно, его директория, deploy — имя пользователя и, соответственно, его домашняя директория на сервере.

Локально создадим конфиг в shared/config/nginx.conf.

# local
cd ~/projects/my_app
vim shared/config/nginx.conf

Содержимое конфига.

upstream unicorn {
    server unix:/home/demo/application/shared/run/unicorn.sock fail_timeout=0; 
}
 
server { 
    listen 80 default; 
    root /home/deploy/my_app/current/public;
    try_files $uri/index.html $uri.html $uri @app;
    location ^~ /assets/ { 
        expires max;
        add_header Cache-Control public;
    }
    location @app {
        proxy_pass http://unicorn;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_buffering off;
        chunked_transfer_encoding on;
    }
    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /home/deploy/my_app/current/public;
    }
}

4. Настройка Unicorn. Добавим гем в гемфайл.

group :production do
  gem 'unicorn', '~> 4.8.3'
end

Добавим конфиг в shared/config/unicorn.rb.

# working directories of a project on a server
root        = '/home/deploy/my_app'
rails_root  = "#{root}/current"
 
# unicorn proccesses pid's
pidfile     = "#{root}/shared/run/unicorn.pid"
pidfile_old = pidfile + '.oldbin'
pid pidfile
 
# main params
worker_processes 3
preload_app true
timeout 30
 
# socket path
listen "#{root}/shared/run/unicorn.sock", backlog: 1024, tcp_nopush: false
 
# logs path
stderr_path "#{rails_root}/log/unicorn_error.log"
stdout_path "#{rails_root}/log/unicorn.log"
 
# garbage collector settings
GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
 
# before server start instructions
before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "#{rails_root}/Gemfile"
end
 
# instructions for managing workers and db connection
 
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!
 
  if File.exists?(pidfile_old) && server.pid != pidfile_old
    begin
      Process.kill("QUIT", File.read(pidfile_old).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end
 
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

Капистрано

1. Добавим необходимые гемы в гемфайл.

# Use Capistrano for deployment
gem 'capistrano', '~> 3.2.1'
gem 'capistrano-bundler', '~> 1.1.3'
gem 'capistrano-rails', '~> 1.1.2'
gem 'capistrano3-unicorn', '~> 0.2.1'
gem 'capistrano-rbenv', github: "capistrano/rbenv"

2. Содержимое Capfile.

# Load DSL and Setup Up Stages
require 'capistrano/setup'
 
# Includes default deployment tasks
require 'capistrano/deploy'
 
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails'
require 'capistrano3/unicorn'
 
set :rbenv_type, :user # or :system, depends on your rbenv setup
set :rbenv_ruby, '2.1.3'
 
# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }

3. Содержимое config/deploy/production.rb.

server 'ip сервера или домен', user: 'deploy', roles: %w{web app db}

4. Содержимое config/deploy.rb.

# config valid only for Capistrano 3.2.1
lock '3.2.1'
 
set :username, 'deploy'
set :application, 'my_app'
set :rails_env, 'production'
set :repo_url, 'git@github.com:USER/REPO.git'
 
set :deploy_to, "/home/#{fetch(:username)}/#{fetch(:application)}"
 
# capistrano3/unicorn settings
set :unicorn_pid, "#{shared_path}/run/unicorn.pid"
set :unicorn_config_path, "#{shared_path}/config/unicorn.rb"
 
set :log_level, :info
 
# Default value for :linked_files is []
set :linked_files, %w{config/secrets.yml config/database.yml}
 
# Default value for linked_dirs is []
set :linked_dirs, %w{public/upload}
 
namespace :setup do
  desc 'Uploading config files to server'
  task :upload_config do
    on roles(:all) do
      execute :mkdir, "-p #{shared_path}"
      ['shared/config', 'shared/run'].each do |f|
        upload!(f, shared_path, recursive: true)
      end
    end
  end
end
 
namespace :nginx do
  desc 'Creating symlink in /etc/nginx/conf.d to an application nginx.conf'
  task :append_config do
    on roles :all do
      sudo :ln, "-fs #{shared_path}/config/nginx.conf /etc/nginx/conf.d/#{fetch(:application)}.conf"
    end
  end
  desc 'Reload nginx'
  task :reload do
    on roles :all do
      sudo :service, :nginx, :reload
    end
  end
  desc 'Restart nginx'
  task :restart do
    on roles :all do
      sudo :service, :nginx, :restart
    end
  end
  after :append_config, :restart
end
 
namespace :deploy do
  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
      invoke 'unicorn:restart'
    end
  end
 
  task :install_js_dependencies do
    on roles(:all) do
      within release_path do
        execute :rake, 'bower:install'
      end
    end
  end
 
  before :compile_assets, :install_js_dependencies
  after :publishing, :restart
  after :finishing, :cleanup
 
  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end
end

Здесь мы задаем несколько тасков. Таск setup:upload_config загружает все файлы из нашей директории с конфигами в соответствующую директорию на нашем сервере. Необходимо вручную запустить этот таск, чтобы импортировать созданные конфиги на сервер.

# local
bundle exec cap production setup:upload_config

Таски из неймспейса nginx ответственны за управление nginx-ом. Так же там есть один таск для импорта конфига. Его тоже нужно прогнать один раз.

# local
bundle exec cap production nginx:append_config

5. Деплой! Всё готово. Запускаем с помощью.

bundle exec cap production deploy

Внимание!

Чтобы пользоваться рельсовой консолью на Ubuntu 14.04, добавьте следующий гем в гемфайл.

# Fix for rails console under ubuntu 14.04
gem 'rb-readline'