Rails окружение на сервере

rails

В предыдущей статье была настроена рабочая станция. Теперь нужно настроить сервер для хостинга приложения в production среде. И что не менее важно — автоматизировать процесс обновления приложения на сервере, деплой.

  • Вход на сервер с рабочей станции через SSH по ключу, без ввода пароля.
  • Git репозиторий.
  • Настройка уже знакомых пакетов с необходимыми поправками на production среду.
  • Nginx в роли сервера статических файлов.
  • Unicorn в роли сервера Rails, интеграция в систему с помощью /etc/init.d/ скрипта.
  • Capistrano, деплой.

Исходные данные #

  • Сервер и рабочая машина в одной локальной сети. Поэтому я использую как адрес 192.168.0.1 вместо dobroserver.ru.
  • На рабочей машине я использую Arch Linux, на сервере Debian/testing.

SSH #

К настройке SSH стоит подойти основательно.

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

Права доступа #

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

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

Поэтому в нашем случае мы будем запускать веб-серверы Nginx и Unicorn от пользователя www-data группы www-data.

Подключайтесь к серверу и создавайте директорию для ваших сайтов/приложений:

mkdir -p /data/projects

Владелец <системный пользователь>:www-data:

sudo chown -R ksevelyar:www-data /data/projects
sudo chmod 770 -R /data/projects

Разрешим основному пользователю перезапускать Unicorn и выставлять владельца для файлов без ввода пароля:

sudo visudo
%sudo   ALL=(ALL:ALL) ALL
%sudo   ALL=NOPASSWD: /etc/init.d/unicorn_dobroserver [A-z]*
%sudo   ALL=NOPASSWD: /bin/chown -R ksevelyar\:www-data /data/projects/[a-zA-Z0-9]*

Репозиторий Git #

Cоздадим репозиторий для нашего приложения:

mkdir -p /data/git/dobroserver.git
cd /data/git/dobroserver.git
git init --bare

Отключайтесь от сервера и переходите в директорию приложения.

cd /data/projects/dobroserver

И подключайте его к новенькому репозиторию на сервере:

git init
git add .
git commit -m "Initial commit."
git remote add origin ssh://ksevelyar@192.168.0.1/data/git/dobroserver.git
git push origin master

RVM + Ruby #

Возвращайтесь к серверу:

sudo aptitude install -y curl git-core

Многопользовательская установка rvm:

curl -L get.rvm.io | sudo bash -s stable

Добавляйте нужных пользователей в группу rvm:

sudo usermod -aG rvm www-data
sudo usermod -aG rvm ksevelyar

Устанавливайте необходимые пакеты:

rvm requirements
sudo aptitude install -y build-essential openssl libreadline6 libreadline6-dev zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion pkg-config

Документация на production не нужна:

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

Руби будет установлен с помощью Capistrano, вручную его ставить не нужно.

Rails #

Так как создавать приложения используя команду rails new на сервере мы не будем, то вся установка ложится на bundler. Никакие гемы глобально ставить не нужно.

Единственное, что нужно сделать, это установить необходимые приложению системные пакеты:

sudo aptitude install -y nodejs imagemagick

Postgresql #

sudo aptitude install postgresql postgresql-client libpq-dev

Создадим базу данных для нашего приложения:

sudo -u postgres createdb dobroserver_production

Создадим пользователя с паролем:

sudo -u postgres psql
CREATE USER dobroserver WITH PASSWORD 'p@ssw0rd';

И дадим ему доступ к базе приложения:

GRANT ALL PRIVILEGES ON DATABASE dobroserver_production TO dobroserver;

Выход из консоли psql: \q.

Nginx #

Nginx отдаёт все статические файлы (включая Assets Pipeline). И это он делает отлично.

sudo aptitude install nginx -y
sudo mkdir -p /etc/nginx/sites-{available,enabled}

Удаляем хост по умолчанию, если он есть (особенность Debian):

sudo rm -f /etc/nginx/sites-enabled/default

Глобальная конфигурация:

sudo vim /etc/nginx/nginx.conf
user www-data;
worker_processes  1;

events {
    worker_connections  1024;
    accept_mutex off; # "on" if nginx worker_processes > 1
    use epoll; # enable for Linux 2.6+
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile on;
    tcp_nopush on; # off may be better for *some* Comet/long-poll stuff
    tcp_nodelay off; # on may be better for some Comet/long-poll stuff

    gzip  on;

    include /etc/nginx/sites-enabled/*;
}

Создаём виртуальный хост для нашего приложения:

sudo vim /etc/nginx/sites-enabled/dobroserver
upstream dobroserver {
  server unix:/data/projects/dobroserver/shared/unicorn.sock fail_timeout=0;
}

server {
  listen www.dobroserver.ru:80;
  server_name www.dobroserver.ru;
  return 301 $scheme://dobroserver.ru$request_uri;
}

server {
  listen dobroserver.ru:80;
  server_name dobroserver.ru;

  keepalive_timeout 5;
  client_max_body_size    50m;
  client_body_buffer_size 128k;

  root /data/projects/dobroserver/current/public;

  error_log  /data/projects/dobroserver/shared/log/nginx_error.log;
  access_log off;

  rewrite_log on;

  # Rails error pages
  error_page 500 502 503 504 /500.html;
  location = /500.html {}

  # Rails assets pipeline
  location ~ ^/assets/ {
    gzip_static on; # to serve pre-gzipped version
    expires max;
    add_header Cache-Control public;
  }

  location / {
    try_files $uri @unicorn;
  }

  location @unicorn {
    proxy_pass  http://dobroserver;
    proxy_redirect     off;

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

    proxy_connect_timeout      90;
    proxy_send_timeout         90;
    proxy_read_timeout         90;

    proxy_buffer_size          4k;
    proxy_buffers              4 32k;
    proxy_busy_buffers_size    64k;
    proxy_temp_file_write_size 64k;
  }
}

Перезапускайте сервер:

sudo service nginx restart

Unicorn #

Переходите в директорию приложения на рабочей станции. И устанавливайте веб-сервер для Рельс, это обычный гем:

gem 'unicorn'
bundle install

Настройка #

vim config/unicorn.rb
app_path   = "/data/projects/dobroserver"
rails_root = "#{app_path}/current"

pid_file   = "#{app_path}/shared/pids/unicorn.pid"
pid          pid_file
old_pid    = pid_file + '.oldbin'

socket_file= "#{app_path}/shared/unicorn.sock"
listen socket_file, :backlog => 64

stdout_path  "#{rails_root}/log/unicorn.log"
stderr_path  "#{rails_root}/log/unicorn_error.log"

worker_processes 4
timeout 30

# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
preload_app true

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

before_fork do |server, worker|
  # the following is highly recomended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  # zero-downtime
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

after_fork do |server, worker|
  # the following is *required* for Rails + "preload_app true",
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

Интеграция в систему #

Мы будем запускать Unicorn в init, поэтому необходимо создать обёртку:

rvm wrapper default default bundle

Где лежит обёртка можно посмотреть так:

which default_bundle
/usr/local/rvm/bin/default_bundle

И посмотреть на какую версию она ссылается:

ls -la /usr/local/rvm/bin/default_bundle
lrwxrwxrwx 1 ksevelyar rvm 46 Dec 14 10:38 /usr/local/rvm/bin/default_bundle -> /usr/local/rvm/wrappers/ruby-1.9.3-p327/bundle

Скрипт, с помощью которого будем управлять Unicorn:

sudo vim /etc/init.d/unicorn_dobroserver
#!/bin/sh

### BEGIN INIT INFO
# Provides:          unicorn_dobroserver
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the unicorn web server
# Description:       starts unicorn using start-stop-daemon
### END INIT INFO

APP_PATH=/data/projects/dobroserver
RAILS_ROOT=$APP_PATH/current

PID=$APP_PATH/shared/pids/unicorn.pid
OLD_PID="$PID.oldbin"

CONFIG=$RAILS_ROOT/config/unicorn.rb

ENV=production
USER=www-data

bundle="/usr/local/rvm/bin/default_bundle"
CMD="$bundle exec unicorn -E $ENV -c $CONFIG -D"

cd $RAILS_ROOT || exit 1

sig () {
  test -s "$PID" && kill -$1 $(cat $PID)
}

oldsig () {
  test -s $OLD_PID && kill -$1 $(cat $OLD_PID)
}

case $1 in
  start)
    sig 0 && echo >&2 "Already running" && exit 0
    su $USER -c "$CMD"
    ;;
  stop)
    sig QUIT && exit 0
    echo >&2 "Not running"
    ;;
  force-stop)
    sig TERM && exit 0
    echo >&2 "Not running"
    ;;
  restart|reload)
    sig HUP && echo reloaded OK && exit 0
    echo >&2 "Couldn't reload, starting '$CMD' instead"
    su $USER -c "$CMD"
    ;;
  upgrade)
    sig USR2 && exit 0
    echo >&2 "Couldn't upgrade, starting '$CMD' instead"
    su $USER -c "$CMD"
    ;;
  rotate)
    sig USR1 && echo rotated logs OK && exit 0
    echo >&2 "Couldn't rotate logs" && exit 1
    ;;
  *)
    echo >&2 "Usage: $0 <start|stop|restart|upgrade|rotate|force-stop>"
    exit 1
    ;;
esac
sudo chmod +x /etc/init.d/unicorn_dobroserver
sudo insserv unicorn_dobroserver

Capistrano #

Что делает Capistrano? #

. Подключается к серверу через ssh.
. Клонирует указанный репозиторий на сервер в /data/projects/dobroserver/current.
. Компилирует Assets Pipeline.
. Линкует database.yml на файл содержащий пароль к production базе данных.
. Перезапускает сервер без потери запросов (та самая фича Unicorn, zero-downtime).

Помимо этого Capistrano может установить/обновить RVM и Ruby, и автоматически обновляет гемы через bundler, если вы изменили Gemfile.

И может сделать что угодно ещё, что вы напишите или настроите сами.

Установка #

Добавьте гемы turbo-sprockets-rails3, capistrano, rvm-capistrano:

vim Gemfile
source 'https://rubygems.org'

gem 'rails', '3.2.9'

gem 'pg'

group :assets do
  gem 'sass-rails'
  gem 'coffee-rails'

  gem 'uglifier'

  gem 'turbo-sprockets-rails3'
end

gem 'jquery-rails'
gem 'slim-rails'

gem 'kaminari'

gem 'carrierwave'
gem 'mini_magick'

gem 'sanitize'

# To use ActiveModel has_secure_password
gem 'bcrypt-ruby'

# Use unicorn as the app server
gem 'unicorn'

# Deploy with Capistrano
group :development do
  gem 'pry'

  gem 'capistrano'
  gem 'rvm-capistrano'
end

Обратите внимание на гем turbo-sprockets-rails3, благодаря ему при деплое компилироваться будут только изменившиеся файлы Assets Pipeline. Настраивать его не нужно.

bundle install
capify .

Включите прекомпиляцию Assets Pipeline:

vim Capfile
load 'deploy/assets'

И настраивайте сам деплой:

vim config/deploy.rb
# REQUIRE
require 'rvm/capistrano'
set :rvm_type,        :system
set :rvm_ruby_string, ENV['GEM_HOME'].gsub(/.*\//,"")

require 'bundler/capistrano'

# SETTINGS
set :application, "dobroserver"
set :deploy_to,   "/data/projects/#{application}"

set :domain, "192.168.0.1"
set :user,   "ksevelyar"

set :rails_env, "production"

server domain, :web, :app, :db, primary: true

set :use_sudo, false
set :keep_releases, 10

set :shared_children, shared_children + %w{public/uploads}

set :scm,                   :git
set :branch,                "master"
set :deploy_via,            :export
set :git_enable_submodules, 1
set :repository,            "/data/git/#{application}.git"
set :local_repository,      "ssh://#{user}@#{domain}/data/git/#{application}.git"

# TASKS
before 'deploy:setup', 'rvm:install_ruby'

after 'deploy:update_code', :roles => :app do
  run "rm -f #{current_release}/config/database.yml"
  run "ln -s #{deploy_to}/shared/config/database.yml #{current_release}/config/database.yml"
  run "sudo chown -R $(whoami):www-data #{deploy_to}"
end

after 'deploy', 'deploy:migrate', 'deploy:cleanup'

namespace :deploy do
  task :restart do
    run "sudo /etc/init.d/unicorn_dobroserver upgrade"
  end
  task :force_restart do
    run "sudo /etc/init.d/unicorn_dobroserver stop"
    run "sudo /etc/init.d/unicorn_dobroserver start"
  end
  task :start do
    run "sudo /etc/init.d/unicorn_dobroserver start"
  end
  task :stop do
    run "sudo /etc/init.d/unicorn_dobroserver stop"
  end
end

Подготовка #

Подготовим для хоста всё необходимое:

cap deploy:setup

Эта команда установит необходимую версию Ruby на сервере и создаст все папки для хоста.

Как вы помните для базы данных на сервере мы используем отдельного пользователя и пароль, которые в конфиге на рабочей машине не указаны. Поэтому в deploy.rb есть таск, который заменяет config/database.yml из репозитория на файл из shared/config.

Подключайтесь к серверу и создавайте этот файл:

cd /data/projects/dobroserver/shared
mkdir config
vim config/database.yml
production:
  adapter: postgresql
  encoding: unicode
  database: dobroserver_production
  pool: 5
  username: dobroserver
  password: p@ssw0rd
  host: localhost

Деплой #

Теперь можно смело выгружать приложение:

cap deploy

Если вы изменили Gemfile (добавили или удалили гемы), то Unicorn после деплоя нужно перезапустить «жёстко»:

cap deploy:force_restart

Чтобы посмотреть доступные задачи с описанием:

cap -T

И вики, в которой много интересного: github.com/capistrano/capistrano/wiki:https://github.com/capistrano/capistrano/wiki

Резервное копирование #

Последнее в списке, но не последнее по важности:

Итог #

Чтобы обновить приложение на сервере нужно выполнить всего одну команду:

cap deploy

Не забыв, конечно, предварительно отправить изменения в репозиторий:

git add .
git commit -m "Does anyone read this? I'll be at the coffee shop accross the street."
git push origin master

Комментарии

Gringo

Добрый день. Делаю все по вашей инструкции, получаю следующую ошибку:

*** [err :: xxxxxxx] Error: RVM was unable to use 'ruby-1.9.3-p194'
command finished in 146ms
failed: "rvm_path=/usr/local/rvm /usr/local/rvm/bin/rvm-shell 'ruby-1.9.3-p194' -c 'mkdir -p /data/projects/app
/data/projects/app/releases
/data/projects/app/shared
/data/projects/app/shared/system
/data/projects/app/shared/log
/data/projects/app/shared/pids
/data/projects/app/shared/uploads'" on xxxxxxxx
RVM уже переставлял, права на папку с проектами выставлены.

ksevelyar

Пользователь от которого запускается деплой добавлен в группу rvm?

Советую посмотреть выход команд:

rvm install 1.9.3
rvm use 1.9.3 --default
ruby -v

Запуская их все от пользователя, через которого идёт деплой. Скорее всего одна из них вернёт более информативную ошибку.

Gringo

пользователь добавлен в группу rvm.
Прошлую проблему решил, теперь столкнулся с этим:

*** [err :: server] /etc/init.d/unicorn_app: 30: kill:
*** [err :: server] No such process
*** [err :: server]
*** [err :: server] Couldn't upgrade, starting '/usr/local/rvm/bin/default_bundle exec unicorn -E production -c /data/projects/app/current/config/unicorn.rb -D' instead
*** [err :: server] su: must be run from a terminal
command finished in 232ms
failed: "rvm_path=/usr/local/rvm /usr/local/rvm/bin/rvm-shell 'ruby-1.9.3-p194' -c '/etc/init.d/unicorn_app upgrade'" on server

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

ksevelyar

Проверяйте выход команды отдельно:

Выделить код
sudo /etc/init.d/unicorn_app upgrade

Добавить комментарий