
SSH ключ
Прежде, чем использовать инструменты, перечисленные в заголовке, необходимо подготовить сервер на котором мы собираемся все организовать. Предположим, вы только что установили свежую Ubuntu 10.04 LTS на сервер (+ завели в процессе первого пользователя), и подняли там OpenSSH daemon. Все! Отныне сервер не должен быть прикасаем для рук, ног и других конечностей — работать с ним мы будем только удаленно, а для этого на своей рабочей машине следует выполнить:
ssh-copy-id vasya@rails-production.example.com
где vasya — это имя пользователя на сервере, от имени и прав которого будет осуществляться деплой, а rails-production.example.com — это адрес или имя только что поднятого вами сервера. После ввода будет необходимо согласиться с добавлением хоста в список known hosts у вас на машине — ничего страшного — это нормально. И ввести Васин пароль. Это будет последним разом, когда вы будете вводить пароль Василия. Теперь доступ к серверу возможен по ssh ключу и ничего вводить не надо.Казалось бы, это основы о которых и упоминать-то не стоит — но всегда есть определенный процент людей, имеющий альтернативную точку зрения на доступ к машине по ssh ключам. Для них я могу посоветовать мазь от артрита суставов кисти, остальным же предлагаю просто поверить, что ssh ключи — это благо.
База данных
Здесь я лишь дам подсказки по установке нужных пакетов для двух самых популярных СУБД:
sudo apt-get install mysql-server mysql-client libmysqld-dev # MySQL
sudo apt-get install postgresql postgresql-client postgresql-server-dev #Postgresql
Настройка СУБД на сервере — это тема отдельной статьи, поэтому предположим, что с этим вы можете справиться самостоятельно. Поэтому — тадааам! СУБД запущена и работает.Rvm
Rvm — это средство управления версиями Ruby в системе, позволяющее создавать отдельные «окружения» из гемов, что в нашем случае не важно. Если рассмотреть концепции bundler и гемсетов Rvm, то может возникнуть чувство, что они созданы для одной и той же цели — изолировать окружение для работы конкретного приложения. Bundler — это замечательное средство разрешения зависимостей гемов, к тому же Rails 3 по умолчанию работает именно с ним. И вообще раз уж об этом зашла речь — я рекомендую использовать bundler для Rails 2.3.x, как это можно сделать описано здесь.
Rvm нам нужен только для того, чтобы без труда переключаться между разными версиями Ruby, и такая необходимость скорее всего возникнет на сервере, где одновременно будут крутиться приложения, написанные на разных версиях Ruby on Rails. У Rvm есть и свои противники. Нет и на самом деле — если вы абсолютно точно уверены, что на этой железке никогда не будет работать больше одной версии ruby, то будет правильнее установить какой-нибудь ree как системный интерпретатор ruby и
Если вы уже ознакомились с документацией, то наверное заметили, что существует два способа установки Rvm — от root (так называемая system wide install) и обычная — для простого пользователя. Так вот, опять я хочу испытать вашу веру — не устанавливайте от root. Не зря же мы создавали пользователя, ответственного за деплой. Поэтому зайдя на сервер под нашим Василием выполняем следующую последовательность команд:
sudo apt-get install git-core curl #Это для того, чтобы заработала установка Rvm.
curl -L https://get.rvm.io | bash -s stable --ruby
type rvm | head -1
Последняя команда должна выдать «rvm is a function» или аналог на русском языке. Если этого не произошло — стоит начать изучение отсюда и до момента — «пока оно не заработает».Ruby
Выбор версии руби зависит от того, какая версия рельс используется в приложении: так для 2.3.x предпочтительнее использовать Ruby Enterprise Edition, для 3.x рекомендуют использовать 1.9.3. В моем случае — это приложение на 2.3.x и ree соответственно. И если для установки ruby 1.9.3 ничего экстраординарного от системы не требуется, то для установки ree необходимо несколько системных пакетов:
sudo apt-get install build-essential bison openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev
rvm install ree-1.8.7-2011.03 # Устанавливаем ree
rvm ree # Указываем, какой интерпретатор Ruby использовать. Если вам так больше нравится - создайте себе гемсет вот так: rvm ree@myapp --create. Но с Bundler он становится попросту ненужным.
gem install bundler # Единственный gem, который мы поставим руками.
sudo mkdir -p /srv/myapp # Создаем директорию, в которой будет находиться наше приложение.
sudo chown -R vasya:vasya /srv/myapp # Передаем права на владение Василию (так как директория пуста, параметр -R можно пропустить).
Nginx
Раз уж мы будем использовать Unicorn, то без Nginx нам никак не обойтись — есть у Unicorn такая особенность — не может он работать с медленными клиентами. Есть, правда, его аналог, который может — Rainbows, но Nginx сам по себе исключительно полезен, хорош и прост в эксплуатации. Инструкции по установке вы можете найти на сайте автора этого замечательного сервера — Игоря Сысоева. Я лишь приведу здесь простой init скрипт для запуска nginx и nginx.conf:
#! /bin/sh
EXEC_PATH="/usr/local/nginx/sbin/nginx"
case "$1" in
start)
echo "Starting NginX"
start-stop-daemon --start --exec $EXEC_PATH
;;
stop)
echo "Stopping NginX"
start-stop-daemon --stop --exec $EXEC_PATH
;;
restart)
echo "Stopping NginX"
start-stop-daemon --stop --exec $EXEC_PATH
sleep 1
echo "Starting NginX"
start-stop-daemon --start --exec $EXEC_PATH
;;
*)
echo "Usage: {start|stop|restart}"
exit 1
;;
esac
exit 0
worker_processes 1; # Более одного рабочего процесса обычно не требуется.
user vasya vasya; # Пользователь с правами которого запускается worker - он же пользователь, от которого осуществляется деплой.
pid /tmp/nginx.pid; # Задаем местоположение файла с идентификатором текущего мастер-процесса Nginx.
error_log /tmp/nginx.error.log;
events {
worker_connections 1024; # Стандартный показатель количества одновременно открытых соединений рабочего процесса.
accept_mutex off; # Ну и раз уж воркер у нас один - отключаем.
}
http {
# Дальше немного стандартных директив - если нет особого желания менять здесь что-то - не меняйте:
include mime.types;
default_type application/octet-stream;
access_log /tmp/nginx.access.log combined;
sendfile on;
tcp_nopush on;
tcp_nodelay off;
gzip on;
# Теперь самая сладкая часть. Далее происходит указание upstream сервера. Так как все происходит в рамках одной машины, слушать апстрим лучше через сокет.
upstream myapp_server {
server unix:/srv/myapp/shared/unicorn.sock fail_timeout=0; # Местоположение сокета должно совпадать с настройками файла config/unicorn.rb от корня вашего приложения.
}
server {
listen 80 default deferred; # Опять же, если на одном и том же ip находится несколько серверов, то эта строка будет выглядеть как-то так myapp.mydomain.ru:80
client_max_body_size 1G; # Максимальный размер тела запроса (а простым языком - ограничение на размер заливаемого на сервер файла).
server_name myapp.mydomain.ru; # Имя сервера
keepalive_timeout 5;
root /srv/myapp/current/public; # Эта строка всегда должна указывать в директорию public Rails приложения. А current там потому что деплой происходит через Capistrano
try_files $uri/index.html $uri.html $uri @myapp; # Имя переменной не важно - главное, чтобы в блоке location ниже было аналогичное
location @myapp {
proxy_pass http://myapp_server; # Часть после http:// должна полностью соответствовать имени в блоке upstream выше.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
error_page 500 502 503 504 /500.html;
location = /500.html {
root /srv/myapp/current/public;
}
}
}
Стоит сразу предупредить, что когда Nginx одновременно обслуживает несколько приложений то блоки
server { ... }
стоит выносить в отдельные файлы в директории /usr/local/nginx/conf/vhosts
а в nginx.conf писать include /usr/local/nginx/conf/vhosts/*
— в примере этого не сделано для наглядности.Unicorn
Unicorn можно установить для всей системы, но делать этого не следует — гораздо правильнее включить в Gemfile соответствующий гем:
gem 'unicorn'
и запускать Unicorn командой bundle exec
. Кстати, рекомендация распространяется не только Unicorn, но и на любые исполняемые файлы, идущие вместе с гемами. Установка Unicorn в рамках конкретного приложения позволит вам без проблем завести сколько угодно приложений на одной машине.Далее я приведу пример конфигурации сервера, которая обеспечивает так называемый zero downtime deploy. Итак, config/unicorn.rb:
deploy_to = "/srv/myapp"
rails_root = "#{deploy_to}/current"
pid_file = "#{deploy_to}/shared/pids/unicorn.pid"
socket_file= "#{deploy_to}/shared/unicorn.sock"
log_file = "#{rails_root}/log/unicorn.log"
err_log = "#{rails_root}/log/unicorn_error.log"
old_pid = pid_file + '.oldbin'
timeout 30
worker_processes 4 # Здесь тоже в зависимости от нагрузки, погодных условий и текущей фазы луны
listen socket_file, :backlog => 1024
pid pid_file
stderr_path err_log
stdout_path log_file
preload_app true # Мастер процесс загружает приложение, перед тем, как плодить рабочие процессы.
GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=) # Решительно не уверен, что значит эта строка, но я решил ее оставить.
before_exec do |server|
ENV["BUNDLE_GEMFILE"] = "#{rails_root}/Gemfile"
end
before_fork do |server, worker|
# Перед тем, как создать первый рабочий процесс, мастер отсоединяется от базы.
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
# Ниже идет магия, связанная с 0 downtime deploy.
if File.exists?(old_pid) && server.pid != old_pid
begin
Process.kill("QUIT", File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
# someone else did our job for us
end
end
end
after_fork do |server, worker|
# После того как рабочий процесс создан, он устанавливает соединение с базой.
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
end
Capistrano
Ну вот мы уже настроили все
group :development do
gem "capistrano"
gem "rvm-capistrano"
end
После установки гема выполняем команду:bundle exec capify .
и получаем почти пустой файл config/deploy.rb. Я же приведу пример файла под наши нужды:require 'rvm/capistrano' # Для работы rvm
require 'bundler/capistrano' # Для работы bundler. При изменении гемов bundler автоматически обновит все гемы на сервере, чтобы они в точности соответствовали гемам разработчика.
set :application, "myapp"
set :rails_env, "production"
set :domain, "vasya@rails-production.example.com" # Это необходимо для деплоя через ssh. Именно ради этого я настоятельно советовал сразу же залить на сервер свой ключ, чтобы не вводить паролей.
set :deploy_to, "/srv/#{application}"
set :use_sudo, false
set :unicorn_conf, "#{deploy_to}/current/config/unicorn.rb"
set :unicorn_pid, "#{deploy_to}/shared/pids/unicorn.pid"
set :rvm_ruby_string, 'ree' # Это указание на то, какой Ruby интерпретатор мы будем использовать.
set :scm, :git # Используем git. Можно, конечно, использовать что-нибудь другое - svn, например, но общая рекомендация для всех кто не использует git - используйте git.
set :repository, "git@github.com:myprojects/myapp.git" # Путь до вашего репозитария. Кстати, забор кода с него происходит уже не от вас, а от сервера, поэтому стоит создать пару rsa ключей на сервере и добавить их в deployment keys в настройках репозитария.
set :branch, "master" # Ветка из которой будем тянуть код для деплоя.
set :deploy_via, :remote_cache # Указание на то, что стоит хранить кеш репозитария локально и с каждым деплоем лишь подтягивать произведенные изменения. Очень актуально для больших и тяжелых репозитариев.
role :web, domain
role :app, domain
role :db, domain, :primary => true
before 'deploy:setup', 'rvm:install_rvm', 'rvm:install_ruby' # интеграция rvm с capistrano настолько хороша, что при выполнении cap deploy:setup установит себя и указанный в rvm_ruby_string руби.
after 'deploy:update_code', :roles => :app do
# Здесь для примера вставлен только один конфиг с приватными данными - database.yml. Обычно для таких вещей создают папку /srv/myapp/shared/config и кладут файлы туда. При каждом деплое создаются ссылки на них в нужные места приложения.
run "rm -f #{current_release}/config/database.yml"
run "ln -s #{deploy_to}/shared/config/database.yml #{current_release}/config/database.yml"
end
# Далее идут правила для перезапуска unicorn. Их стоит просто принять на веру - они работают.
# В случае с Rails 3 приложениями стоит заменять bundle exec unicorn_rails на bundle exec unicorn
namespace :deploy do
task :restart do
run "if [ -f #{unicorn_pid} ] && [ -e /proc/$(cat #{unicorn_pid}) ]; then kill -USR2 `cat #{unicorn_pid}`; else cd #{deploy_to}/current && bundle exec unicorn_rails -c #{unicorn_conf} -E #{rails_env} -D; fi"
end
task :start do
run "bundle exec unicorn_rails -c #{unicorn_conf} -E #{rails_env} -D"
end
task :stop do
run "if [ -f #{unicorn_pid} ] && [ -e /proc/$(cat #{unicorn_pid}) ]; then kill -QUIT `cat #{unicorn_pid}`; fi"
end
end
P.S. Если у кого-то возникнут предложения по улучшению изложенного материала или конструктивные замечания буду рад прочесть и исправить.