Deploy приложения на RoR 4 с помощью Capistrano 3

от автора

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

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

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

Теперь можно сделать первый коммит и запушить проект в 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, загрузить некоторые файлы, которые не будут меняться, создать upstart скрипт (с помощью foreman).
  • Перед каждым деплоем автоматически выполнять git add, git commit, git push (сообщение к коммиту спрашивать у пользователя). После каждого деплоя создавать симлинки и перезапускать Unicorn.

Нужные только в первый раз файлы будем хранить в папке shared (в папке проекта на локальной машине), не зря мы ее добавили в .gitignore. Сначала создадим там nginx.conf примерно с таким содержимым:

nginx.conf

user nginx web;   pid /var/run/nginx.pid; error_log /var/www/log/nginx.error.log;  events {   worker_connections 1024; # increase if you have lots of clients   accept_mutex off; # "on" if nginx worker_processes > 1   use epoll; # enable for Linux 2.6+   # use kqueue; # enable for FreeBSD, OSX }  http {   # nginx will find this file in the config directory set at nginx build time   include mime.types;   types_hash_max_size 2048;   server_names_hash_bucket_size 64;   # fallback in case we can't determine a type   default_type application/octet-stream;    # click tracking!   access_log /var/www/log/nginx.access.log combined;    # you generally want to serve static files with nginx since neither   # Unicorn nor Rainbows! is optimized for it at the moment   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    # we haven't checked to see if Rack::Deflate on the app server is   # faster or not than doing compression via nginx.  It's easier   # to configure it all in one place here for static files and also   # to disable gzip for clients who don't get gzip/deflate right.   # There are other gzip settings that may be needed used to deal with   # bad clients out there, see http://wiki.nginx.org/NginxHttpGzipModule   gzip on;   gzip_http_version 1.0;   gzip_proxied any;   gzip_min_length 0;   gzip_vary on;   gzip_disable "MSIE [1-6]\.";   gzip_proxied expired no-cache no-store private auth;   gzip_comp_level 9;   gzip_types text/plain text/xml text/css              text/comma-separated-values              text/javascript application/x-javascript              application/atom+xml;    # this can be any application server, not just Unicorn/Rainbows!   upstream app_server {     server unix:/var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/socket/.unicorn.sock fail_timeout=0;   }    server {     # PageSpeed     pagespeed on;     pagespeed FileCachePath /var/ngx_pagespeed_cache;     location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" {        add_header "" "";      }     location ~ "^/ngx_pagespeed_static/" { }     location ~ "^/ngx_pagespeed_beacon$" { }     location /ngx_pagespeed_statistics {        allow 127.0.0.1; allow 5.228.169.73; deny all;      }     location /ngx_pagespeed_global_statistics {        allow 127.0.0.1; allow 5.228.169.73; deny all;      }     pagespeed MessageBufferSize 100000;     location /ngx_pagespeed_message {        allow 127.0.0.1; allow 5.228.169.73; deny all;      }     location /pagespeed_console {        allow 127.0.0.1; allow 5.228.169.73; deny all;      }           charset utf-8;     # enable one of the following if you're on Linux or FreeBSD     listen 80 default deferred; # for Linux     # listen 80 default accept_filter=httpready; # for FreeBSD      # If you have IPv6, you'll likely want to have two separate listeners.     # One on IPv4 only (the default), and another on IPv6 only instead     # of a single dual-stack listener.  A dual-stack listener will make     # for ugly IPv4 addresses in $remote_addr (e.g ":ffff:10.0.0.1"     # instead of just "10.0.0.1") and potentially trigger bugs in     # some software.     # listen [::]:80 ipv6only=on; # deferred or accept_filter recommended      client_max_body_size 4G;     server_name _;      # ~2 seconds is often enough for most folks to parse HTML/CSS and     # retrieve needed images/icons/frames, connections are cheap in     # nginx so increasing this is generally safe...     keepalive_timeout 5;      # path for static files     root /var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/current/public;      # Prefer to serve static files directly from nginx to avoid unnecessary     # data copies from the application server.     #     # try_files directive appeared in in nginx 0.7.27 and has stabilized     # over time.  Older versions of nginx (e.g. 0.6.x) requires     # "if (!-f $request_filename)" which was less efficient:     # http://bogomips.org/unicorn.git/tree/examples/nginx.conf?id=v3.3.1#n127     try_files $uri/index.html $uri.html $uri @app;      location ~ ^/(assets)/  {       root /var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/current/public;              expires max;       add_header Cache-Control public;     }     location @app {       # an HTTP header important enough to have its own Wikipedia entry:       #   http://en.wikipedia.org/wiki/X-Forwarded-For       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        # enable this if you forward HTTPS traffic to unicorn,       # this helps Rack set the proper URL scheme for doing redirects:       # proxy_set_header X-Forwarded-Proto $scheme;        # pass the Host: header from the client right along so redirects       # can be set properly within the Rack application       proxy_set_header Host $http_host;        # we don't want nginx trying to do something clever with       # redirects, we set the Host: header above already.       proxy_redirect off;        # set "proxy_buffering off" *only* for Rainbows! when doing       # Comet/long-poll/streaming.  It's also safe to set if you're using       # only serving fast clients with Unicorn + nginx, but not slow       # clients.  You normally want nginx to buffer responses to slow       # clients, even with Rails 3.1 streaming because otherwise a slow       # client can become a bottleneck of Unicorn.       #       # The Rack application may also set "X-Accel-Buffering (yes|no)"       # in the response headers do disable/enable buffering on a       # per-response basis.       # proxy_buffering off;        proxy_pass http://app_server;     }      # Rails error pages     error_page 500 502 503 504 /500.html;     location = /500.html {       root /var/www/apps/ИМЯ_ПРИЛОЖЕНИЯ/current/public;     }   } } 

Это мой конфиг для 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 и не было проблем даже под нагрузкой.

ссылка на оригинал статьи http://habrahabr.ru/post/213269/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *