Ruby on Rails - Apache - Mongrel

Średnia ocena: 4 (1 głos)

Zrobiłeś właśnie swoją super-hiper-wybajerzoną aplikację w Ruby on Rails i chcesz ją udostępnić szerokiemu światu. Problemem jest wąskie gardło w postaci serwera Webrick będącego częścią Railsów. Można temu zaradzić trzyetapowo:

  1. Należy zastąpić Webrick czymś wydajniejszym.
  2. Należy zdjąć obowiązek serwowania plików statycznych (np statycznych obrazków, styli) z serwera Railsowego.
  3. Należy rozłożyć obciążenie na kilka serwerów Railsowych

W dalszym tekście zakładam, że masz zainstalowane i działające Ruby on Rails i apache2.


[edytuj] Serwer mongrel

Na początek trzeba zastąpić Webricka silniejszym narzędziem. Polecam serwer Mongrel ze względu na fakt, że jest częściowo w kodzie natywnym (moduły w C), przez co jest całkiem wydajny.

Instalujemy go tak (Gem mongrel_cluster pozwala zarządzać kilkoma serwerami naraz):

~ # gem install mongrel mongrel_cluster -y
Need to update 14 gems from http://gems.rubyforge.org
..............
complete
Select which gem to install for your platform (x86_64-linux)
1. mongrel 1.0.1 (mswin32)
2. mongrel 1.0.1 (ruby)
3. mongrel 1.0 (mswin32)
4. mongrel 1.0 (ruby)
....
> 2 [wybierasz najwyższą wersję "(ruby)"]
Building native extensions.  This could take a while...
ruby extconf.rb install mongrel mongrel_cluster rails -y
checking for main() in -lc... yes
creating Makefile

...

Successfully installed mongrel-1.0.1
Installing ri documentation for mongrel-1.0.1...
Installing RDoc documentation for mongrel-1.0.1...
Successfully installed mongrel_cluster-1.0.2

~ # mkdir /etc/mongrel_cluster

Czasami mongrel chce doinstalować jeszcze kilka gemów, wybieraj zawsze wersję najwyższą typu "ruby".

Teraz należy przygotować odpowiedniego użytkownika i uprawnienia dla niego:

~ # addgroup --system rails
Dodawanie grupy `rails' (GID 129)...
Gotowe.
~ # adduser --system --home /var/www/rails --shell /bin/bash \
  --disabled-password --ingroup rails rails 
Dodawanie użytkownika systemowego `rails' (UID 120)...
Dodawanie nowego użytkownika `rails' (UID 120) w grupie `rails'...
Tworzenie katalogu domowego `/var/www/rails'...
~ # id rails
uid=120(rails) gid=129(rails) grupy=129(rails)

Mamy teraz użytkownika rails w grupie rails, na którego nie można się zalogować bezpośrednio (względy bezpieczeństwa), ale z konta administratora można przelogować się tak:

~ # su - rails
~ $ whoami
rails
~ $ pwd
/var/www/rails

Możemy teraz wygenerować (ciągle jako użytkownik rails) prosty szkielet testowy:

~ $ mkdir static
~ $ rails test
     create
     create  app/controllers
     create  app/helpers
     create  app/models
     create  app/views/layouts
...
     create  log/development.log
     create  log/test.log
~ $ ls
static test

Teraz uruchamiamy to pod serwerem (zwróć uwagę, że uruchamia się już pod kontrolą mongrela! Magia rubiego działa!):

~ $ cd test
~/test $ script/server
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
** Rails signals registered.  HUP => reload (without restart).  It might not work well.
** Mongrel available at 0.0.0.0:3000
** Use CTRL-C to stop.

W tej chwili możesz sprawdzić działanie aplikacji wysyłając swoją przeglądarkę WWW na adres http://localhost:3000 (czy inny wskazany na zgłoszeniu mongrela - pamiętając, żeby zamienić adres 0.0.0.0 na localhost). Po sprawdzeniu (powinna pokazać się strona powitalna serwera railsowego) naciśnij Ctrl-C aby wyłączyć serwer.


[edytuj] Loadalancing


[edytuj] Klaster mongrela

Problemem mongrela jest to, że nie jest wielowątkowy i nie przyjmuje nowego zapytania przed ukończeniem poprzedniego. Przy małych obciążeniach (kilka zapytań na sekundę) jest to akceptowalne, natomiast przy dużych już nie. Rozwiązaniem jest klaster kilku serwerów (nawet fizycznie na tym samym komputerze) dzielących się zadaniami.

Na początek trzeba przygotować serwery mongrela (polecenia należy wykonywać dalej z poziomu użytkownika rails):

~/test $ mongrel_rails cluster::configure -e production \
 -p 4000 -a 127.0.0.1 -N 3 -c `pwd` --user rails --group rails 
Writing configuration file to config/mongrel_cluster.yml.

Powyższy zapis oznacza tyle:

  • Klaster będzie chodził z ustawieniami 'production' (możesz na wstępie dać zamiast tego 'development')
  • Będzie słuchał na adresie 127.0.0.1 (localhost)
  • Będą uruchomione 3 serwery od portu 4000 w górę
  • Serwer będzie uruchomiony jako użytkownik 'rails' w grupie 'rails'.

Czysto i bezpiecznie

Teraz trzeba wrócić do trybu administratora i podlinkować nowy plik konfiguracyjny do katalogu konfiguracyjnego klastra:

~/test $ naciścnij CTRL-D
~ # ln -s /var/www/rails/test/config/mongrel_cluster.yml /etc/mongrel_cluster/test.yml
~ # ls /etc/mongrel_cluster
test.yml

Testujemy wszystko wydając następujące polecenia:

# mongrel_cluster_ctl start
Starting all mongrel_clusters...
# mongrel_cluster_ctl status
Checking all mongrel_clusters...
mongrel_rails cluster::status -C test.yml
found pid_file: tmp/pids/mongrel.4000.pid
found mongrel_rails: port 4000, pid 20708

found pid_file: tmp/pids/mongrel.4001.pid
found mongrel_rails: port 4001, pid 20711

found pid_file: tmp/pids/mongrel.4002.pid
found mongrel_rails: port 4002, pid 20714

Można sprawdzić poprawność łącząc się z którymkolwiek z serwerów: http://localhost:4000, http://localhost:4001 bądź http://localhost:4002

po sprawdzeniu możesz wyłączyć klaster komendą

# mongrel_cluster_ctl stop
Stopping all mongrel_clusters...
# mongrel_cluster_ctl status
Checking all mongrel_clusters...
mongrel_rails cluster::status -C test.yml
missing pid_file: tmp/pids/mongrel.4000.pid
missing mongrel_rails: port 4000

missing pid_file: tmp/pids/mongrel.4001.pid
missing mongrel_rails: port 4001 

missing pid_file: tmp/pids/mongrel.4002.pid
missing mongrel_rails: port 4002


[edytuj] Serwer apache

Teraz klaster trzeba pokazać jako jeden serwer za pomocą Apache2. Wszystkie potrzebne moduły masz już zainstalowane, przy okazji instalacji samego serwera. Trzeba je tylko włączyć:

# for mod in rewrite proxy proxy_http proxy_balancer; do a2enmod $mod ; done
Module rewrite installed; run /etc/init.d/apache2 force-reload to enable.
Module proxy installed; run /etc/init.d/apache2 force-reload to enable.
Module proxy_http installed; run /etc/init.d/apache2 force-reload to enable.
Module proxy_balancer installed; run /etc/init.d/apache2 force-reload to enable.

Teraz musisz napisać stosowny plik konfiguracyjny do apache'a w katalogu /etc/apache2/sites-available. Nazwij go rails:

# Adres IP, ilość i porty zgodne z naszym klastrem mongrela.
<Proxy balancer://mongrel_cluster>
 BalancerMember http://127.0.0.1:4000
 BalancerMember http://127.0.0.1:4001
 BalancerMember http://127.0.0.1:4002
</Proxy>

# Robimy wirtualny host
<VirtualHost *>
 # Będziemy się odwoływać do niego przez http://rails
 ServerName rails 

 # Ścieżka do naszej aplikacji railsowej, do jej podkatalogu public
 DocumentRoot /var/www/rails/test/public

 # Robimy osobne pliki logów
 ErrorLog /var/log/apache2/error_rails.log
 CustomLog /var/log/apache2/access_rails.log common
 
 # Dajemy odpowiednie ustawnienia - wyłączając między innymi
 # domyślny railsowy .htaccess, teraz już niepotrzebny
 <Directory "/var/www/rails/test/public">
  Options FollowSymLinks
  AllowOverride None
  Order allow,deny
  Allow from all
 </Directory>

 # Włączamy przepisywajkę adresów
 RewriteEngine On

 # Jak wyłączasz mongrela, daj info w pliku /system/maintenance.html.
 # Apache wyświetli tą stronę jako info zamiast dowolnego adresu z serwisu
 # (taki sprytny catch-all)
 RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
 RewriteCond %{SCRIPT_FILENAME} !maintenance.html
 RewriteRule ^.*$ /system/maintenance.html [L]

 # Zabawy z keszami
 RewriteRule ^/$ /index.html [QSA]
 RewriteRule ^([^.]+)$ $1.html [QSA]

 # No i najważniejsze - pliki które są FIZYCZNIE na dysku są serwowane
 # przez apacza z pominięciem Railsów. Railsy obsługują tylko wywołania
 # do ścieżek nie istniejących fizycznie na dysku
 RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
 RewriteRule ^/(.*)$ balancer://mongrel_cluster%{REQUEST_URI} [P,QSA,L]
</virtualHost>

Jeszcze tylko trzeba włączyć naszego site'a w apaczu i dodać wpis w /etc/hosts:

~ # a2ensite rails
Site rails installed; run /etc/init.d/apache2 reload to enable.
~ # echo 127.0.0.1 rails >> /etc/hosts


[edytuj] Test

Nadszedł czas uruchomienia wszystkiego razem:

~ # mongrel_cluster_ctl start
Starting all mongrel_clusters...
~ # /etc/init.d/apache2 restart
* Forcing reload of web server (apache2)...     [ OK ]

Teraz wklep w przeglądarce http://rails i powinien pojawić się ekran powitalny railsów serwowanych przez indianina z kilku kociołków naraz.

Pozdrawiam!