Рубин на рельсах: продакшен и деплой для чайников

  • Tutorial
Год назад я довел свое первое рельсовое приложение до приемлемого вида. Вопрос использования готового кода в продакшене ранее меня не заинтересовал. С чего вдруг? Несложный язык, лаконичный фреймворк — уж деплой-то явно не сложнее, чем преодоление ментального тормоза после PHP.

Команда разработчиков Rails рекомендует использовать Phusion Passenger, он что-то вроде mod_php — установил, разместил файлы и полетел. На момент изучения вопроса на форумах хватало баталий о производительности решений; Passenger в них фаворитом не значился.

Совета относительно альтернативы я спросил у техдиректора сайта с миллионом уников в сутки — тот отправил меня гуглить на тему Nginx и Unicorn. Инструкция по настройке продакшена, найденная на Хабре, датировалась 2009 годом. Помимо прочего, ее просто переполняли изъяны уроков «Как нарисовать сову».

Отдельные составляющие процесса кое-где разжеваны по-английский, но монолитный tutorial на глаза так и не попался. В традициях рельсового сообщества лежит принцип, предписывающий делиться результатами и опытом решения проблем.

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

— О чем пойдет речь?
— Эта инструкция поможет новичкам подобрать и настроить хостинг, а также подготовить имеющийся проект для первоначального деплоя и систематической выкатки обновлений.

— А подробнее?
— Мы будем использовать Ubuntu 14.04, RVM, Nginx, Unicorn и Capistrano. Текст состоит из двух глав: подготовки проекта и настройки сервера. Все локальные манипуляции описываются в Mac OS X. Для выполнения процедур не будет лишней современная IDE вроде RubyMine, если нет — сойдет и TextMate. Все описываемые действия зафиксированы в специальном репозитории.

Глава первая Хостинг


Выбор хостинга


Сладкая парочка Nginx и Unicorn имеет весьма внушительный аппетит на оперативную память, а сами рельсы требуют установки ряда дополнительного ПО. Эти ограничения явно говорят о необходимости VPS. Есть вариант использовать специализированный хостинг вроде Heroku, но для новичков будет полезным процесс настройки проделать руками.

В рамках этого текста я буду использовать свежесозданный дроплет Digital Ocean на базе Ubuntu 14.04. (Промо-кодов на месяц—два бесплатного пользования в интернете хватает, кому нужна рефералка с десятью долларами на счете — дам ссылку.)

Обновление системных пакетов


Заходим в систему под рутом и обновляем пакеты:

sudo apt-get update
sudo apt-get upgrade

Установка Git и NodeJS


sudo apt-get install git-core nodejs

Создание пользователя


Предполагаю, что у вас (как и у меня) есть коммерческий интерес к деплою. Создадим отдельного пользователя (другими словами — клиента), в домашней директории которого мы и будем разворачивать приложение. Помимо очевидного, такой подход дает преимущества в виде отдельного RVM (ruby version manager), что позволит использовать разные версии интерпретатора и гемов для разных клиентов и приложений. Мы создадим пользователя demo и добавим его в группу sudo.

sudo adduser demo
sudo adduser demo sudo

Закрываем сессию, заходим в систему как пользователь demo.

Отмена запроса пароля для sudo


Для некоторых процедур деплоя потребуются привилегии суперпользователя. Чтобы команды исполнялись с помощью Capistrano и не вызывали ошибок, необходимо отключить запрос пароля. Отредактируйте файл sudoers: sudo nano /etc/sudoers.

# Найдите следующую строку:
%sudo ALL=(ALL:ALL) ALL
# И приведите ее к виду:
%sudo ALL=(ALL) NOPASSWD:ALL

Официальная документация Capistrano поясняет, что нативные фазы деплоя не требуют sudo; однако, если речь заходит об автоматизации всех процессов, без passwordless sudo не обойтись.

Генерация SSH-ключей


Потребуются две пары ключей. С помощью одной из них вы (и Capistrano) будете проходить авторизацию на сервере с локального компьютера. Второй парой вы предоставите серверу доступ в репозиторий (так называемый ключ развертывания). Кодовые фразы в обоих случаях оставьте пустыми.

Первая пара (необходимо генерировать локально):


ssh-keygen -t rsa -b 2048

Если вы знакомы с процедурой генерации и использования ключей, то путь к файлу и стойкость выбирайте по своему вкусу; иначе — оставьте по умолчанию.

Теперь необходимо скопировать содержимое публичного ключа (по умолчанию ~/.ssh/id_rsa.pub на локальном компьютере) и добавить его в файл ~/.ssh/authorized_keys на сервере. После этой нехитрой манипуляции с помощью SSH вы сможете подключиться к серверу без пароля. Если нет — проверьте права на сервере: 700 для ~/.ssh и 600 для ~/.ssh/*.

Вторая пара (на сервере):


ssh-keygen -t rsa -b 2048

Аналогичным образом содержимое из серверного ~/.ssh/id_rsa.pub нужно добавить в список ключей развертывания (в Гитхабе их можно найти в настройках каждого репозитория).

Установка свежего Nginx


Версия Nginx, доступная в Ubuntu, зачастую более старая, нежели в официальном репозитории разработчика. Я стараюсь использовать свежую версию, но если вы других взглядов — установите Nginx самостоятельно и пропустите эту часть.

Мы добавим официальные репозитории Ubuntu в системный список sudo nano /etc/apt/sources.list. В конец файла добавим строки:

# Nginx official repository
deb http://nginx.org/packages/ubuntu/ trusty nginx
deb-src http://nginx.org/packages/ubuntu/ trusty nginx

Помните, что использованные параметры актуальны только для Ubuntu 14.04. Информацию по установке на другие версии ОС ищите на сайте разработчика. Для установки Nginx из указанных репозиториев потребуется также загрузить и добавить в систему ключ:

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

Теперь можно обновить список доступных пакетов и установить Nginx:

sudo apt-get update
sudo apt-get install nginx

Удалим дефолтные конфиги из /etc/nginx/conf.d. (Разумеется, не вздумайте делать этого, если работаете не на «чистом» сервере.) Виртуальный хост для приложения мы создадим в следующей главе.

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

Установка RVM


\curl -sSL https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm

В случае с чистым сервером понадобится установить также некоторые зависимости:

rvm requirements

Осталось установить непосредственно Ruby необходимой вашему приложению версии. К примеру, новую стабильную версию 2.1.3:

rvm install 2.1.3
rvm --default use 2.1.3

Проверить корректность установки можно с помощью команд ruby -v и rvm info

Установка компонентов приложения


Наверняка, ваше приложение будет использовать еще ряд компонентов (не включая гемы), вроде базы данных или графического процессора Imagemagick, их установку придется закончить самостоятельно. Тут стоит добавить небольшую ремарочку (если забыть про которую — автоматический деплой будет завершаться ошибкой): рельсам для работы с некоторыми компонентами иногда требуются дополнительные пакеты. Например, для использования MySQL вам понадобится установить, помимо прочего, пакет libmysqlclient-dev.

Глава вторая Подготовка приложения


Использование Git


Я предполагаю, что у вас уже есть готовое приложение. Первое правило — проект должен находиться под управлением Git. Это де-факто стандарт в рельсовом мире (даже файл .gitignore в корне создаваемых рельсовых приложений недвусмысленно намекает на это). Более того, последние версии гема Capistrano, который будет отвечать непосредственно за деплой, нативно поддерживают только эту систему.

Что такое Capistrano


Официальное определение Capistrano звучит так: «A remote server automation and deployment tool». С точки зрения пользователя, Capistrano — штука, которая позволит выполнить произвольный набор команд на удаленном сервере через SSH. Существует и другие инструменты для деплоя (например, Mina), но пока Capistrano также некий стандарт, тем более, что позволяет выполнять параллельный деплой приложения сразу на ряд серверов, в том числе разделенных по ролям.

Принцип работы Capistrano


На сервере структура приложения под контролем Capistrano в целом состоит из трех директорий: repo, releases и shared. В первой хранится копия репозитория, во второй — релизы, в третьей — общие файлы, необходимые приложению и не зависящие от релиза. Также в корне присутствует симлинк current, ссылающийся на версию текущего релиза и лог-файл деплоев.

Когда вы (со своего локального компьютера) отдаете команду Capistrano выполнить деплой, устанавливается SSH-соединение с сервером и начинается выполнение нехитрого алгоритма. Для начала Capistrano сверяется с удаленным репозиторием и получает недостающие коммиты. После создается новый релиз (в директории releases). Туда перекладывается актуальная версия кода и там же выполняется ряд тестов.

Проще говоря, для каждого нового релиза Capistrano выполняет привычные команды вроде bundle install, rake db:migrate, rake assets:precompile, постоянно проверяя наличие конфликтов и ошибок. Пропустили точку с запятой в default.scss, не закомитили актуальный Gemfile.lock, подключили Paperclip, но забыли установить на сервере Imagemagic? Во всех этих случаях Capistrano покажет ошибку и прекратит установку, никак не затронув текущий работающий релиз. Если деплой прошел удачно, но результат вас не устроил, с помощью Capistrano можно сделать rollback к предыдущему релизу.

Организация файлов конфигураций


Каждый релиз для Capistrano — самостоятельная сущность. При очередном деплое происходит ее полная замена, поэтому ряд файлов необходимо хранить за пределами структуры приложения (как правило, загружаемый пользователями и генерируемый приложением контент, а также ряд конфигов).

Нам понадобится общая директория, необходимые файлы из которой мы будем линковать к каждому новому релизу при деплое; назовем ее shared и создадим локально в корне проекта mkdir ./shared, предварительно добавив исключение в .gitignore. (Я, разумеется, в учебный репозиторий исключение добавлять не буду.)

Теперь в общей директории создадим заранее будущую структуру. Для начала нам понадобятся папки config и run. В config положите актуальные для боевого сервера database.yml и secrets.yml. (Лично я предпочитаю уже на локальном компьютере переместить два этих файла из config, где потом создать на них ссылки.)

mv ./config/database.yml ./shared/config
ln -fs ./shared/config/database.yml ./config/database.yml
mv ./config/secrets.yml ./shared/config
ln -fs ./shared/config/secrets.yml ./config/secrets.yml

Конфигурация Nginx


Здесь же — в shared/config, — мы создадим конфиг для Nginx shared/config/nginx.conf. В базовом виде он состоит из двух небольших частей: апстрима и типичного виртуального хоста. Будьте особенно внимательны с путями (в этом и всех дальнейших) в конфигурационных фалах. 90% ошибок, возникавших при деплое, были связаны именно с ними.

upstream unicorn {
    server unix:/home/demo/application/shared/run/unicorn.sock fail_timeout=0; 
}

server { 
    listen 80 default; 
    root /home/demo/application/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_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 /home/demo/application/current/public;
    }
}

Мы используем наш VPS для единственного приложения, поэтому хост будет использоваться по умолчанию. (Не нужно же объяснять, что в иной ситуации придется явно указать server_name и позаботиться об уникальности имен апстримов?)

Конфигурация Unicorn


Раз уж мы создали апстрим, в котором указали путь к сокету Unicorn, давайте перейдем и к его конфигу. Необходимо добавить гем Unicorn в Gemfile. Не забывайте, что добавление новых гемов должно сопровождаться выполнением bundle install и комитом в репозиторий. Если забыть про git push, то на сервер будет загружена старая версия кода, которая, в силу отсутствия указанных гемов, будет вызывать ошибки при деплое.

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

В директории shared/config создайте файл unicorn.rb. По большей части в нем мы должны указать пути к составным частям нашего приложения. Для удобства это можно делать с использованием переменных.

# Рабочие директории приложения на сервере
root        = '/home/demo/application'
rails_root  = "#{root}/current"

# Файлы, хранящие идентификаторы запущенных Unicorn-процессов
pidfile     = "#{root}/shared/run/unicorn.pid"
pidfile_old = pidfile + '.oldbin'
pid pidfile

# Главные параметры
worker_processes 1
preload_app true
timeout 30

# Путь к сокету
listen "#{root}/shared/run/unicorn.sock", :backlog => 1024

# Путь к лог-файлам
stderr_path "#{rails_root}/log/unicorn_error.log"
stdout_path "#{rails_root}/log/unicorn.log"

# Установки сборщика мусора
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!
  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

Также указываются некоторые системные значения (полный список и документация). Обратить внимание следует на количество воркеров — worker_processes, — каждый из которых употребит некоторое количество памяти (сколько именно — зависит от приложения) и на timeout (обычно от 15 до 30 секунд). Многие упоминают и про preload_app (значение false которого может сократить время старта воркера); давайте пока остановимся на true, а потом вы решите сами.

Подключение и настройка Capistrano


Необходимые гемы


В Gemfile нужно внести Capistrano и серию гемов, реализующих его связь с RVM, Bundler и Rails.

group :development do
  gem 'capistrano', '~> 3.2.1'
  gem 'capistrano-rvm', '~> 0.1.1'
  gem 'capistrano-bundler', '~> 1.1.3'
  gem 'capistrano-rails', '~> 1.1.2'
end

Осталось выполнить bundle install, после чего инициализировать Capistrano с помощью cap install. Будет создан набор файлов, список которых вы увидите в консоли. Мы будем работать с тремя из них: Capfile, config/deploy.rb и config/deploy/production.rb. (В config/deploy Capistrano создает дефолтные файлы staging.rb и production.rb. Мы настроим только боевой сервер с помощью production.rb.)

Обновление Capfile


Все гемы, связанные с Capistrano, функциональность которых мы собираемся использовать, необходимо подключить в создавшемся в корне Capfile. Взгляните на дефолтный файл и раскомментируйте нужные строки или используйте следующее содержимое:

require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails'
Dir.glob('lib/capistrano/tasks/*.rb').each { |r| import r }

Настройка продакшен-сервера


Откройте файл config/deploy/production.rb и внимательно посмотрите на его содержимое. Оно поможет вам разобраться в формате и возможностях настройки. В целом, если вы используете авторизацию с помощью SSH-ключа, никакой экзотики писать не придется; всего одна строчка:

server '178.62.252.46', user: 'demo', roles: %w{web app}

Сценарий деплоя


Мы добрались до самой интересной части, файла config/deploy.rb. Именно он описывает параметры, процедуры и сценарий предстоящего деплоя. Я опишу каждый блок файла, но если хотите взглянуть на него в завершенном виде — воспользуйтесь репозиторием.

Обязательные параметры


Прежде всего, требуется указать версию Capistrano, для которой предназначен данный сценарий:

lock '3.2.1'

Capistrano требует ряд обязательных параметров:

# Репозиторий
set :repo_url, 'git@github.com:eboyko/deneb.git'
# Используемое окружение
set :rails_env, 'production'

А также параметр deploy_to, определяющий путь для деплоя приложения на сервере. Выше мы уже говорили, что подразумеваем коммерческий интерес, поэтому сделаем файл сценария более универсальным с помощью переменных и зададим недостающий параметр:

# Имя пользователя
set :username, 'demo'
# Имя приложения
set :application, 'application'
# Путь для деплоя
set :deploy_to, "/home/#{fetch(:username)}/#{fetch(:application)}"

Установим также параметр log_level (дефолтное значение :debug делает Capistrano излишне разговорчивым):

set :log_level, :info

Заметьте, глобальные переменные Capistrano (вроде shared_path) можно использовать напрямую; заданные с помощью set — через метод fetch.

Релизонезависимые данные

Мы уже рассмотрели необходимость доступа работающего релиза к данным, созданным предыдущими версиями. К примеру, вы храните файлы, загружаемые пользователями, в public/upload. Чтобы подключать их к каждому новому релизу, в config/deploy.rb можно задать параметр :linked_dirs:

set :linked_dirs, %w{public/upload}

При каждом деплое public/upload будет заменяться симлинком, ведущим на директорию shared/public/upload, где и скапливались данные за время работы предыдущих релизов. Аналогичным образом, с помощью :linked_files, линкуются отдельные файлы:

set :linked_files, %w{config/secrets.yml config/database.yml}

Учтите, что добавление в :linked_files указанных файлов (config/secrets.yml и config/database.yml) обязательно. В противном случае деплой завершится ошибкой по причине отсутствия подключения к базе данных.

Процедуры


Capistrano позволяет создавать наборы процедур, которые для удобства можно объединять в пространства имен. Неймспейс :deploy уже существует, его можно лишь дополнить; все включенные в него процедуры для сервера production можно вызвать командой cap production deploy

Сет-ап

В результате предыдущих шагов у нас сформировалась директория shared, в которой, помимо прочего, лежат файлы конфигураций (shared/config). Логичным первым шагом будет загрузить их на сервер. Для этого все в том же файле config/deploy.rb, в неймспейсе :setup, мы напишем процедуру:

namespace :setup do
  desc 'Загрузка конфигурационных файлов на удаленный сервер'
  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

Как вы понимаете, процедура подразумевает создание shared_path (потому как на сервере еще нет никакой структуры) и загрузку туда локальной директории shared/config. Вы можете выполнить ее с помощью команды cap production setup:upload_config

Управление Nginx

Я отмечал выше, что Capistrano по сути — способ выполнить произвольные команды на удаленном сервере. Мы загрузили конфигурационные файлы, в том числе для Nginx. Теперь напишем несколько процедур для управления: создание симлинка на конфиг и релоад/рестарт сервиса (для них потребуются права sudo).

namespace :nginx do
  desc 'Создание симлинка в /etc/nginx/conf.d на 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 'Релоад nginx'
  task :reload do
    on roles :all do
      sudo :service, :nginx, :reload
    end
  end
  desc 'Рестарт nginx'
  task :restart do
    on roles :all do
      sudo :service, :nginx, :restart
    end
  end
  after :append_config, :restart
end

Заметили приятную мелочь? — Можно задать последовательность исполнения различных процедур как в рамках одного неймспейса, так и между ними.

Управление Unicorn

Наверняка уже есть гем, расширяющий Capistrano в плоскости управления Unicorn, но мне было весьма интересно узнать, как он на самом деле устроен и работает. Поэтому сейчас, аналогично предыдущим примерам, напишем две процедуры (старт и завершение) для Unicorn.

set :unicorn_config, "#{shared_path}/config/unicorn.rb"
set :unicorn_pid, "#{shared_path}/run/unicorn.pid"

namespace :application do
  desc 'Запуск Unicorn'
  task :start do
    on roles(:app) do
      execute "cd #{release_path} && ~/.rvm/bin/rvm default do bundle exec unicorn_rails -c #{fetch(:unicorn_config)} -E #{fetch(:rails_env)} -D"
    end
  end
  desc 'Завершение Unicorn'
  task :stop do
    on roles(:app) do
      execute "if [ -f #{fetch(:unicorn_pid)} ] && [ -e /proc/$(cat #{fetch(:unicorn_pid)}) ]; then kill -9 `cat #{fetch(:unicorn_pid)}`; fi"
    end
  end
end

Не забудьте задать значения параметров :unicorn_config и :unicorn_pid.

Процедуры до и после деплоя

После деплоя нам нужно удалить самые старые релизы (по умолчанию Capistrano хранит пять последних), очистить кеши и перезапустить Unicorn. Главный рабочий блок :deploy будет выглядеть примерно так:

namespace :deploy do
  after :finishing, 'application:stop'
  after :finishing, 'application:start'
  after :finishing, :cleanup
end

Вынесение процедур в отдельные файлы


Чтобы не загромождать файл deploy.rb, написанные процедуры можно (а может быть и нужно) выносить за его пределы. В Capfile последняя строка отвечает за иморт таких задач из директории lib/capistrano/tasks,— именно туда и стоит перенести эту часть логики.

Выполнение деплоя


Начиная с этого момента, все, что вам потребуется сделать для выкатки новой версии своего приложения, — закомитить изменения, сделать git push и использовать команду cap production deploy.

По аналогии вы сможете настроить сервер staging, деплой на который осуществить cap staging deploy.

Что-то не получается? — Оставляйте комментарии, давайте дополним статью.

Ожидаются апдейты:


— краткий обзор имеющихся серверов приложений (в контексте выбора Unicorn);
— управление процессами (unicorn worker killer и какой-нибудь менеджер);
— выбор и настройка аналога RVM;
— установка Nginx с помощью PPA;
— правки относительно passwordless sudo;
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 81

    +3
    я также укажу конкретную версию (такая педантичность когда-нибудь убережет вас от ошибок из-за обратной несовместимости новых версий некоторых гемов)


    А разве Gemfile.lock не для этого придуман?
      0
      Может получиться так, что вы обновите сам гем (например, со второй до третьей версии) и попробуете выполнить старый сценарий с помощью нового гема.
        +6
        Как правильно заметил farcaller, именно для этого и есть Gemfile.lock.
        И нет, не может если:

        1. в репозитории хранится как Gemfile, так и сгенерированный Gemfile.lock
        2. и если выполняется все через bundle exec
          0
          Полагаю, вы правы. Ремарка дана под влиянием bootstrap-sass, обновившегося с 3.1 до 3.2 (в чем, казалось бы, ничего криминального) и сломавшего деплой. Сейчас поправлю в тексте, чтобы не смущать народ.
            0
            Возможно, у вас ассеты не перекомпилились.
              0
              Так и произошло, и по этой причине деплой не был выполнен — Capistrano зафиксировал ошибку компиляции.
                0
                И в чём была ошибка?
      +1
      Тут, кстати, не указано что будет при перезагрузке сервера, а будет печаль.
        –2
        Эту часть я тоже хотел включить, но уже и так слишком много букв. Или нет?
          0
          Минималистичный конфиг того же monit'а не повредит. Ну и как минимум упоминание о том что этим стоит озаботиться.
            +1
            Про него и Unicorn Worker Killer тоже думаю. Допишу — добавлю. Спасибо!
            +1
            тут мне больше нравится runit.
          0
          Для перезапуска и мониторинга CPU/ памяти рекомендую посмотреть на github.com/kostya/eye.
          Для его интеграции в capistrano есть capistrano-eye. Очень простой gem которые автоматом добавляет таски по eye load config/ eye restart
          В одном конфиге может быть несколько процессов (unicorn/ sidekiq/ etc), а так же приложение может быть разделено на несколько конфигов, что удобно если куски деплоятся отдельно, но хочется их перезапускать одновременно
            +1
            Хороший пост, спасибо.
            Добавлю буквально мелочь: при установке версии руби, имеет смысл сразу её[версию] ставить по дефолту.

            rvm install 2.1.3
            rvm --default use 2.1.3
            

              0
              Да, кстати! Спасибо, дособираю все замечания и внесу правки в текст.
              +3
              Статья весьма неплохая вышла — неделю назад у самого были мысли написать о том же и примерно теми же словами, но лень-матушка не дала.

              В некоторых моментах, правда самую малость перемудрили.

              В наших проектах используем rbenv вместо rvm — результат получается более гибкий и адекватный, потому что rbenv не лезет куда не нужно — весь зоопарк версий ограничен уровнем директорий, что бывает очень полезно например если на одной и той же машинке в соседних папках живут приложения, работающие на разных версиях руби. Сам довольно долго пользовался RVM, но в итоге посчитал этот подход более удобным. Кроме того, достаточно сильно напрягает что RVM прописывается везде так что потом не выпилишь, подменяя стандартные команды шелла. И еще, если какой-то руби-скрипт (не рельсы, а например какой-нибудь парсер) запускаются по крону, с RVM я долго плясал почему окружение ведет себя как-то странно пока не прописал в этот же crontab состояние всего окружения с текущего пользователя. Как по мне — так топорный подход, это как для того чтобы завести будильник, ты должен был ему свои паспортные данные показывать. В этом плане rbenv работает значительно проще. Но и для стандартного rvm-подобного подхода никто не мешает задать глобальную версию руби, а также отдельную версию например для шелла (а также набор гемов чтобы не грузить лишнее для консольных утилит).

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

              Конечно, уже не в рамках этой статьи, но дальше заинтересовавшимся лучше посмотреть в сторону связки Vagrant / Docker чтобы деплой ограничивался вообще разворачиванием собственного заранее настроенного контейнера со всем окружением (тем более удобно если вся команда работает с копиями этого же контейнера локально и ни у кого ничего не отличается). Но это мня уже чуток занесло. Понедельник, простите.

              P.S. собственно, кому интересна замена rvm, посмотрите: github.com/sstephenson/rbenv
                0
                Спасибо за такой развернутый комментарий, я во многих мануалах видел rbenv, но пока еще не разбирался с ним. А что касается деплоя контейнеров — уже и хостеры, вроде Digital Ocean, предлагают дроплеты с предустановленным окружением.
                  0
                  Не осилил rbenv и rvm (не смог их настроить так, чтобы оно устанавливалось в %homedir% при компиляции, а затем работало из /opt). Запилил свой скрипт, который на специальном build сервере (docker) скачивает сорцы ruby, компилирует их и устаналивает только нужные gem. Затем это упаковывается в rpm, который уже устанавливает ruby в /opt директорию. В результате мы можем установить рабочие ruby для приложения в docker без всяческих gcc, headers, -devel пакетов, только бинарники. Docker контейнеры подготавливаю как раз установкой этих rpm.
                    0
                    postmodern/ruby-install вам в помощь. Зачем RVM на сервере куда происходит деплой не понятно.
                      0
                      Вы тоже за rbenv? Поделитесь ссылкой на что-нибудь, что даст ясное понимание преимуществ?
                        +2
                        Товарищ имел ввиду другой подход — github.com/postmodern/ruby-install

                        А насчет плюсов и минусов — это как разные дистрибутивы линукса сравнивать. Это чрезвычайно субъективно. Просто пакетные менеджеры (читай — способы приспособить среду под себя для выполнения в ней ruby-скриптов)

                        Очень кратко мое мнение:

                        RVM — наиболее популярен, встраивается в окружение, меняет под себя настройки шелла, хранит все версии в себе, больше подходит для практики «один контейнер — одно окружение — один проект» (ну или несколько проектов но с единым набором вводных данных для начала работы).

                        rbenv — легковесен, не встраивается в систему, не цепляет лишнего, заменяет одну строчку в настройке шелла (и то руками пользователя), все дистрибутивы со всеми приблудами по папочкам, автономно, больше подходит для практики смешанного окружения на контейнер, когда разные приложения используют разные версии руби и набор необходимых ресурсов.

                        ruby-install — не совсем пакетный менеджер, скорее я бы назвал утилитой для установки любой версии руби в систему, больше подходит для задач «поставил и забыл», минимум настроек, версия по-умолчанию глобальна на контейнер.

                        -+-+-+-

                        Тем не менее, любой из инструментов справится со своей задачей — поставить нужную версию руби и настроить окружение. Любой можно использовать по своему усмотрению и у каждого свои плюшки. Первыми двумя я пользовался на реальных проектах, последний — разок ставил посмотреть нужно ли оно и счел для моих задач не нужным (читай — для моих задач не настолько нужным чтобы менять привычное на него).

                        Буду рад если кто аргументированно поправит.
                          0
                          Я за RVM. rbenv я терпеть не могу. Я не одобряю использование rvm'а и ему подобных на продакшен серверах.
                            0
                            Чрезвычайно интересно услышать подробное обоснование.
                            1. что не так с rbenv и как он мешает вашему подходу?
                            2. чем rvm лучше и почему вы за него?
                            3. почему не одобряете использование на продакшене?

                            Это не придирка, просто реально интересно обоснованное мнение, потому что кроме субъективного нравится / не нравится вообще в интернете мало что встречаю. В принципе не понимаю, как может нравиться или не нравиться консольная утилита или пакетный менеджер. Как минимум, интересны критерии оценки.
                              0
                              0. Зачем rbenv и rvm вообще? Так ли они часто нужны?
                                0
                                Это зависит от подхода в разработке и специфики версий.

                                Например, у нас частая задача — иметь несколько версий ruby для разных приложений дабы они не пересекались друг с другом. Это может быть полезно если несколько веб-приложений работают из одного контейнера, чаще всего это могут быть веб-сайты, которые могут быть написаны в разное время, и не факт что какие-то из них будут активно поддерживаться (клиенты оплатили работу, хостинг на год и все, на этом работа над сайтом закончилась), соответственно велик риск нарушить работоспособность сайта если обновить версию руби глобально. Решается как раз при помощи rbenv с привязкой отдельной версии руби и локом ее на проект.

                                Если вы работаете над большими приложениями или просто придерживаетесь подхода: один контейнер — одно приложение, то может вам ни rvm ни rbenv не нужны и вы ставите глобально версию руби в системе с которой работает конкретное приложение.

                                Как говорится — итс ап то ю.
                                  0
                                  А у ruby так много несовместимых версий, что Lock и всё? Сколько их сейчас актуальных? 2? 3?
                                    0
                                    Не так много, но например на реальном проекте когда ненароком обновили с 1.8.7 до 2.1.2 много чего отвалилось и проект упал, пролежав в дауне до тех пор пока не локализовали проблему. Да, это было всего пару часов от силы, но с тех пор взяли за практику лочить версии на проект. Опять же, это справедливо для нашего подхода который я описал, поэтому вам и кажется нелогичным потому что вы работаете иначе.
                                      0
                                      Логично. 1.8.7 это вообще другой язык. Частные применения мне понятны. Но это же совершенно специальные случаи. А тут прямо так мимоходом как само собой разумеющееся — «ставим rvm». В том же python все напропалую используют virtualenv. Но в python нет традиции bundler и там оно как раз его функции выполняет. А тут…

                                      P.S. Понятно, что я на хостинге держу php 5.2, тут даже пришлось копать патч, который его к apache 2.4 клеит… Но это очень специфичная бизнес-проблема хостинга…
                                  0
                                  0) Если на одной машине надо несколько версий руби. Позволяет очень просто установить разные версии с разными патчами и легко между ними переключаться. К примеру, у меня есть скрипты которые написаны под jruby (нужны настоящие треды), есть приложение которое должно работать в 2.0.0, на каждый день я использую 2.1.x, а так же есть одно легакси под 1.9.3 которое нельзя взять и обновить не уделяя времени на это.

                                  Делать Vagrant Box под каждый проект затратно. Впрочем у меня есть универстальная коробка под руби приложения, но это скорее чтобы не устаналивать 100500 зависимостей на рабочую машину.
                                  +1
                                  1) rbenv это три шага назад если сранивать с rvm; (тоесть до уровня питона)
                                  2) Я использую практически весь функционал rvm, которого в rbenv тупо нет
                                  3) Потому, что на продакшене должна быть одна версия руби и ровно одно приложение; если на продакшене зоопарк версий и приложений то это шаред хостинг для php, а не продакшен сервер.

                                    0
                                    1. размыто, хочется конкретики
                                    2. опять же как и по первому вопросу важна и интересна конкретика
                                    3. Зависит от приложений, подхода и ситуации. Один из кейсов описал в комментарии выше. Задачи бывают разные, и под приложением каждый понимает что-то свое. Приложением прекрасно может являться и сайт на пару страничек, для которого отдельный контейнер брать — жирновато. Предвижу что вы скажете мол зачем тогда вообще ему руби или пишите статику и юзайте шаред-хостинги, но опять же, это дело подхода, и соответственно личное дело каждого. В идеальном мире конечно нужно только по фен-шую все делать, но мы живем в реальности, где иногда один чайный пакетик можно на две и более кружек заварить и никто от этого не умрет.
                                      0
                                      1-2) Вы же понимаете, что это один и тот же вопрос? Меня просто не устраивает функционал rbenv, и нравится функционал rvm. Я же не эвангелист rvm, чтобы склонять кого-то на ту или иную сторону. Я считаю, что от rbenv несет питоном. Кого-то устраивает rbenv, лично меня нет.

                                      3) Используйте, я не против. Я просто этого не одобряю. Только потому не удивляйтесь когда у вас утечет база или, что-то в этом роде. Я помню как было весело проникат на один ресурс через соседний DLE. Впрочем, легковестные контейнеры существуют уже много лет: zones, jails. Теперь даже есть хипстерский docker.
                                        0
                                        1-2) я не пытаюсь кого-то достать или подколоть, мне реально интересна суть. Где если не на хабре могут развернуто и обоснованно рассказать об интересующей теме? Просто подход «нравится / не нравится» например попахивает не питоном а детским садом. Ценность ответа была бы намного выше если бы вы описали ту задачу которая решается без труда при помощи rvm но rbenv для нее не подходит — это была бы ценная и полезная информация, может быть побудившая кого-то написать отдельную статью. И нашлись бы люди кто рассказал как эту же задачу решают совершенно по-другому.

                                        3) проекты разного уровня требуют разных подходов и степеней защиты данных. По легковесным конейнерам в соседней теме на хабре обсуждали — это безусловно круто, но на это надо переходить, а перетаскивать over9000 проектов без надобности — выкидывать ценное время на реорганизацию того что уже работает и кушать не просит. Тем не менее думаем в этом направлении и ищем решения, пока не все прозрачно и удобно.
                                          0
                                          Собрать руби с правильными патчами. Создать 3 гемсета на основе того, что было собрано. Сделать обвертку для пары гемом — чтобы foo сразу использовать jruby, bar — использовать rbx. При этом все остальное было в 2.1.2. Каждый проект имеет свой гемсет который автоматически включается при заходе в директорию. Я так же хочу, что jruby всегда был доступен через обвертку. Технически rbenv даже не может установить руби.

                                          У кого это over9000 проектов? Что за фабрика визиток? Секрет контейнеров в том, что они существовали уже давно. Но линукс в них не мог (И все еще не может)

                              0
                              За ruby-install спасибо, не знал. Вот так и изобретаются велосипеды.
                              Только мой костыль уже немного продвинутее.
                              0
                              была похожая задача — решить можно простым шелл-скриптом и символьными ссылками. Хотя ваш подход реально интереснее. Тоже нечто подобное обдумываем, но пока это на стадии «отложено на завтра», пока уровень потраченного свободного времени не соотносится с явной или неявной выгодой. Но с каждым подобным примером интерес-таки к экспериментам растет.
                            +2
                            Оставлю это здесь: wiki.summercode.com/rails_deployment_step_by_step_to_ubuntu — пошаговое руководство по настройке деплоймента рельсо-приложения на один продакшн-сервер (там есть один нюанс в том, что статья основана на Capistrano 2.x, а не на 3-ем, у меня руки все никак не дойдут обновить)
                              +2
                              Замечание к статье: у вас не zero-downtime деплоймент. Вы сначала гасите все юникорны, а потом поднимаете. Будет даунтайм. Чтобы не было даунтайма, нужно рестартовать юникорны через сигналы. В моей инструкции (см. выше) это учтено (так же как и автоматическое поднятие юникорнов после перезагрузки сервера через init.d-скрипты).
                                0
                                Да, это просчет :-( Мне от чего-то казалось, что апстрим nginx-а продержит соединение пока перезапускается Unicorn. Ваш мануал хорош, но там много кода и мало слов — для чайников будет сложновато разобраться.
                                  +1
                                  Да, я с этим совершенно не буду спорить. Скорее писал его для себя, как памятку, и для того, чтобы скинуть кому-нибудь ссылку.

                                  А что касается юникорна и zero-downtime деплойменту дело вот в чем: при запуске юникорн создает процесс-мастер, который читает config/unicorn.rb (обычно) и создает какое-то кол-во воркеров — своих форков; и мастер же обслуживает unix-сокет. При получении сигнала на рестарт, мастер-процесс перестает распределять новые запросы из unix-сокета (куда их отправляет nginx) между детьми, он как раз запускает новый мастер, и ждет, пока тот поднимет нужное кол-во воркеров, а воркеры текущего вернут результаты обработки запросов и тогда они тоже гасятся. Потом передает бразды правления сокетом новому мастеру и само-уничтожается.
                                +5
                                Зачем же вы учите юзеру в sudoers указывать NOPASSWD:ALL =) Небезопасно, да и не за чем.
                                  0
                                  Ряд англоязычных гайдов по Капистрано рекомендует делать это именно таким образом; если очень страшно — предлагают nopasswd на исполнение ограниченного набора команд. Но если совсем страшно, то можно даже отказаться от выполнения блоков кода, требующих sudo; только линковать конфиги и перезапускать сервисы придется вручную.
                                    +1
                                    Чтобы меня потом не обвиняли в голословности, вот ссылка, раздел Authorisation. Я добавлю в текст, но смысл таков: если требуется использование собственных сценариев (базовые функции Capistrano не требуют sudo), то необходимо иметь этот самый paswodless sudo. В контексте статьи и вашего комментария, строка в sudoers может выглядеть так:

                                    demo ALL=NOPASSWD:/etc/init.d/mysqld, /etc/init.d/apache2
                                    
                                      +3
                                      Так уже лучше. Мало ли кто-то напишет <%= `ls #{params[:dir]}` %>. Пример, конечно, совсем деревянный, но могут и другие дыры оказаться.
                                      Вообще конфигурацию веб-сервера и субд, думаю, нелогично в капистрано запихивать. Если много серверов, для этого есть chef. Если один — можно вручную настроить.
                                    +2
                                    Добавьте в Capfile:
                                    require 'capistrano/rails/assets'
                                    require 'capistrano/rails/migrations'
                                    

                                    Иначе придётся при каждом деплое вручную накатывать миграции и компилировать ассетсы.
                                      +1
                                      А мы реквайрим же полный стек capistrano/rails — и миграции, и ассеты компилируются в таком виде.
                                        0
                                        извините, не заметил. Никогда не приходило в голову реквайрить полностью весть capistrano rails, посмотрел gem, это по сути то же самое.
                                      0
                                      А зачем nodejs устанавливать?
                                        0
                                        для сборки ассетов
                                          0
                                          Rails по-дефолту хочет работающий ExecJS для компиляции coffee -> js. Можно не ставить.
                                          +1
                                          После нескольких настроенных руками серверов пишется что-то вроде https://github.com/arrowcircle/chef-rails-suite и все мелкие проекты больше не вызывают боли
                                            0
                                            Chef это слишком тяжеловесно, мне кажется Ansible тут в самый раз будет. Заодно можно и функции Capistrano заменить.
                                            Если кому будет интересно, то могу написать playbook и сделать обзорную статью здесь на эту тему.
                                              0
                                              Не можно, а нужно. И вот я бы даже попросил меня известить :)
                                                0
                                                Я такую штуку уже делал для клиента. Лежит на гитхабе https://github.com/redde/ansible-rails-stack. К сожалению, доки нет и основная работа идет в сабмодулях.
                                                  0
                                                  Вот, отлично!

                                                  Из improvements я бы дописал еще две роли (RVM + source), то есть выбор способа установки нужной версии Ruby(не все апологеты rbenv). Ну и в головном плейбуке файл Readme бы дописать. Раз уж вы в гэлэкси это закинули, то описание минимальное надо.
                                                  + сервер бы, Unicorn/PUMA
                                                    0
                                                    В галакси не закидывал, пока только дописал файлы метадаты да и то не для всего.
                                                    Добавление rvm потребует написания кучи кода. Также, как и использование mysql.
                                                    Для шеф рецептов я игрался с пумой, однако в продакшене использую только unicorn. Гитлаб ушел обратно на unicorn с пумы, ну и я не знаю ни одного проекта на пуме.

                                                    Я не до конца разобрался с переменными в ansible, поэтому в коде есть не очень красивые куски.

                                                    Если хотите — присоединяйтесь к разработке.
                                                      0
                                                      Конечно, коллега.
                                                      В свободное время помогу Вам с развитием плейбуков.
                                              +2
                                              Поправьте — редактировать sudoers следует ТОЛЬКО командой visudo. Она вызывет /etc/alternatives/editor который вы можете настроить в т.ч. и на nano. Но самое главное — visudo выполняет проверку синтаксиса /etc/sudoers, а также проверяет не редактирует ли этот файл кто-то вместе с вами.
                                                0
                                                добавлю, что если вам нужно запустить visudo под конкретным редактором на один раз, то можно перед запуском в переменной окружения EDITOR указать желаемый редактор:
                                                $ sudo EDITOR=nano visudo
                                                0
                                                Nginx в Ubuntu стоит ставить не сысоевский, а собранный ребятами из команды поддержки nginx в Ubuntu из соотв. PPA ppa:nginx/stable launchpad.net/~nginx/+archive/ubuntu/stable
                                                  0
                                                  Сразу за оба комментария спасибо! — Сейчас поправлю.
                                                    +1
                                                    почему, кстати?)
                                                      0
                                                      С чего бы?
                                                      –4
                                                      Ничего не говорит за VPS. VPS всегда медленее шареда. Просто надо шаред искать нормальный.

                                                      Я не совсем понял, зачем в этой статье RVM. Это какая-то прослойка типа болонки для сдачи спектакля комиссии? Понятно зачем он нужен, но что он даёт в контексте статьи. Я бы например сделал упор на установку bundler и gem в домашний каталог, или вот bundle standalone. Об этом многие не знают и начинается тыкание rvm во все дыры.

                                                      P.S. А вообще любая статья про деплой — это круто :)
                                                        0
                                                        Гм, что-то и правда много ругаются на RVM, придется переписать этот кусочек. А по поводу хостинга ты, Щорс, предвзят :-) А если нет, то предлагай, про выбор хостинга там много свободного места осталось.
                                                          0
                                                          Я не предвзят. Я же дауншифтингом занимаюсь. VPS — это боль. Да, иногда он нужен, не спорю. Но для размещения приложения на рельсах вполне сойдёт например мой шаред. И вот да — вопрос развёртывания приложения он важен.
                                                            0
                                                            RVM он на самом деле тоже нужен. Но он тут портит всё своей лишнестью. Или тогда надо как-то привязать, где он тут к месту
                                                              0
                                                              До меня просто не совсем доходит что же в нем такого страшного, как по мне — он примерно Brew в Mac OS X. Пишут, что подменяет собой кучу пакетов, гадит по разным директориям. Я может чего-то не понял, но всегда думал, что использует только ~/.rvm и выпилить его можно удалив эту директорию и пару строчек из ~/.bash_profile?
                                                                0
                                                                А зачем? В чём смысл? В ruby в отличии даже от того же Python есть bundler, который 99% задач с разными пакетами решает.
                                                          0
                                                          del
                                                            0
                                                            Настраивал сервера, используя конфиги вот отсюда: gist.github.com/mikhailov/3052776
                                                            Может быть там мало комментариев и подробностей, зато неплохо прокачал скилл в процессе настройки.
                                                              0
                                                              Чем хуже ruby + nginx + passenger? Можно посмотреть бенчмарки?
                                                                0
                                                                Есть бенч 2009 года, но там а) не все так однозначно б) прошло пять лет. Есть свежий взгляд у Ярда, но выводы несколько иные.

                                                                Я писал про Unicorn потому что это не худший вариант. Кроме того, он позволяет чуть полнее понять принцип работы всей связки и возможности настройки. Плюс я не знаю как у пассажира с выносом за пределы одного сервера. Поделитесь своим опытом?

                                                                Но в целом-то вопрос из серии modphp или phpfpm, нет?
                                                                  0
                                                                  Мне показалось, что nginx + passenger гораздо проще в настройке. Если нет разницы в производительности, то и нет смысла «платить больше», как по мне.
                                                                0
                                                                Дико извиняюсь, если мои вопросы уже были раскрыты в комментах, но лень-матушка переборола над поиском во всех комментариях.

                                                                1. Зачем на production-сервере использовать Ubuntu. Ведь намного удобнее CentOS, или тот же самый Debian?

                                                                2. Насчет Vagrant. Если использовали, то в чем профит после поднятия сервера? То есть, вы же не дропаете сервер каждое утро и не поднимаете его с нуля.

                                                                3. Лично вас, устраивает RVM на сервере? Просто я не заметил особых недостатков в его использовании не на машине для локал-девелопмента.
                                                                  0
                                                                  Мне лично прекрасно подходит Убунта. Я не слышал ни о каких побочных эффектах, противопоказаниях и проблем за последние три года не испытывал.

                                                                  Против RVM в комментариях выше уже высказывались; я, ради чистоты эксперимента, проделал все описанное в тексте, но с RBENV (плюс подменить одну строку импорта в Capfile). Апдейт к статье пишется (также и про мониторинг), скоро приаттачу отдельную ссылку (ибо редактор хабра — мука).

                                                                  Про Вагрант совсем не понял вопроса %)
                                                                    0
                                                                    Интересно, а чем Debian удобнее Ubuntu?)
                                                                      0
                                                                      Времена, когда Ubuntu «могла быть» хуже Debian давно прошли и Вам настало время забросить этот топор войны.
                                                                      А то получается, война закончилась, а Вы все поезда под откос пускаете.
                                                                      Используем 200+ виртуальных и хостовых машин на Ubuntu(10,12,14 lts) и Debian и не находим каких-либо принципиальных отличий, чтобы пересесть только на Debian.
                                                                      0
                                                                      Как раз сам хотел написать на днях подобную инструкцию, но вы справились отлично.

                                                                      В принципе, мы в работе используем тоже самое, только связка nginx + passenger и вместо RVM используем rbenv, но в общем ничего не отличается практически.
                                                                        0
                                                                        В комментариях за RVM я уже получил тумаков, скоро приложу ссылку на апдейт с Rbenv. Но есть вакантный слот с хорошим тестом производительности серверов приложений. Вы вот почему passenger выбрали?
                                                                          +1
                                                                          Ну есть и на Unicorn проекты, но все актуальные проекты на passenger.

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

                                                                          В принципе, это как и в вопросе rbenv/rvm — чистой воды вкусовщина. По той же производительности смотря как мерить. Разные приложения имеют разный принцип работы, и где-то пассажир показывает лучшие результаты в определенных условиях, а где-то единорог.

                                                                          Ключевой аспект выбора именно то что используем парадигму нескольких проектов на одной VPS. Хотя планируем в не столь отдаленном будущем переходить на какое-то решение наподобие Docker'а, и концепцию «одно приложение — одно окружение», но пока в этом направлении еще маловато опыта и перестраивать воркфлоу на ровном месте с того к чему привыкли — не самый лучший фен-шуй.

                                                                      Only users with full accounts can post comments. Log in, please.