Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Deploy приложения на RoR 4 с поддержкой Capistrano 3

Anna | 20.06.2014 | нет комментариев

Представьте: Вы — веб-разработчик, тот, что только незадолго освоил Ruby on Rails. И здесь Ваш 1-й план подходит к стадии, когда его необходимо выложить в интернет.
Вы, безусловно, можете залить его на Heroku, но тамошние цены немножко кусаются. Остается только приобрести VPS, настроить его и выложить план туда.
«Что может быть проще? Обнаружу какой-нибудь гайд, да следаю всё по нему» — подумаете Вы. Вот только гайдов, которые не легко выкладывают команды, но и поясняющие, что эти команды делают, — единицы, да и те применяют теснее устаревшую вторую версию Capistrano.

Следственно я решил написать свой гайд, в котором постараюсь детально разглядеть:

  • Первичную настройку сервера
  • Установку и настройку nginx (с модулем PageSpeed), postgresql, redis
  • Установку rvm, rails
  • Настройку гема foreman для управления процессами Вашего приложения
  • Настройку сервера Unicorn
  • Настройку гема Capistrano (v3.1) для автоматизации деплоя

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

Первичная настройка сервера

Вы приобрели свой 1-й VPS, установили ОС (я использую ubuntu 12.04 LTS и все команды буду выкладывать именно под неё), зашли по SSH. Что делать дальше?

Первым делом сменим пароль для пользователя root коммандой

passwd 

Сделаем нового пользователя:

adduser deployer

Позволим ему пользоваться коммандой sudo:

visudo 

и дописываем:

deployer ALL=(ALL:ALL) ALL

Изменим настройки ssh сервера (запретим логин под root, доступ по доменному имени и позволим логин только под нашим новым пользователем). Добавляем в файл ‘/etc/ssh/sshd_config’:

PermitRootLogin no
UseDNS no
AllowUsers deployer

Перезапустим ssh сервер коммандой:

reload ssh

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

ssh-copy-id deployer@123.123.123.123

(На маке требуется установка ssh-copy-id, сделать дозволено через brew, на Windows не знаю автоматизированного средства для копирования ключей, но в интернете есть много увлекательного на эту тему).

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

dd if=/dev/zero of=/swapfile bs=1024 count=512k
mkswap /swapfile
swapon /swapfile

Дальше в файле ‘/etc/fstab’ добавляем строчку:

 /swapfile       none    swap    sw      0       0 

И дальше исполняем:

echo 0 > /proc/sys/vm/swappiness
sudo chown root:root /swapfile 
sudo chmod 0600 /swapfile

Дозволено перезагрузиться и проверить присутствие SWAP файла коммандой

swapon -s 

Установка и настройка nginx

На данный раз логинемся под нашим новым пользователем коммандой

ssh deployer@123.123.123.123

(на локальном компьютере).
Лично я использую модуль PageSpeed, следственно nginx собираю сам. Но вначале нам нужно обновить репозитории, обновить систему, и скачать нужные для удачной сборки пакеты:

sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get install build-essential zlib1g-dev libpcre3 libpcre3-dev unzip

Сейчас собираем:

wget https://github.com/pagespeed/ngx_pagespeed/archive/v1.7.30.1-beta.zip
unzip v1.7.30.1-beta.zip
cd ngx_pagespeed-1.7.30.1-beta
wget https://dl.google.com/dl/page-speed/psol/1.7.30.1.tar.gz
tar -xzvf 1.7.30.1.tar.gz
wget http://nginx.org/download/nginx-1.4.4.tar.gz
tar -xzvf nginx-1.4.4.tar.gz
cd nginx-1.4.4
./configure --add-module=$HOME/ngx_pagespeed-1.7.30.1-beta
make
sudo make install

Для управления nginx напишем upstart скрипт. Создаём файл ‘/etc/init/nginx.conf’ со дальнейшим содержимым:

etc/init/nginx.conf

description "nginx http daemon"
author "George Shammas <georgyo@gmail.com>"

start on (filesystem and net-device-up IFACE=lo)
stop on runlevel [!2345]

env DAEMON=/usr/local/nginx/sbin/nginx
env PID=/var/run/nginx.pid

expect fork
respawn
respawn limit 10 5
#oom never

pre-start script
        $DAEMON -t
        if [ $? -ne 0 ]
                then exit $?
        fi
end script

exec $DAEMON

Сейчас вы можете исполнять

sudo start/stop/restart/status nginx

Наш nginx.conf лежит по адресу ‘/usr/local/nginx/conf/nginx.conf’, но пока мы его трогать не будем. Мы его зальем механически при первом деплое приложения.

Для наших веб приложений мы сделаем нового пользователя и новую группу, добавим себя в эту группу, и сделаем папку:

sudo useradd -s /sbin/nologin -r nginx
sudo groupadd web
sudo usermod -a -G web nginx
sudo usermod -a -G web deployer
sudo mkdir /var/www
sudo chgrp -R web /var/www
sudo chmod -R 775 /var/www

Для того, Дабы мы сумели писать в папку придется разлогиниться и зайти вновь под нашим пользователем.

Установка и настройка PostgreSQL

В репозиториях ubuntu лежит устаревшая версия, так что мы добавим сторонний репо. В файл ‘/etc/apt/sources.list.d/pgdg.list’ добавим:

deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main

После этого добавляем ключ репозитория и устанавливаем PostgreSQL:

wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update
sudo apt-get install postgresql-9.3 postgresql-server-dev-9.3

И создаем нового пользователя:

sudo -u postgres psql

create user deployer with password 'ваш пароль';
alter role deployer superuser createrole createdb replication;

\q

Дабы иметь доступ с локального компьютера в файле ‘/etc/postgresql/9.3/main/postgresql.conf’ изменим параметр listen_addresses = 'localhost' на listen_addresses = '*' и добавим в файл ‘/etc/postgresql/9.3/main/pg_hba.conf’ строчку

host    all             deployer       ваш.внешний.ip.адрес 255.255.255.0 md5

Перезагружаем postgresql коммандой

sudo service postgresql restart

Установка и настройка Redis

Если Вы используте gem resque, то Вам необходимо установить Redis. Потому как в репозитории устаревшая версия, я его собираю из исходников, к тому же это занимает немножко времени:

sudo apt-get install tcl8.5
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
make test
sudo cp src/redis-server /usr/local/bin
sudo cp src/redis-cli /usr/local/bin

Redis по умолчанию не защищен паролем и открыт каждому, следственно поставим пароль: в файле ‘redis.conf’ добавляем параметр requirepass с нашим паролем. Redis легко брутфорсится, следственно я делаю пароль не менее 100 символов. Также, Дабы потом не вылетало ошибок, меняем параметр dir на /var/www/other, заранее сделав такую папку (mkdir /var/www/other).
Копируем наш конфиг командой

sudo cp redis.conf /etc/redis/redis.conf 

Создаем upstart скрипт по адресу ‘/etc/init/redis-server.conf’ со дальнейшим оглавлением:

/etc/init/redis-server.conf

#!upstart
description "Redis Server"

env USER=deployer

start on runlevel [2345]
stop on runlevel [016]

respawn
exec start-stop-daemon --start --make-pidfile --pidfile /var/run/redis-server.pid --chuid $USER --exec /usr/local/bin/redis-server /etc/redis/redis.conf >> /var/www/log/redis.log 2>&1

Сейчас мы можем руководить Redisом коммандами

sudo start/stop/restart/status redis-server

, заранее сделав папку для логов (mkdir /var/www/log).

Установка RVM, Ruby, Rails, Bundler

Здесь вовсе ничего трудного:

sudo apt-get install git curl python-software-properties

sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs

curl -L get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
rvm requirements

rvm install 2.0.0
rvm use 2.0.0 --default
gem install rails --no-ri --no-rdoc
gem install bundler

Создаем репозиторий на GitHub/BitBucket

Мы будем применять git на удаленном сервере для деплоя нашего приложения. Дозволено и настроить git сервер на нашем VPS, но для чего, если есть комфортные бесплатные решения. Выходит, создаем репозиторий на GitHub/BitBucket (у BitBucket приватные репозитории бесплатны), но не торопимся заливать туда наш план, вначале отредактируем .gitignore файл (он находится в корне приложения), Дабы в репо не попала никакая конфидициальная информация (исключительно это значимо, если репо публичный), да и заодно лишние файлы нам там не необходимы:

/config/database.yml # доступ к базе данным
/Procfile # про него я еще расскажу
/config/deploy/ # файлы Capistrano
/shared/ # файлы, которых нет в репозитории, но они будут скопированы на сервер при первом деплое приложения
/public/system/ # если установлен Paperclip

Сейчас дозволено сделать 1-й коммит и запушить план в git.

git init
git remote add origin #АДРЕС РЕПО
git add -A
git commit -m 'first commit'
git push -u origin --all

Также нам нужно добавить ключ нашего сервера в админку Github/BitBucket, это непременное условие, т.к. из репозитория метаморфозы будут загружаться на сервер. Как это сделать дозволено узнать в Helpе обслуживания.

gem foreman

Доктор Форман из сериала 'Доктор Хаус'

foreman — гем для управления процессами приложения. На локальной машине он разрешает запускать сразу все процессы, указанные в Procfile, одной коммандой

foreman start

и показывает их итог.
На сервере командой

foreman export upstart

он создает upstart скрипт для легкого управления приложением с поддержкой команд start/stop/restart. Но об этом потом. Пока легко установим его, сотворим Procfile в корне приложения, и наполним его для локального применения. У меня он выглядит так.

web: rails s
job1: bundle exec rake resque:work PIDFILE=./tmp/pids/resque2.pid QUEUES=send_email
job2: bundle exec rake resque:work PIDFILE=./tmp/pids/resque2.pid QUEUES=send_email

Production конфигурацию мы напишем потом, когда речь зайдет о Capistrano.

Устанавливаем Unicorn

Unicorn — продвинутый HTTP сервер. Установим его, добавив

group :production do
  gem 'unicorn'
end

в Gemfile. (Не позабудьте про bundle install)

В папке ‘/config/’ создаем файл unicon.rb c приблизительно таким содержимым:

Unicorn.rb

worker_processes 2

working_directory "/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/current" # available in 0.94.0 

# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen "/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/socket/.unicorn.sock", :backlog => 64
listen 8080, :tcp_nopush => true

# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30

# feel free to point this anywhere accessible on the filesystem
pid "/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/run/unicorn.pid"

# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path "/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/log/unicorn.stderr.log"
stdout_path "/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/log/unicorn.stdout.log"

# 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

# Enable this flag to have unicorn test client connections by writing the
# beginning of the HTTP headers before calling the application.  This
# prevents calling the application for connections that have disconnected
# while queued.  This is only guaranteed to detect clients on the same
# host unicorn runs on, and unlikely to detect disconnects even on a
# fast LAN.
check_client_connection false

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!

  # The following is only recommended for memory/DB-constrained
  # installations.  It is not needed if your system can house
  # twice as many worker_processes as you have configured.
  #
  # # This allows a new master process to incrementally
  # # phase out the old master process with SIGTTOU to avoid a
  # # thundering herd (especially in the "preload_app false" case)
  # # when doing a transparent upgrade.  The last worker spawned
  # # will then kill off the old master process with a SIGQUIT.
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      sig = (worker.nr   1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
  #
  # Throttle the master from forking too quickly by sleeping.  Due
  # to the implementation of standard Unix signal handlers, this
  # helps (but does not completely) prevent identical, repeated signals
  # from being lost when the receiving process is busy.
  # sleep 1
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

Заменяем ИМЯ_ПРИЛОЖЕНИЯ на Ваше имя приложения, которое Вы потом зададите в настройках Capistrano.

Capistrano

Capistrano — крайне комфортное средство для деплоя приложения, пускай и вначале таковым не кажется. Установим его с необходимыми дополнениями, добавив в Gemfile:

group :development do
  gem 'capistrano'
  gem 'capistrano-rails'
  gem 'capistrano-bundler'
  gem 'capistrano-rvm'
end

Исполняем bundle exec cap install и добавляем в Capfile:

require 'capistrano/deploy'
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails'

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

  • Загрузит ваше приложение из репозитория на сервер в папку рабочая_папка/имя_приложения/releases/дата_релиза/, не удаляя ветхую версию (по умолчанию он хранит 5 последних версий приложений).
  • Исполнит bundle install.
  • Исполнит db:migrate.
  • Исполнит assets:precompile.
  • Сделает symlink из папки приложения в папку рабочая_папка/имя_приложения/current

Но нам этого немного. Нам необходимо реализовать следующее:

  • При первом деплое исполнить настройку nginx, unicorn, загрузить некоторые файлы, которые не будут менятrmark! } }

Это мой конфиг для nginx, там нужно заменить ‘ИМЯ_ПРИЛОЖЕНИЯ’ на Ваше имя приложения, указанное в первой строчке config/deploy.rb (set :application, ‘ИМЯ_ПРИЛОЖЕНИЯ’).

Сейчас создаем там же (в /shared/) файл Procfile с таким оглавлением:

web: bundle exec unicorn_rails -c /var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/current/config/unicorn.rb -E production 
job1: bundle exec rake resque:work RAILS_ENV=production PIDFILE=/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/run/resque1.pid QUEUES=*
job2: bundle exec rake resque:work RAILS_ENV=production PIDFILE=/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/run/resque2.pid QUEUES=*

Это конфиг для приложения с двумя resque workerами. Если Вы не используете Resque, то легко оставьте только первую строчку.
Там же создаём database.yml с настройками базы данных и application.yml, если пользуетесь гемом Figaro.

Нвш Capistrano скрипт будет исполнять некоторые команды от имени суперпользователя на сервере. Дабы позволить ему делать это, исполняем команду

sudo visudo

на сервере и добавляем строчку:

deployer ALL=NOPASSWD: /usr/sbin/service, /bin/ln, /bin/rm, /bin/mv, /sbin/start, /sbin/stop, /sbin/restart, /sbin/status

Осталось только настроить Capistrano. В файле ‘config/deploy/production’ делаем метаморфозы:
server 'IP сервера', user: 'deployer', roles: %w{web app db}
В файл ‘config/deploy.rb’ добавляем сверху:

deploy.rb

set :repo_url, 'Адрес репозитория'
set :application, 'ИМЯ_ПРИЛОЖЕНИЯ'
application = 'ИМЯ_ПРИЛОЖЕНИЯ'
set :rvm_type, :user
set :rvm_ruby_version, '2.0.0-p353'
set :deploy_to, '/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ'

namespace :foreman do
  desc 'Start server'
  task :start do
    on roles(:all) do
      sudo "start #{application}"
    end
  end

  desc 'Stop server'
  task :stop do
    on roles(:all) do
      sudo "stop #{application}"
    end
  end

  desc 'Restart server'
  task :restart do
    on roles(:all) do
      sudo "restart #{application}"
    end
  end

  desc 'Server status'
  task :status do
    on roles(:all) do
      execute "initctl list | grep #{application}"
    end
  end
end

namespace :git do
  desc 'Deploy'
  task :deploy do
    ask(:message, "Commit message?")
    run_locally do
      execute "git add -A"
      execute "git commit -m '#{fetch(:message)}'"
      execute "git push"
    end
  end
end

Что всё это значит? Первые строчки — конфиг. После этого мы описываем задачи. Есть задача foreman, у которой есть 4 действия: start, stop, restart status. При выполнении ‘cap production foreman:start’ на локальной машине на сервере будет исполнено ‘sudo start ИМЯ_ПРИЛОЖЕНИЯ’, но пока что это нам ничего не даст, потому как foreman еще не сотворил upstart скрипты. Идем дальше: есть задача git, у которой есть действие deploy. При выполнении ‘cap production git:deploy’ пользователя спросят комментарий к коммиту и будет исполнено:

git add -A
git commit -m 'КОММЕНТАРИЙ'
git push

Вовсе не трудно, правда? Но этими командами мы не будем пользоваться сами, они будут выполняться при выполнении других скриптов. Сейчас внутри ‘namespace :deploy do’ добавляем

deploy.rb

  desc 'Setup'
  task :setup do
    on roles(:all) do
      execute "mkdir  #{shared_path}/config/"
      execute "mkdir  /var/www/apps/#{application}/run/"
      execute "mkdir  /var/www/apps/#{application}/log/"
      execute "mkdir  /var/www/apps/#{application}/socket/"
      execute "mkdir #{shared_path}/system"
      sudo "ln -s /var/log/upstart /var/www/log/upstart"

      upload!('shared/database.yml', "#{shared_path}/config/database.yml")

      upload!('shared/Procfile', "#{shared_path}/Procfile")

      upload!('shared/nginx.conf', "#{shared_path}/nginx.conf")
      sudo 'stop nginx'
      sudo "rm -f /usr/local/nginx/conf/nginx.conf"
      sudo "ln -s #{shared_path}/nginx.conf /usr/local/nginx/conf/nginx.conf"
      sudo 'start nginx'

      within release_path do
        with rails_env: fetch(:rails_env) do
          execute :rake, "db:create"
        end
      end

    end
  end

  desc 'Create symlink'
  task :symlink do
    on roles(:all) do
      execute "ln -s #{shared_path}/config/database.yml #{release_path}/config/database.yml"
      execute "ln -s #{shared_path}/Procfile #{release_path}/Procfile"
execute "ln -s #{shared_path}/system #{release_path}/public/system"
    end
  end

  desc 'Foreman init'
  task :foreman_init do
    on roles(:all) do
      foreman_temp = "/var/www/tmp/foreman"
      execute  "mkdir -p #{foreman_temp}"
      # Создаем папку current для того, Дабы foreman создавал upstart файлы с положительными путями
      execute "ln -s #{release_path} #{current_path}"

      within current_path do
        execute "cd #{current_path}"
        execute :bundle, "exec foreman export upstart #{foreman_temp} -a #{application} -u deployer -l /var/www/apps/#{application}/log -d #{current_path}"
      end
      sudo "mv #{foreman_temp}/* /etc/init/"
      sudo "rm -r #{foreman_temp}"
    end
  end

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      sudo "restart #{application}"
    end
  end

  after :finishing, 'deploy:cleanup'
  after :finishing, 'deploy:restart'

  after :updating, 'deploy:symlink'

  after :setup, 'deploy:foreman_init'

  after :foreman_init, 'foreman:start'

  before :foreman_init, 'rvm:hook'

  before :setup, 'deploy:starting'
  before :setup, 'deploy:updating'
  before :setup, 'bundler:install'

Есть задача deploy и мы добавили 4 новых действия: setup (первичная настройка), foreman_init (создание upstart скрипта для приложения), symlink (создание символьных ссылок) и restart (перезагрузка приложения). Также мы указываем после/перед какими стадиями, что необходимо исполнить.

deploy:setup исполняет первичную настройку сервера: загружает файлы из папки shared на локальном компьютере в папку shared на сервере, настраивает nginx, создает надобные папки и запускает deploy:foreman_init, тот, что в свою очередь создает upstart скрипты через foreman и копирует их в /etc/init, позже чего мы можем руководить нашим приложением командами sudo start/stop/restart/status ИМЯ_ПРИЛОЖЕНИЯ. Перед deploy:setup выполняется три первых шага обыкновенного деплоя приложения, а именно заливаются файлы на сервер и выполняется bundle install. Позже всякого деплоя создаются новейший симлинки и перезагружается Unicorn. Осталось только в конец этого файла добавить before :deploy, 'git:deploy' и сейчас перед всяким деплоем механически будут коммититься новые метаморфозы.

Еще раз:

  • исполняем cap production deploy:setup при самом первом деплое Вашего приложения.
  • исполняем cap production deploy при всяком деплое Вашего приложения.

Вот именно так я неизменно деплою свои приложения на VPS. Безусловно, данный метод не есть правда в последней инстанции, но я постарался на примере объяснить как работает Capistrano, Дабы даже у новичка не было задач с изменением скрипта под свои нужды. Так же я не утверждаю, что мои nginx.conf и unicorn.rb безукоризненны, но у меня с ними теснее примерно год все работает на несильно сильных VPS и не было задач даже под нагрузкой.

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB