Настройка ruby 1.9.3 в облаке Hostpro
Друзья, сегодня мы хотим поделиться с вами интересным материалом, который был написан нашим клиентом. Ниже будет описана процедура установки Ruby 1.9.3 + Unicorn + Nginx + Mysql на сервер под управлением CentOS 5.x. Весь процесс установки происходил на сервере, который размещён в нашем облаке.
Это краткий конспект шагов, необходимых для установки и запуска нескольких одновременно работающих Ruby on Rails приложений на CloudVPS сервере Hostpro с чистым дистрибутивом CentOS 5.5.
Устанавливаемое ПО
- CentOS 5.5
- Mysql 5
- Nginx
- RVM
- Ruby 1.9.3
- Unicorn web server
- Rails 3.2
CentOS — операционная система, MySQL — базы данных, с ними вроде всё ясно.
Nginx + Unicorn — одна из самых популярных связок в Ruby-мире. Nginx встречает запросы и отдаёт статику (картинки, скрипты, css и прочее), а остальное передаёт Rails-приложению т.е. Unicorn’у. Это позволяет увеличить скорость обработки запросов и существенно снизить нагрузку с приложения. https://github.com/blog/517-unicorn.
RVM — Ruby Version Manager. Позволяет использовать разные версии Ruby и наборы gem-пакетов в Rails-приложениях. Например, одно из приложений всё ещё работает на Ruby 1.8.7 и использует старые версии gem’ов. Типичный пример — Redmine, который не хочется апгрейдить. Мы создаём под него свою конфигурацию Ruby 1.8.7 + пакеты и т.д. https://rvm.io
Создаём пользователя и настраиваем SSH-доступ
Уже в самом начале удобно задать для сервера алиас и прописать его в настройках ~/.ssh/config:
<div>
<pre>Host hostpro
Hostname 194.28.86.211 # или Hostname onapp.my_domain.net</pre>
</div>
Такая запись позволяет впоследствии писать:
<div>
<pre>ssh hostpro # вместо [email protected]_domain.net</pre>
</div>
Итак, перед нами чистый дистрибутив CentOS с root-доступом:
<div>
<pre>ssh root@hostpro
Last login: Fri Jun 15 01:20:47 2012 from 91.218.89.42
[root@onapp ~]# cat /etc/redhat-release
CentOS release 5.5 (Final)
[root@onapp ~]# pwd
/root</pre>
</div>
Добавляем пользователя:
<div>
<pre>useradd -m -G users wheel stanislaw</pre>
</div>
Задаём пароль:
<div>
<pre>passwd stanislaw
cd /home/stanislaw</pre>
</div>
С локального компьютера копируем публичный ключ ssh на сервер, на локальном компьютере выполняем:
<div>
<pre>scp ~/.ssh/id_rsa.pub hostpro:/home/stanislaw</pre>
</div>
На сервере содержимое публичного ключа добавляем в перечень допустимых ключей (сервер чистый, поэтому, конечно, файл ~/.ssh/authorized_keys пока пуст):
<div>
<pre>[stanislaw@onapp ~]$ mkdir ~/.ssh
[stanislaw@onapp ~]$ cat ~/id_rsa >> ~/.ssh/authorized_keys</pre>
</div>
Результат — теперь мы можем просто писать:
<div>
<pre>ssh hostpro</pre>
</div>
Вводим свой любимый пароль и попадаем в домашнюю директорию созданного пользователя.
Устанавливаем MySQL
Устанавливаем пакеты:
<div>
<pre>su
yum install mysql mysql-devel mysql-server</pre>
</div>
Запускаем MySQL:
<div>
<pre>/etc/init.d/mysqld start</pre>
</div>
Задаём root-пароль:
<div>
<pre>/usr/bin/mysqladmin -u root password 'root-cool-pass'</pre>
</div>
Заходим в mysql-консоль, чтобы создать пользователя stanislaw:
<div>
<pre>mysql -u root -p
mysql> GRANT ALL PRIVILEGES ON *.* TO 'stanislaw'@'localhost'
IDENTIFIED BY 'mysql-cool-pass' WITH GRANT OPTION;</pre>
</div>
Впоследствии, конечно, желательно ограничить права пользователя stanislaw — разрешить только те базы, для которых ему действительно нужен доступ.
Добавляем MySQL в автозапуск (сервер по умолчанию работает на runlevel 3):
<div>
<pre>/sbin/chkconfig --add mysqld
/sbin/chkconfig --level 3 mysqld on</pre>
</div>
Nginx
yum install nginx
После установки нужно сделать:
<div>
<pre>chmod 0751 /home/stanislaw # Если интересно, причина: http://stackoverflow.com/questions/6795350/nginx-403-forbidden-for-all-files</pre>
</div>
Добавляем nginx в авто-запуск:
<div>
<pre>sudo /sbin/chkconfig nginx on
sudo /sbin/chkconfig --list nginx
nginx 0:off 1:off 2:on 3:on 4:on 5:on 6:off</pre>
</div>
Git
В пакетах CentOS 5 git отсутствует.
Наиболее частое решение — использовать дополнительный репозиторий (https://webtatic.com/)
<div>
<pre>rpm -Uvh http://repo.webtatic.com/yum/centos/5/latest.rpm
yum install --enablerepo=webtatic git-all</pre>
</div>
ImageMagick
Его обязательно потребует при установке gem rmagick, который используется практически в любом Rails-проекте. Понадобится свежая версия прямо с родного сайта, т.к. версия в репозитории CentOS 5 очень давняя:
<div>
<pre>wget http://www.imagemagick.org/download/linux/CentOS/i386/ImageMagick-6.7.7-7.i386.rpm
wget http://www.imagemagick.org/download/linux/CentOS/i386/ImageMagick-devel-6.7.7-7.i386.rpm</pre>
</div>
Библиотеки, которые потребуются при сборке:
<div>
<pre>yum install libtool-ltdl lzma freetype-devel jasper-devel</pre>
</div>
И, наконец:
<div>
<pre>rpm -Uvh ImageMagick-6.7.7-7.i386.rpm
rpm -Uvh ImageMagick-devel-6.7.7-7.i386.rpm</pre>
</div>
Ruby + RVM
RVM рекомендуется устанавливать в домашнюю директорию своего пользователя. Все действия (кроме установки пакетов с использованием yum) в этом разделе ведутся от имени пользователя, а не администратора!
Чтобы не возиться с SSL-сертификатами (после установки RVM это, конечно, лучше убрать):
<div>
<pre>echo insecure >> ~/.curlrc</pre>
</div>
Устанавливаем RVM:
<div>
<pre>curl -L https://get.rvm.io | bash -s stable</pre>
</div>
Смотрим, что нужно для установки Ruby:
<div>
<pre>rvm requirements</pre>
</div>
Команда отобразит все необходимые зависимости, которые выглядят так:
<div>
<pre>yum install -y gcc-c++ patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison</pre>
</div>
Дополнительные зависимости для разных гемов:
<div>
<pre>yum install libxml2 libxml2-devel libxslt libxslt-devel</pre>
</div>
Наконец, можно установить Ruby:
rvm install 1.9.3
И Bundler:
<div>
<pre>gem install bundler</pre>
</div>
Unicorn
<div>
<pre>gem install unicorn</pre>
</div>
Наверное, единственное, что стоит отметить про связку Nginx + Unicorn — это то, что между собой Unicorn и Nginx могут взаимодействовать как через порты, так и через сокеты. Второй способ предпочтителен и используется в конфигурации ниже: Unicorn слушает socket-файл; это означает, что со стороны портов Unicorn можно закрыть вообще, см. соответствующий комментарий в конфигурационном файле config/unicorn.rb ниже.
Предположим, что у нас есть два Rails-приложения: first_app и second_app.
Каждое приложение должно содержать конфигурационный файл config/unicorn.rb со следующим содержанием (пример дляfirst_app):
Конфигурация Unicorn (config/unicorn.rb)
<div>
<pre>#worker_processes 4
deploy_to = "/home/stanislaw/apps/first_app"
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"
error_log_file = "#{rails_root}/log/unicorn_error.log"
old_pid = pid_file + ".oldbin"
working_directory rails_root
listen socket_file, :backlog => 64
# listen 8080, :tcp_nopush => true # Слушать порт нет необходимости, потому что nginx будет передавать запросы через socket-файл.
pid pid_file
stderr_path error_log_file
stdout_path log_file
timeout 30
preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
GC.copy_on_write_friendly = true
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?(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|
# per-process listener ports for debugging/admin/migrations
# addr = "127.0.0.1:#{9293 + worker.nr}"
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
# the following is *required* for Rails + "preload_app true",
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
# if preload_app is true, then you may also want to check and
# restart any other shared sockets/descriptors such as Memcached,
# and Redis. TokyoCabinet file handles are safe to reuse
# between any number of forked children (assuming your kernel
# correctly implements pread()/pwrite() system calls)
end</pre>
</div>
Обратите особое внимание на путь, указанный в переменной pid_file — он понадобится нам для работы скрипта автозапуска/etc/init.d/unicorn, а путь из socket_file мы укажем в настройках Nginx.
Конфигурация Nginx (/etc/nginx/nginx.conf)
<div>
<pre>user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log;
#error_log /var/log/nginx/error.log notice;
#error_log /var/log/nginx/error.log info;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
gzip on;
gzip_http_version 1.0;
gzip_proxied any;
gzip_min_length 500;
gzip_disable "MSIE [1-6]\.";
gzip_types text/plain text/html text/xml text/css
text/comma-separated-values
text/javascript application/x-javascript
application/atom+xml;
include /etc/nginx/conf.d/*.conf;
upstream first_app_server {
server unix:/home/stanislaw/apps/first_app/shared/unicorn.sock
fail_timeout=0;
}
upstream second_app_server {
server unix:/home/stanislaw/apps/second_app/shared/unicorn.sock
fail_timeout=0;
}
server {
listen 80 default_server;
client_max_body_size 4G;
server_name first_app.com www.first_app.com;
location /robots.txt { alias /usr/local/etc/nginx/robots.txt; }
location ~ ^/assets/ {
expires 1y;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
break;
}
keepalive_timeout 5;
root /home/stanislaw/apps/first_app/current/public;
try_files $uri/index.html $uri.html $uri @app;
location @app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://first_app_server;
}
error_page 500 502 503 504 /500.html;
location = /500.html {
root /home/stanislaw/apps/first_app/current/public;
}
}
server {
listen 80 default_server;
client_max_body_size 4G;
server_name second_app.com www.second_app.com;
location /robots.txt { alias /usr/local/etc/nginx/robots.txt; }
location ~ ^/assets/ {
expires 1y;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
break;
}
keepalive_timeout 5;
root /home/stanislaw/apps/second_app/current/public;
try_files $uri/index.html $uri.html $uri @app;
location @app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://second_app_server;
}
error_page 500 502 503 504 /500.html;
location = /500.html {
root /home/stanislaw/apps/second_app/current/public;
}
}
}</pre>
</div>
Конфигурация Capistrano (config/deploy.rb)
<div>
<pre>load 'deploy/assets'
# require 'bundler/capistrano'
# Важные строки для правильного включения RVM
https://rvm.io//integration/capistrano/
set :rvm_ruby_string, '1.9.3'
require 'rvm/capistrano'
set :user, "stanislaw"
set :application, "first_app"
set :repository, "."
set :hostpro_ip, "194.28.86.211"
set :deploy_to, "/home/stanislaw/apps/first_app"
set :scm, :none
set :scm_verbose, :true
set :deploy_via, :copy
set :use_sudo, :false
set :ssh_options, { :forward_agent => true, :keys =>
%w(/home/stanislaw/.ssh/id_rsa) }
set :unicorn_conf, "#{deploy_to}/current/config/unicorn.rb"
set :unicorn_pid, "#{deploy_to}/shared/pids/unicorn.pid"
role :web, hostpro_ip # Your HTTP server, Apache/etc
role :app, hostpro_ip # This may be the same as your `Web` server
role :db, hostpro_ip, :primary => true # This is where Rails migrations will run
# role :db, "your slave db-server here"
before "deploy:assets:precompile" do
run "cd #{latest_release}; bundle install --without=development test"
end
after "deploy:assets:precompile" do
# run "cd #{latest_release}; bundle install --without=development test"
end
after 'deploy:update_code' do
# run "cd #{latest_release}; RAILS_ENV=production rake assets:precompile"
end
namespace :deploy do
task :restart do
run "kill -USR2 `cat #{unicorn_pid}`"
end
task :start do
run "cd #{deploy_to}/current; bundle exec unicorn_rails -c
#{unicorn_conf} -E #{rails_env} -D"
end
task :stop do
run "kill -QUIT `cat #{unicorn_pid}`"
end
end</pre>
</div>
Скрипт для авто-запуска /etc/init.d/unicorn (не забудьте сделать его исполняемым)
<div>
<pre>#!/bin/bash
#chkconfig: 345 20 80
#description: Multiple unicorns startup script
# init.d script for single or multiple unicorn installations. Expects
at least one .conf
# file in /etc/unicorn
#
# (2012.06.18) Modified by [email protected]
http://github.com/stanislaw, as taken from http://jay.gooby.org/post/an-etcinitdunicorn-script-for-multiple-unicorn-installations:
# * RVM-based environments (.rvmrc should go in RAILS_ROOT if it differs from default gemset)
# * Replaced 'exit 0' with 'return'. It was breaking entire script after executing command (fx. stop) for the first file in /etc/unicorn/
# Modified by [email protected] http://github.com/jaygooby
# based on http://gist.github.com/308216 by http://github.com/mguterl
#
## A sample /etc/unicorn/my_app.conf
##
## RAILS_ENV=production
## RAILS_ROOT=/home/stanislaw/apps/my_app/current
## UNICORN_PID=/home/stanislaw/apps/my_app/shared/pids/unicorn.pid
# This configures a unicorn master for your app at /home/stanislaw/apps/my_app/current running in production mode. It will read config/unicorn.rb for further set up.
#
# You should ensure different ports or sockets are set in each config/unicorn.rb if you are running more than one master concurrently.
#
# If you call this script without any config parameters, it will attempt to run the
# init command for all your unicorn configurations listed in /etc/unicorn/*.conf
#
# /etc/init.d/unicorn start # starts all unicorns
#
# If you specify a particular config, it will only operate on that one:
#
# /etc/init.d/unicorn start /etc/unicorn/my_app.conf
set -e
sig () {
test -s "$PID" && kill -$1 `cat "$PID"`
}
oldsig () {
test -s "$OLD_PID" && kill -$1 `cat "$OLD_PID"`
}
cmd () {
case $1 in
start)
sig 0 && echo >&2 "Already running" && return;
echo "Starting";
eval $CMD;
;;
stop)
sig QUIT && echo "Stopping" && return;
echo >&2 "Not running";
;;
force-stop)
sig TERM && echo "Forcing a stop" && return;
echo >&2 "Not running"
;;
restart|reload)
sig USR2 && sleep 5 && oldsig QUIT && echo "Killing old master"
`cat $OLD_PID` && return;
echo >&2 "Couldn't reload, starting '$CMD' instead"
eval $CMD
;;
upgrade)
sig USR2 && echo Upgraded && return;
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
eval $CMD
;;
rotate)
sig USR1 && echo rotated logs OK && return;
echo >&2 "Couldn't rotate logs" && exit 1
;;
*)
echo >&2 "Usage: $0 <start|stop|restart|upgrade|rotate|force-stop>"
exit 1
;;
esac
}
setup () {
echo -n "$RAILS_ROOT: "
cd $RAILS_ROOT || exit 1
export PID=$UNICORN_PID
export OLD_PID="$PID.oldbin"
# We need to source .bash_profile to enable RVM!
UNICORN_CMD="source ~/.bash_profile; unicorn_rails -c
config/unicorn.rb -E $RAILS_ENV -D"
CMD="su -s /bin/bash stanislaw -c \"$UNICORN_CMD\""
}
start_stop () {
# either run the start/stop/reload/etc command for every config under /etc/unicorn or just do it for a specific one
# $1 contains the start/stop/etc command
# $2 if it exists, should be the specific config we want to act on
if [ $2 ]; then
. $2
setup
cmd $1
else
for CONFIG in /etc/unicorn/*.conf; do
# import the variables
. $CONFIG
setup
# run the start/stop/etc command
cmd $1
done
fi
}
ARGS="$1 $2"
start_stop $ARGS</pre>
</div>
Этот скрипт предполагает, что для каждого приложения в папке /etc/unicorn/ создаётся отдельный файл, например:
/etc/unicorn/first_app.conf
<div>
<pre>#!/bin/sh
RAILS_ENV=production
UNICORN_PID=/home/stanislaw/apps/first_app/shared/pids/unicorn.pid
RAILS_ROOT=/home/stanislaw/apps/first_app/current</pre>
</div>
Чтобы при перезагрузке VPS сервера все Unicorn’ы запускались, нужно обязательно изменить порядок запуска startup-скриптов в /etc/rc3.d (сервер работает на runlevel 3): unicorn должен идти после mysql, т.е. файл SЦИФРАunicorn в директории /etc/rc3.d/ должен иметь цифру большую, чем файл SЦИФРАmysqld
Разные версии Ruby
По умолчанию каждое приложение из /home/stanislaw/apps/ будет использовать версию Ruby 1.9.3, которая была установлена в начале. Предположим, что для приложения second_app нам нужна версия 1.8.7. Устанавливаем её дополнительно:
<div>
<pre>rvm install 1.8.7</pre>
</div>
и создаём в корне приложения second_app файл .rvmrc:
<div>
<pre># http://stackoverflow.com/a/5143967/598057
rvm use 1.8.7</pre>
</div>
MySQL
В целях безопасности рекомендуется отключить сетевой порт 3306, на который по умолчанию настроен MySQL. В файл/etc/my.cnf добавляем:
<div>
<pre>skip-networking</pre>
</div>
И в обоих приложениях исправляем в config/database.yml секцию production:
<div>
<pre>production:
adapter: mysql2
database: albumer_production
username: stanislaw
password: stanislaw-cool-pass
encoding: utf8
socket: /var/lib/mysql/mysql.sock</pre>
</div>
Разное
Если при работе с сервером наблюдаются такие строки:
<div>
<pre>tput: unknown terminal "rxvt-unicode"</pre>
</div>
подойдёт такое решение:
<div>
<pre>Solution 1:
The terminfo file /usr/share/terminfo/r/rxvt-unicode is missing from
the remote box. scp it from your laptop.</pre>
</div>
Обратная связь
За написание этой статьи мы выносим благодарность Станиславу, который подробно описал и оформил данную статью для нашего блога. Вы также имеете возможность опубликовать свой материал у нас на сайте. А за интересные статьи мы будем предоставлять нашим пользователям подарки.
Возможно, вас заинтересует
Друзья! В условиях войны у нас возникли дополнительные расходы, связанные с резервированием данных и...
Поскольку ISPmanager – русская панель управления, мы обязаны прекратить сотрудничество с ней. Все действующие...
Обычный хостинг вынуждает вас делиться. Делиться диском, делиться памятью, делиться ресурсами сервера. И всё...
Наш телеграм
с важными анонсами, розыгрышами и мемами
Присоединиться