Папа, настрой Nginx

Есть задача, даже две. Первая - разобраться с настройкой nginx для отдачи rails-статики и балансировки двух (трёх, сколько нужно), mongrel-ов. Вторая - всё то же самое, но без mongrel, а с использованием passenger и, главное, через capistrano.

Nginx + mongrels

Первую задачу я реализовывал локально, на своей уютненькой убунте и всё решилось сравнительно мирно.

Убрал из дефолтной конфигурации все директивы server и upstream, оставил только директивы include - так гибче, если что.

1
2
3
4
5
6
##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

Добавил в отдельный файл конфигурацию апстрима на двух монгрелах (важен был сам принцип, а не количество) и сервер:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
upstream mongrel { # <-- имя upstream-а для ссылок на него
  server localhost:3000; # Первый mongrel
  server localhost:3001; # Второй mongrel
  # <-- добавить mongrel-ы по вкусу
}

server {
  listen           8084; # <-- номер порта
  server_name      ~^(?<subdomain>[-\w]+)\.lvh\.me$; # <-- Запоминаем subdomain
  access_log       off;

  location / {
    proxy_pass       http://mongrel;
    proxy_set_header Host $subdomain.lvh.me:8084; # <-- Передаём subdomain и port
  }

  location ~ ^/(image|stylesheet|javascript)s {
    root             /home/user_name/devel/rails/project_name/public;
  }
}

Отдельный пункт в первой задаче был - сохранить для использования в рельсовских контроллерах субдомен, пришедший в запросе. Что и было реализовано при помощи server_name с регулярным выражением и proxy_set_header с использованием пришедшего из регэкспа поля $subdomain. Без передачи параметра Host в запросе к приложению приходил параметр “mongrel” с никаким субдоменом, а без передачи порта возникали проблемы с открытием следующих страниц (редиректы, ссылки и т.п.)

Теперь заходим на organization_name.lvh.me:8084 и в приложение приходят запросы с указанным субдоменом и разбрасываются между монгрелами. Как-то так.

Capistrano, а потом passenger

На самом деле, мне просто хотелось лёгкого деплоя. И поэтому я решил что на другую машину я буду деплоить при помощи capistrano. А passenger всплыл уже потом.

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

1
2
3
$ cd /home/user_name/devel/rails/project_name
$ capify .
$ emacs -nw config/deploy.rb # <-- Вместо emacs используйте Ваш любимый редактор ;)

Полученный Capfile оставляем в покое, а вот с config/deploy.rb пришлось потанцевать, по целому ряду причин. Во-первых, мне категорически не хотелось использовать sudo при деплое, во-вторых, мне не хотелось вводить пароли при каждом деплое, в третьих, на удалённой машинке rvm был установлен из-под root-а.

Итак, берём deploy.rb и внимательно на него смотрим.

config/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
### Где всё будет происходить
#
role :web, "195.151.207.37"                   # Машинка, куда всё будет деплоиться
role :app, "195.151.207.37"                   # Как правило, то же самое, что :web
role :db,  "195.151.207.37", :primary => true # А здесь будет лежать база данных


### Что ставим, куда ставим
#     1. Имя приложения
set :application,     "your_application_name"
#     2. Куда ставим
set :deploy_to,       "/home/user_name/deploy_path"
#     3. Откуда берём исходники
#        В нашем случае исходники лежали на bitbucket, но никто не запрещает держать
#        их на github или другом хостинге для исходников.
#
set :repository,      "ssh://hg@bitbucket.org/account_name/repository_name"
set :scm,             :mercurial
#     4. Кем ставим
#        Собой, любимым
set :user,        "user_name"
set :use_sudo,    false
#     5. Форвардим ssh-agent, чтобы удалённая машина знала, что это мы.
#        Разумеется, на удалённой машине должен лежать Ваш public key,
#        и на bitbucket - тоже
set :ssh_options, {:forward_agent => true}


### Bundler
#     1. bundle install автоматически
require 'bundler/capistrano'
#     2. bundle install в домашний каталог (чтобы не запускать рутом)
set :bundle_flags, "--deployment --quiet --path=/home/user_name/bundle"
#     3. bundle install, но с локальными настройками из $shared_path
before "bundle:install",     "bundler:config_symlink"
after  "deploy:setup",       "bundler:config_setup"

namespace :bundler do
  # Создаём каталог, куда будут сохраняться локальные настройки bundler-а
  #
  task :config_setup do
    run "mkdir -p #{shared_path}/bundler_config"
  end

  # Создаём линк на каталог, чтобы bundler не создавал ./.bundler по-новой
  #
  task :config_symlink do
    run "cd #{release_path} && ln -s #{shared_path}/bundler_config .bundle"
  end
end


### Копируем данные, лежащие в shared/config в свой config
#    config/database.yml, например
#
#    Не забудьте предварительно создать сам каталог и всё, что в нём должно лежать!
#
after  "deploy:update_code", "deploy:copy_config"

namespace :deploy do
  ### А этот код достаточно просто раскомментировать
  task :start do ; end
  task :stop do ; end
  task :restart, :roles => :app, :except => { :no_release => true } do
    run "touch #{File.join(current_path,'tmp','restart.txt')}"
  end

  task :copy_config do
    run "cp #{shared_path}/config/* #{release_path}/config/"
  end
end

Примерно так стал выглядеть deploy.rb на моей машинке (если комментарии не считать). В процессе мне пришлось разбираться, как настроить ssh-forwarding, как запускать на удалённой машине ssh-agent и как запускать passenger в standalone режиме и что man-ы и официальная документация are the developer’s best friends.

И всё было почти хорошо. И деплой проходил, не заканчиваясь сообщением, что какая-то команда не прошла, но вот рестарт passenger-а не происходил. Вот если бы passenger-а можно было бы запускать с командной строки, указывая, в каком каталоге будет производиться запуск, тогда бы, возможно, следующую часть можно было бы и опустить. А так мне пришлось устанавливать passenger в конкретном gemset-е, в нём же запускать установку nginx-модуля для passenger и настраивать nginx уже на работу с /home/user_name/deploy_path/public.

1
2
3
4
$ sudo rvm 1.9.2@project gem install passenger
$ sudo rvm 1.9.2@project passenger-install-nginx-module
$ sudo rvm 1.9.2@project passenger-config --root # <-- Отсюда возьмём путь к passenger-у
$ sudo vi /opt/nginx/conf/nginx.conf             # <-- Да, иногда я пользуюсь vi. Особенно на удалённых машинах ;)

Дальше - правки, внесенные в /opt/nginx/conf/nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http {
    # Сюда пишем то, что выдала команда passenger-config --root
    passenger_root /usr/local/rvm/gems/ruby-1.9.2-p290@crm/gems/passenger-3.0.9;
    # Эту строчку выставляет passenger-install-nginx-module (а откуда ещё я мог её взять?)
    passenger_ruby /usr/local/rvm/wrappers/ruby-1.9.2-p290@crm/ruby;
...
    # А вот этот кусочек я написал, глядя в руководство
    server {
      listen 80;
      server_name _;
      root /home/user_name/deploy_path/current/public;
      passenger_enabled on;
    }
...
}

Перезапускаем nginx и идём пить кефир.

Список используемой литературы

Comments