Связка rvm + Rails + Nginx + Unicorn или деплоим рельсы правильно

Целью данной заметки я ставлю в подробностях описать организацию сервера для Rails приложений в самой популярной на данный момент связке: rvm + Rails + Nginx + Unicorn. К написанию статьи побудило отсутствие полной пошаговой документации по этой связке, понятной не только ядреным профессионалам этой области. Далее я попытаюсь подробно, шаг за шагом, описать идеологически правильный процесс организации сервера для обслуживания нескольких Rails приложений (на примере одного) — если у вас есть абсолютная уверенность в том, что на подопытной машине никогда не будет работать более одного приложения — настройка может быть существенно короче и проще. Хочу предупредить, что тонкости, касающиеся работы приложения под высокой нагрузкой в статье не описываются, т.к. цель ставилась иная — заставить работать приложение в связке и сократить количество конфликтов с другими приложениями до минимума.

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 — это позволит держать все вещи в порядке.

Если вы уже ознакомились с документацией, то наверное заметили, что существует два способа установки 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

Ну вот мы уже настроили все, что только можно базовые вещи, поэтому следует разобраться с Capistrano. Для начала стоит поместить гем capistrano в группу :development нашего Gemfile, а также поместить туда же rvm-capistrano для интеграции с rvm (для версий rvm >= 1.1.0):
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. Если у кого-то возникнут предложения по улучшению изложенного материала или конструктивные замечания буду рад прочесть и исправить.
Поделиться публикацией
Комментарии 135
    +2
    Отличный материал! Таких кастомных статей оч мало.
    +5
    А слабо установить это все чз dpkg с созданием deb пакетов? а то накидать софта в систему особого ума ненадо…
      0
      Чем вам rubygems, как менеджер пакетов, не угодил?
        +3
        я думаю что предполагалась установка руби деб пакетом, что есть совсем необосновано, т.к. rvm более правилен (особенно когда нужно несколько версий руби). да и удаление занимает несколько секунд — rm -rf ~/.rvm (удаляем и рвм и все руби)
          0
          Могу предположить, что необходимостью держать (а главное использовать) два менеджера пакетов.
        0
        На гитхабе рассказывали как у них все работает — github.com/blog/517-unicorn

        Конфиг юникорна — gist.github.com/206253
          0
          Да — на гитхабе все «немножко» посложнее. Теперь хоть прочту, что значит вот эта строка:
          GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
          — там пояснение есть.
          +1
          Самая популярная на данный момент связка, имхо, nginx+passenger. И ставится в разы проще.
            +3
            По моим наблюдениям passenger на продакшн серверах начинает уходить, сменяясь на Unicorn.
              +1
              Robot Unicorn Attack
                +2
                А преимущества у Unicorn перед Passenger есть? Passenger автоматический стартует или убивает лишние инстансы и делает кучу еще всего. Как с этим у Unicorn?
                  0
                  unicorn.bogomips.org/ там по пунктам написано что может.
                    +1
                    Это я читал, но там написана общие фичи архитектуры, мне больше интересны реальные преимущества (скорость обработки запросов, использованная память и т.д.) перед Passenger. Везде старые статьи 2009 года, за это время Passenger улетел вперед, хочется реальное сравнение.
                    Да и чисто по перечислению фич Passenger выглядит как-то более солидно.
                  0
                  Мое мнение, что passenger — это модуль для nginx/apache и он как бы «впаян» во frontend сервер и без него никуда. Используя Unicorn мы получаем четкое разделение на frontend/backend сервера — с точки зрения построения продакшн инфраструктуры такой подход, на мой взгляд, надежнее.

                  Пассажир подкупает простотой своей настройки, при этом не являясь чем-то плохим, и вполне имеет право на жизнь.
                    0
                  –1
                  только из-за шаред-хостингов (в которых всегда по дефолту есть апач). все серьёзные приложения даже не смотрят на пассажира, ибо он 1) практически не масштабируется 2) течёт 3) вместе с апачем ест памяти больше, чем unicorn или thin.
                    0
                    Апач вообще фи, про него ни слова не было. Nginx+passenger дают вполне неплохую производительность, zero-time-restart начиная с версии 3 и в настройке/установке просто как три копейки.
                      0
                      zero-time-restart это только у nginx. сами по себе рельсы всё равно environment порой минуты по 3 поднимают. если б thin'у не надо было рельсы поднимать, он бы тоже за секунды стартовал :)
                      во всяком случае на мой взгляд всё равно frontend <-> backend намного отказоустойчивее, производительнее и масштабируемее. пассажир — это всё-таки решение больше для тех, кто хочет меньше париться с поддержкой приложения, и кому не нужна высокая нагрузка.
                      кто-то выше говорил про автостарт: не знаю как unicorn, но thin умеет в одну команду весь свой кластер сделать сервисом, и если кто-то умирает, он сразу перезапускается
                        0
                        ZTR в юникорне и третьем пассажире одинаково работает. Старый инстанс обрабатывает запросы, пока поднимается новый. Как только новый загружен и готов работать, запросы переключаются на него, а старый выгружается. Nginx тут вообще не при делах, он не рестартится.
                      +1
                      1) Масштабируется отлично. В третьей версии пассажира появился Passanger Standalone режим. Вообще, у пассажира гораздо более правильная очередь, имхо, + неплохие возможности подтюнить его поведение в нужную сторону.
                      2) Пассажир перестал течь после 1 версии, 3 версия вообще очень хороша в этом плане.
                      3) А по поводу пассажир+апач — похоже на проблемы апача, т.к. пассажир+nginx довольно таки неприхотлив в памяти. А вообще у вас странное стравнение — пассажир+апач vs unicorn. Надо сравнивать «пассажир+nginx vs unicorn+nginx», т.к. unicorn все равно без nginx использовать это самоубийство ))
                      0
                      Плюсую. Тем более, что Passenger заточен под REE (или REE под Passenger), что дает достаточный бонус производительности.
                        +1
                        вот и возникает вопрос. производительность либо плюшки 1.9.2. я выбираю плюшки.
                          0
                          Когда пробовал 1.9.2, была туча гемов, которые на этой версии либо не заводились вообще, либо жутко баговали, поэтому как-то не впечатлился и вернулся на REE. А под REE лучше пассажира просто ничего нет.
                          Возможно, сейчас ситуация обстоит иначе, не спорю. Но пока продакшн-сервера будут сидеть на пассажире, а там посмотрим.
                            0
                            мы недавно перешли на Rails 3.0 и на Ruby 1.9.2 сразу. Да немного пришлось подправить код, но у нас было минимум правок. Скорость работы лучше, да и много вкусных вещей в 1.9
                      0
                      Unicorn очень медленно стартует
                        0
                        но рестартать мастер процесс не нужно(после деплоя), а воркеры подымаются очень быстро.
                        0
                        Спасибо за статью.

                        Пока только не понял, может ли Unicorn запускать разные приложения под разными версиями Ruby, что иногда очень хочется иметь. В связи с этим два вопроса:
                        Зачем rvm?
                        Чем это по большому счёту лучше, чем nginx+passenger?
                          0
                          два инстанса юникорна с разными конфигами и сокетами.
                            0
                            Да, может. Причем при подходе, описанном в статье, получается, что каждое приложение имеет собственный конфигурационный файл для unicorn и собственный бинарник unicorn (благодаря bundle exec). Пачек «один мастер — n воркеров» тоже будет несколько — одна на приложение. Сокеты юникорнов будут расположены в разных местах и для запуска нового приложения на сервере вам будет необходимо создать еще одну upstream секцию (лучше делать в отдельном файле в conf/vhosts) в nginx. Рестартуем nginx один раз — и больше этого не требуется при деплоях.

                            Rvm для легкой смены версий руби, если одному приложению требуется 1.8.7 или ree, а другому 1.9.2 — задавать их через rvm очень удобно.

                            Ответ на второй вопрос здесь: habrahabr.ru/blogs/ror/120368/#comment_3947007

                          0
                          автор, а вам не кажется слишком смелым слово «правильно» в заголовке?

                          вот я к примеру деплою с помощью git, вместо unicorn'а у меня thin, вместо rvm я собираю ruby из сорцов (всегда пользую 1.9.2, ибо пора забывать об 1.8), и в общем-то не понимаю, почему мой подход менее правильный.
                            0
                            Здесь не как у Эдиссона — может быть и несколько правильных способов :)

                            Основная идея — прийти к разделению fronend/backend части при эксплуатации приложения.

                            А показал я на одном из многочисленных примеров — просто я использовал набор именно этих инструментов и мне они удобны. Более того, я уверен, что их набор должен меняться в соответствии с конкретной задачей.
                              0
                              то что вы используете thin это ваше право.
                              а вот то что вы собираете из сорцов — значит у вас очень много свободного времени. как вы обновляете руби? ручками? в наше то прогрессивное время…
                              в любом случае удачи вам
                                0
                                вы либо ни разу не пользовались rvm, либо у вас супер-сервера и вы не замечаете, что он их тоже собирает из сорцов.

                                мне во всяком случае проще написать

                                wget что-то/ruby.tar.gz
                                tar -zxf ruby.tar.gz
                                ./confidure && make && make install


                                против трёх же команд по установке rvm, установке нужной версии руби и выбору её по дефолту.

                                и вам удачи
                                  +1
                                  ну тут дело в том что параметров для ./configure намного больше нужно и запоминать их влом, да конечно можно написать скрипт и т.д. но как-то проще rvm install 1.9.2 (даже ссылку копировать не нужно откуда качать)
                                    0
                                    я ещё в своей практике ни разу не сталкивался с тем, что нужно писать дополнительные команды к configure при установке рубей. один раз нужно было что-то дополнительно из сорцов докомпилить (типа zlib'а, а может и не оно), но это было так давно, что я уже и не вспомню.
                                      0
                                      до сих пор нужно zlib, openssl. не?
                                        0
                                        кстати да. руби очень странно относиться к новым либам openssl.
                                          0
                                          я уже давно свежий дистр не ставил, поэтому не вспомню, память короткая.
                                          по части этого вашего rvm'а оно конечно хорошо, что одной командой обновлять. только мне этого и не надо так часто. на днях было на одном из серверов, где rvm была: висело себе уже 2 месяца приложение, работало. обновил rvm'ом руби (1.9.2 аж с p0 до свежей стабильной), отказала половина вьюх, в которых использовалось самописное расширение Time чтоб расширить DateTime (для синтаксического сахара). Оказалось, что внезапно DateTime стал наследоваться от Date. и подобные баги не первые в моей практике, вплоть до того, что после обновления не загружается один-единственный файл из папки autoload'а (когда другие все загружаются).
                                          лично для себя я давно уяснил по части MRI: если работает, то лучше не трогать.
                                            0
                                            Ну у меня привычка другая. Читаю постоянно фиды с руби и с рельсов и правлю код до того как все депрекейшн вылетит таким образом.

                                            З.Ы. да — моя фанат )
                                              0
                                              а спеков у вас нету? CI опять же.
                                                0
                                                да есть спеки. только в чём мне выгода постоянно свежую версию руби держать? приростов производительности не даёт, на моей памяти уже полгода не было ни одной серьёзной уязвимости. только чтобы лишних 10 часов рабочего времени на прогонку test::unit и отладку тратить?
                                                  +1
                                                  а зачем тратить время на прогонку тестов?
                                                  есть teamcity с несколькими профилями сборки.
                                                  моно сделать еще один профиль с новым руби, благо тимсити поддерживает rvm.
                                                  и оно все автоматом прогонит еще и скажет где была ошибка
                                                    0
                                                    вы сейчас сказали, как это можно упростить (и то сомнительно), а не в чём мне выгода от обновлений.
                                                      0
                                                      выгода в том что бы после года сиденья на старом релизе не прыгнуть на новый с тысячами косяков и геморроем.а на новый прийдется пересесть или изза безопасности или изза скорости или вы сервак новый купили. вы на новое железо будете ставить старый софт? это как на последний оптерон поставить Debian Woody.
                                                        0
                                                        в чём профит регулярных косяков и геморроя относительно таковых раз в полгода?
                                                          0
                                                          а зачем на них отвлекаться? у нас проект с постоянной разработкой. сидеть на старом нету смысла. тем более что мы не продали заказчику и забыли.
                                                            0
                                                            я имею в виду чем косяки и геморрой раз в месяц лучше косяков и геморроя раз в полгода?
                                                              0
                                                              тем что они незаметны. разработка и так идет постоянно, потому подправить один баг можно быстрее чем десяток. ИМХО конечно же.
                                                                0
                                                                исправить 10 багов за 1 раз, или 20 багов за 6 раз? логика подсказывает, что быстрее первое.
                                                                  0
                                                                  ну я думаю их будет меньше.
                                                                  скорее всего 20 за 1 раз или 10 за 6.
                                                                    0
                                                                    нет, почему же. смотрите: допустим в каждую версию вносится 2 бага, а 1 предыдущий исправляется. в этом случае я исправляю 6 багов, а вы 12. моё число багов по законам комбинаторики не может быть больше вашего.
                                                                      0
                                                                      но вы не исправляя свой код вовремя окажетесь в большой куче deprecation и removed functionality.
                                                                        0
                                                                        да, но суммарно мне исправлять один за полгода не больше, чем вам суммарно 6 раз за 6 месяцев.
                                                                          0
                                                                          * один раз за полгода
                                                                            0
                                                                            господа нам скоро места не хватит для этого холиварного треда :)
                                                                              0
                                                                              To update or not to update? :)
                                                                            0
                                                                            не факт. если бы я не начал начиная с беты переводит проект с 2.3.5 на 3.0.beta, то потом было бы намного сложнее. то же самое сейчас и с 3.1, а с 3.2 еще и хуже будет. уже сейчас из мастер ветки выкинуто то(TemplateHandlers), что в 3.1 еще будет.
                                                                          +1
                                                                          но вы учтите, что я исправляю постоянно и знаю что где ломалось, а вы нет.
                                                                          я потрачу 12 раз по 10 минут, а вы 6 раз по 30.
                                                                            0
                                                                            за месяц к следующему обновлению вы можете что-то забыть.
                                                                            я за день, в который буду исправлять, вряд ли что-то забуду.
                                                                              +1
                                                                              ну и что? мне зачем все помнить, я то уже все исправил :)
                                                                                0
                                                                                вы в прошлом комментарии использовали абсолютно обратный аргумент.

                                                                                и вообще, вы мне сейчас говорите вещи, которые и математически-то в голове не укладываются.

                                                                                прекращаю этот спор, ибо начинаю чувствовать запах жира и сказочных существ, живущих под мостом.
                                                                                  0
                                                                                  А ответ то всегда один — каждый делает так, как ему удобнее. У меня знакомый есть который продакшны на debian sid'е держит, и при этом целыми днями х… пинает :)
                                                                +1
                                                                Честно говоря я никогда не понимал людей сидящих до сих пор на 8.04 с такими версиями руби что рельсы 3-й поднять невозможно. Эта гипотетическая безопасность убивает весь прогресс. Это то же самое что сидеть на ИЕ6 потому что в ИЕ9 больше дырок. Вы уж извините, но я считаю, что лучше потратить сутки в месяц на обновления, чем сидеть на каменных велосипедах.
                                                                  0
                                                                  при чём здесь 8.04 и ИЕ6, если я сижу на 1.9.2, который позволяет мне запустить третьи рельсы, и при критических уязвимостях раз в полгода я обновляюсь?
                                                                  вернее будет аналогия не с 8.04 и ИЕ6, а с 10.04 LTS vs 11.04 и FireFox 3.6.15 vs FireFox 4.0.0. Кстати, я на 11.04 и FF4.
                                                                  > Эта гипотетическая безопасность убивает весь прогресс.
                                                                  мои старые рубя убивают ваш прогресс? или чей? лично мне они экономят нервы и время.
                                                                    0
                                                                    ну да. немного преувеличил. но лично для меня разница между p136 и p180 есть (даже в номере). так что считаю что обновления хотя бы раз в месяц необходимы. конечно если это не убъет проект (как mysql2 > 0.3.0 для rails 3.0.x)
                                                                    0
                                                                    а прогресс убивает то что авторы гемов вынуждены делать совместимость с предыдущими версиями (что есть ересь на мой взгляд), и из-за этого код только пухнет. т.е. ничего полезного.
                                              +2
                                              я как раз пользую rvm во всю.
                                              у нас не супер-сервера а простой Amazon EC2 middle.
                                              дело то не в том что собирает или нет из сорцов. дело в том что когда мне надо обновить руби я просто пишу
                                              rvm upgrade 1.9.2-p136 192-p180 и всё.
                                              rvm мне и жемсеты перенесет, и пересоберет что надо в жемах. не надо париться. и симлинку на дефолтный обновит.всего одна команда против трех.
                                                0
                                                >./confidure && make && make install

                                                Я бы убивал за такое, с разжалованием в виндузятники, лишением всех наград и т.д.

                                                serverfault.com/a/419211/190178
                                                How do I install the latest stable version of Redis?

                                                github.com/jordansissel/fpm/wiki

                                                www.slideshare.net/fabiokung/ruby-and-rails-packaging-to-production
                                                0
                                                ах да, обновляю я повтором указанных выше команд с впереди стоящим make clean из папки сорцов старой версии.
                                                кстати, сразу видно, что у вас очень много свободного времени, если вы на продакшне всегда держите свежую свежую руби. трудная, наверное, работа, дебажить приложение после каждого обновления.
                                                  0
                                                  держим свежую СТАБИЛЬНУЮ версию руби. опять же в каждом релизе есть ошибки. и по части безопасности тоже.
                                                  а в чем проблема держать последнюю версию? прогнали спеки, проверили что ничего не поломалось и можно использовать.
                                                    0
                                                    ответил выше в чём проблема.
                                              0
                                              RVM удобен тем, что работает по сути везде и одинаково, а в том же Debian вечно в пакетах что-то сломают или не обновят по полгода.
                                              +1
                                              Забыли одну весчь.
                                              before_exec do |server|
                                              ENV["BUNDLE_GEMFILE"] = "#{rails_root}/Gemfile"
                                              end

                                              Если этой строчки не будет, то при обновлении Gemfile у вас после релоада уникорна не будет новых изменений бандла джемов.
                                                0
                                                Благодарю за замечание — это весомый такой недосмотр с моей стороны.
                                                0
                                                Вот эту статью надо к Locum отправить, а то их «развертка» просто ни о чем. Тут ясно и понятно, что да как. Спасибо за статью.
                                                Вот еще бы написали, для чего Capistrano, а то не понятно.
                                                  0
                                                  Capistrano — это штука для автоматизации развертки новых версий приложения. Т.е. надо положить изменения на сервер — пишешь одну команду у себя на рабочей машине: cap deploy — она автоматически производит деплой, если возникает ошибка — откатывается к предыдущей версии. Вещь очень удобная — сильно снижает человеческий фактор ошибки.
                                                  0
                                                  Кстати, у нас используется установка RVM в режиме system-wide.

                                                  Хотя бы потому, что на одном сервере может быть размещено несколько ruby-сервисов.
                                                    0
                                                    Каждый под своим user.
                                                      0
                                                      А в чем недостаток когда у каждого этого юзера будет свой рвм и свой руби?
                                                        0
                                                        а зачем плодить кучу сущностей? у каждого юзера своя версия rvm. вот кому это надо?
                                                          0
                                                          ну меня просто напрягает rvm установленный под рутом :) мне кажется что лучше пусть больше сущностей, но без рутового доступа (а еще желательно и с nologin)
                                                            0
                                                            а чем он вас напрягает?
                                                            сделать дефолтным другую версию руби не получиться — прав не хватит.
                                                            ну с капистрано не получиться nologin
                                                              0
                                                              Я и говорю чтобы рвм и руби у каждого свои.

                                                              Вот с капистрано у меня тоже проблемы ) точнее не люблю его. я лучше сделаю

                                                              ssh user_allowed_to_login@host
                                                              sudo su rvm_user
                                                              git pull
                                                                0
                                                                в этом случае будут проблемы, например, с откатом назад (надо знать, на какое количество коммитов откатываться, к примеру), а также с тем, чтобы не забыть обновить конфиги, выполнить миграции и так далее.

                                                                У нас обычно это все зашивается в cap deploy или в chef recipes.
                                                                  0
                                                                  теги сила. чекаутим по тегам, откат по тегам.
                                                                    0
                                                                    а базу вы тоже ручками обновляете? ;)
                                                                      0
                                                                      довольно редкая операция в продакшне если честно, да и rake db:migrate сделать не сложно на мой взгляд
                                                                        0
                                                                        а у вас сколько продакшенов? мне просто лень идти на все воркеры делать одни и теже операции. тем более что ошибиться не мудрено, а капистрано очень помогает.
                                                                          0
                                                                          4.

                                                                          но обновляются они то не одновременно, проекты то разные.
                                                                            0
                                                                            Очень может быть, что и одинаковые. Например, при развертывании одного приложения на нескольких серверах (масштабируемость, отказоустойчивость).
                                                                              0
                                                                              а у меня проект один, но воркеров много. да и лень это такая штука…
                                                                              как говориться правильный админ — ленивый админ, потому что он один раз все настроил, а дальше уже все автоматом :)
                                                                                0
                                                                                вот поэтому я не админ :) а когда разорюсь на админа, тогда он и будет ленивым :)
                                                                0
                                                                Надо разделять процессы bootstrapping и deploy.

                                                                На этапе bootstrapping сервер подготавливается, ставится операционная система и системные сервисы (например, snmp, collectd, rvm, ruby).

                                                                А на этапе deploy делаются service-specific tasks.
                                                        +3
                                                        Кстати для полноге zero-downtime нужно делать так:
                                                        after_fork do |server, worker|
                                                        begin
                                                        worker.user('deployer', 'deployer') if Process.euid == 0
                                                        defined?(ActiveRecord::Base) and
                                                        ActiveRecord::Base.establish_connection
                                                        old_pid = "#{server.config[:pid]}.oldbin"
                                                        if File.exists?(old_pid) && server.pid != old_pid
                                                        begin
                                                        # after this all request will be processed by new workers
                                                        Process.kill("WINCH", File.read(old_pid).to_i)
                                                        sleep 0.1
                                                        # send stop for the old master
                                                        Process.kill("QUIT", File.read(old_pid).to_i)
                                                        rescue Errno::ENOENT, Errno::ESRCH
                                                        end
                                                        end
                                                        rescue => e
                                                        puts "[#{Process}]: #{e.message}\n"
                                                        puts "[#{Process}]: #{e.backtrace.join("\n")}"
                                                        end
                                                        end
                                                          +1
                                                          Еще для полноты стоит использовать директивы nginx а ля proxy_cache_use_stale.
                                                          +1
                                                          Я извиняюсь за, возможно, нубский вопрос, но чем собранный руками nginx лучше nginx из репозитория?
                                                          общая рекомендация для всех кто не использует git — используйте git.
                                                          Я использую mercurial. C репозиториями git работаю опять-таки из mercurial (благодаря плагину hg-git (что характерно, работает без git в $PATH)). Зачем мне переезжать на git полностью?
                                                            0
                                                            > чем собранный руками nginx лучше nginx из репозитория?

                                                            Если вы про репозиторий убунты, то ответ — цифрами в номере версии.

                                                            >Я использую mercurial. C репозиториями git работаю опять-таки из mercurial

                                                            если при это выполнив в консоли git clone git@url вы не получите ошибок, то переезжать не надо.
                                                              0
                                                              тогда, вероятно, стоит привести заодно и список пакетов с хедерами, необходимых для сборки nginx?
                                                                0
                                                                apt-get build-dep часто помогает.
                                                                  +1
                                                                  Ха, ну я-то знаю. И вы знаете, да. А вот читатель топика — не факт.
                                                              +1
                                                              Мы давно перешли на сборку nginx руками по нескольким причинам:
                                                              1) полный контроль над списком собираемых модулей (часть модулей выкинута, часть добавлена, типа aio);
                                                              2) необходимость использования директив конфигурации nginx, которые появились только недавно;
                                                              3) Патчи на некоторые баги (например, патчи на AIO).

                                                              При этом, конечно, вместо сборки можно просто сделать сборку однократно и положить пакеты в локальный репозиторий пакетов.
                                                              0
                                                              установка rvm на debian и чтобы не system-wide — боль. постоянно хочет установить в /usr/local/rvm вместо ~/.rvm
                                                              как это лечить?
                                                                0
                                                                Если он хочет установить в /usr/local/rmv — скорее всего вы делаете установку из-под root. Если нет, то вам сюда.
                                                                  0
                                                                  да нет, ставлю обычным юзером. точнее не ставлю тк пишет mkdir: cannot create directory `/usr/local/rvm': Permission denied
                                                                0
                                                                Скажите, а вы не придумали способ, как при деплое капистраной сделать, чтоб в случае ошибки в приложении происходил фейл и rollback? Просто в случае graceful restart с помощью USR2 иногда (когда какой-то косяк в коде) происходит следующее: unicorn стартует новый мастер, переименовывает pid в oldpid, фейлится, переименовывает обратно и продолжает работать со старой версией приложения. При этом, капистрана об этом ничего не знает и думает, что произошёл successful deploy. Со стороны это выглядит как ничего не изменилось после деплоя. Единственный способ узнать об ошибке – зайти на сервер и посмотреть в логи.
                                                                  0
                                                                  А разве компилировать Ruby — это правильно. Если найдут уязвимость в безопасности, то надо будет самому об этом узнать и обновить, а не написать просто
                                                                  sudo apt-get update
                                                                  . Да и надо самому следить, чтобы заголовочные файлы были нужных версий и т. д.
                                                                    0
                                                                    Для оперативного обновления наверное компиляция более правильное решение, если следить. А версия в репах может не обновляться неизвестное время. Если обратили внимание, то и nginx здесь компилируется, видимо с аналогичными соображениями. Вроде и настроить можно более тонко при компиляции, но это к гентушникам :)

                                                                    Если сервис некритичный, то, имхо, лучше из реп ставить действительно, может из нестабильных.

                                                                    А если очень критичный, то компилировать на тестовом сервере с аналогичным конфигом, собирать пакет и ставить его на продакшен после всестороннего исследования. Как-то так.
                                                                    0
                                                                    Блин, так и не понял, чем unicorn так хорош.

                                                                    Для меня все это сильно похоже на mongrel_cluster, на который все благополучно забили, задолбавшись с ним. А тут заново история повторяется. Да еще и конфиг какой-то на ruby писать теперь придется.
                                                                      0
                                                                      не очень понятно, как поднимать unicorn при старте системы (а также красиво опускать/перезапускать его) — неужели придётся писать свой кастомный скрипт в /etc/init.d/?
                                                                        +1
                                                                        Мы используем runit: habrahabr.ru/blogs/sysadm/83775/
                                                                          0
                                                                          Вопрос такой тогда: как вы интегрируете runit с capistrano?
                                                                            +1
                                                                            namespace :deploy do
                                                                              task :start do
                                                                                run "sudo /usr/bin/sv up #{application}"
                                                                              end
                                                                              task :stop do
                                                                                run "sudo /usr/bin/sv down #{application}"
                                                                              end
                                                                              task :restart do
                                                                                run "sudo /usr/bin/sv hup #{application}"
                                                                              end
                                                                            end
                                                                            
                                                                              +1
                                                                              тут по идее должно быть
                                                                              namespace :deploy do
                                                                                task :start do
                                                                                  sudo "/usr/bin/sv up #{application}"
                                                                                end
                                                                                task :stop do
                                                                                  sudo "/usr/bin/sv down #{application}"
                                                                                end
                                                                                task :restart do
                                                                                  sudo "/usr/bin/sv hup #{application}"
                                                                                end
                                                                              end
                                                                              


                                                                              но взято из рабочего кода.

                                                                              понятно, надо прописать sudoers для пользователя deploy.
                                                                            0
                                                                            Подскажите как запускать Unicorn под Runit?

                                                                            У меня такой скрипт не работает

                                                                            #!/bin/sh
                                                                            
                                                                            exec 2>&1
                                                                            
                                                                            # Settings
                                                                            APP_ROOT=/srv/vchemodan
                                                                            APP_USER=deployer
                                                                            APP_GROUP=deployer
                                                                            APP_RAILS_ENV='production'
                                                                            
                                                                            UNICORN_CONFIG=$APP_ROOT/current/config/unicorn.rb
                                                                            CMD="bundle exec unicorn -E $APP_RAILS_ENV -c $UNICORN_CONFIG -E $APP_RAILS_ENV"
                                                                            
                                                                            cd $APP_ROOT/current
                                                                            
                                                                            exec chpst  -u $APP_USER:APP_GROUP $CMD
                                                                            
                                                                            
                                                                              0
                                                                              остановите сервис, перейдите в его каталог </etc/sv/your_service>.

                                                                              попробуйте запустить так:
                                                                              ./run
                                                                              


                                                                              Все ошибки станут сразу видны.
                                                                                0
                                                                                Как минимум, APP_GROUP без $.
                                                                                  0
                                                                                  Да это случайно, на самом деле $ перед APP_GROUP есть.

                                                                                  Вот какую ошибку мне возвращает ранит:
                                                                                  chpst: fatal: unable to setgroups: permission denied
                                                                                    0
                                                                                    У утилиты chpst нет прав на установку группы. Видимо, запускается runsvdir не из под рута или какие-то странные пермишены?

                                                                                    Попробуйте заменить chpst на sudo, или убрать строку chpst -u ..., оставить просто

                                                                                    exec unicorn ...
                                                                                    
                                                                                      0
                                                                                      C sudo точно не запуститься, т.к. у меня не системвайд RVM.

                                                                                      Убрал chpst. Runit запускае Unicorn, но тот висит в консольном режиме, пока не нажму ctrl+c ничего не могу сделать.
                                                                                        0
                                                                                        Все нормально. Главное, ./run сработал. Теперь запустите опять сервис через sv start.
                                                                                          0
                                                                                          Неа, через sv start не стартует. Похоже придеться по старинке через демонизацию Unicorna запускать.
                                                                                            0
                                                                                            Кстати, если не Systemwide VM, то можно запускать, но требуется немного магии.

                                                                                            Мы предпочитаем systemwide.
                                                                                              0
                                                                                              В любом случае, спасибо за помощь.
                                                                              +1
                                                                              После долгих дискуссий и поисков я пришел к выводу, что гарантии может дать только runit. Однако есть у меня один могильничек… В общем gem whenever (cron) + проверка на живость мастера (bash). Если с купюрами, то выглядит как-то так:
                                                                              if environment == 'production'
                                                                              every 1.minute do
                                                                              command «if [! -f #{unicorn_pid} ] || [! -e /proc/$(cat #{unicorn_pid}) ]; then rvm #{ruby} && cd #{current} && bundle exec unicorn_rails -c #{unicorn_conf} -E #{rails_env} -D; fi»
                                                                              end
                                                                              end

                                                                                0
                                                                                для этого лучше подходит monit. заодно может проверить отклик по http.
                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                                0
                                                                                Спасибо за статью!
                                                                                У меня у двух сайтов почему-то конфликтовали директивы `listen 80 default deferred;` (duplicated listen ....) при том что голый `listen 80;` работает.

                                                                                Мне кажется можно было бы описать еще подход с /etc/nginx/sites-available/ /etc/nginx/sites-enabled/ вместо /usr/local/nginx/conf/vhosts/*
                                                                                соответственно, конфиги — в sites-available, симлинки на рабочие сайты — в sites-enabled, в nginx вместо `include /usr/local/nginx/conf/vhosts/*` стоит `include /etc/nginx/sites-enabled/*.conf`
                                                                                Позвляет быстро отключать сайты, не ломая их конфиги.
                                                                                  0
                                                                                  Это вопрос принятых в определенном дистрибутиве соглашений и личных предпочтений.

                                                                                  Nginx'у всё равно откуда include делать, если прав хватает. В Debian и Ubuntu, насколько помню, используются /etc/nginx/sites-{available,enabled}, в RHEL/CentOS — /etc/nginx/conf.d, в ArchLinux это вообще на усмотрение админа, по умолчанию никаких директорий нет и include /etc/nginx/whatever/*.conf спокойно пишут руками, кому нужно. В подходе а-ля rhel отключать конфиги можно с помощью mv, всё равно потом SIGHUP кидать.

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

                                                                                Самое читаемое