Кросспост моей записи на хабре

Capistrano

Представьте: Вы — веб-разработчик, который только недавно освоил 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’:

1
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

nginx

На этот раз логинемся под нашим новым пользователем коммандой ssh deployer@123.123.123.123 (на локальном компьютере).

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

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

Теперь собираем:

1
2
3
4
5
6
7
8
9
10
11
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 checkinstall

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

etc/init/nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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’, но пока мы его трогать не будем. Мы его зальем автоматически при первом деплое приложения.

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

1
2
3
4
5
6
7
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

PostgreSQL

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

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

1
2
3
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

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

1
2
3
4
5
6
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’ строчку

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

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

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

Redis

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

1
2
3
4
5
6
7
8
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
1
2
3
4
5
6
7
8
9
10
#!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

Ruby on Rails

Тут совсем ничего сложного:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 на удаленном сервере для деплоя нашего приложения. Можно и настроить git сервер на нашем VPS, но зачем, если есть удобные бесплатные решения. Итак, создаем репозиторий на GitHub/BitBucket (у BitBucket приватные репозитории бесплатны), но не спешим заливать туда наш проект, сначала отредактируем .gitignore файл (он находится в корне приложения), чтобы в репо не попала никакая конфидициальная информация (особенно это важно, если репо публичный), да и заодно лишние файлы нам там не нужны:

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

Теперь можно сделать первый коммит и запушить проект в git.

1
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 в корне приложения, и наполним его для локального использования. У меня он выглядит так.

1
2
3
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

Unicorn — продвинутый HTTP сервер. Установим его, добавив group :production do gem 'unicorn' end в Gemfile. (Не забудьте про bundle install)

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

unicorn.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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

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

Gemfile
1
2
3
4
5
6
group :development do
  gem 'capistrano'
  gem 'capistrano-rails'
  gem 'capistrano-bundler'
  gem 'capistrano-rvm'
end

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

Capfile
1
2
3
4
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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 с таким содержанием:

nginx.conf
1
2
3
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 на сервере и добавляем строчку:

nginx.conf
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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’ пользователя спросят комментарий к коммиту и будет выполнено:

deploy.rb
1
2
3
git add -A
git commit -m 'КОММЕНТАРИЙ'
git push

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

deploy.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
 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 и не было проблем даже под нагрузкой.

Комментарии