Y-Ken Studio

新しもの好きのデータエンジニアが四方山話をお届けします。

プロキシを使わずにRuby-1.9/2.1混在環境も作れる、Apache2+Passenger4+rbenvを用いた混在環境の作り方

Passenger 3.x で複数のRubyバージョンを使い分けるには、とてもトリッキーな対応が必要でした。
ところがPassenger 4.0.0 より公式に複数のRubyバージョンに対応しました。つまり、同一筐体のApacheで動く他アプリケーションへ影響を与えることなく、気軽にアプリ毎に使うRubyバージョンを変更できるようになります。

これまでの手法

mod_proxyを用いて、必要なRubyバージョン毎に単体起動させたpassengerにTCP/Socketでプロキシするというものでした。なぜなら、読み込むモジュールだけでなく、グローバル値として指定するPassengerRootPassengerRubyの設定が衝突するためです。

これからの手法

Phusion Passenger 4.0 beta 1 is here – Phusion Corporate Blogにて、次のように言及されています。

f:id:yoshi-ken:20140417134331p:plain

Passenger 4.0.0 よりモジュール本体とRubyとの依存が無くなり、複数のRubyバージョンおよびJRubyに対応しました。新しく追加されたオプションPassengerDefaultRubyがグローバル値として適用され、PassengerRubyをアプリケーション毎など、個別に指定できるようになりました。
これらはVirtualHost、Directory、Locationと.htaccessといったどのディレクティブでも設定できます。

使い方

これよりVirtualHosts毎に別のRubyでアプリケーションを動かすサンプルを紹介します。

step1. rbenvのインストール

公式ドキュメントを参考にインストールします
https://github.com/sstephenson/rbenv#installation

step2. passengerのインストール

rbenvで切り替えた各Rubyにてそれぞれインストールを行います。

# rootで実行
gem install passenger
passenger-install-apache2-module

step3. Apacheへ設定ファイルを投入

少なくとも3つの設定(LoadModule, PassengerRoot, PassengerDefaultRuby)を次のいずれかのファイルに書き込みます。 LoadModuleで指定できる.soファイルは、各バージョンのRubyにて実行したgem install passengerでインストールされた場所に存在します。しかしどうやら、どちらのRubyにインストールされたものを指定しても構わないようです。

$ cat /etc/httpd.d/conf/passenger.conf
LoadModule passenger_module /root/.rbenv/versions/2.1.1/lib/ruby/gems/2.1.0/gems/passenger-4.0.41/buildout/apache2/mod_passenger.so
<IfModule mod_passenger.c>
  PassengerRoot /root/.rbenv/versions/2.1.1/lib/ruby/gems/2.1.0/gems/passenger-4.0.41
  PassengerDefaultRuby /root/.rbenv/versions/2.1.1/bin/ruby
</IfModule>

Header always unset "X-Powered-By"
Header always unset "X-Rack-Cache"
Header always unset "X-Content-Digest"

次に、VIrtualHostの設定を行う準備を行います。
各アプリケーションにPassengerRubyオプションを用いてrubyバイナリの位置を指定します。この値はrbenvコマンドで使うrubyバージョンに切り替えた後に、passenger-config about ruby-commandコマンドを叩くと次のように分かります。

passenger-config about ruby-command
passenger-config was invoked through the following Ruby interpreter:
  Command: /root/.rbenv/versions/1.9.3-p545/bin/ruby
  Version: ruby 1.9.3p545 (2014-02-24 revision 45159) [x86_64-linux]
  To use in Apache: PassengerRuby /root/.rbenv/versions/1.9.3-p545/bin/ruby
  To use in Nginx : passenger_ruby /root/.rbenv/versions/1.9.3-p545/bin/ruby
  To use with Standalone: /root/.rbenv/versions/1.9.3-p545/bin/ruby /root/.rbenv/versions/1.9.3-p545/lib/ruby/gems/1.9.1/gems/passenger-4.0.41/bin/passenger start

それぞれ調べた値をvirtualhost設定へPassengerRubyオプションと共に指定します。

$ cat /etc/httpd.d/conf/virtualhosts.conf
NameVirtualHost *:80

<VirtualHost *:80>
   ServerName ruby19.example.jp
   DocumentRoot /var/www/ruby19.example.jp/public
   <Directory /var/www/ruby19.example.jp/public>
     # This relaxes Apache security settings.
     AllowOverride all
     # MultiViews must be turned off.
     Options -MultiViews
   </Directory>
   AllowEncodedSlashes on
   PassengerAllowEncodedSlashes on
   # ruby19.example.jp では Ruby 1.9.3-p545 を利用する
   PassengerRuby /root/.rbenv/versions/1.9.3-p545/bin/ruby
   LimitRequestBody 41943040
</VirtualHost>

<VirtualHost *:80>
   ServerName ruby21.example.jp
   DocumentRoot /var/www/ruby21.example.jp/public
   <Directory /var/www/ruby21.example.jp/public>
     # This relaxes Apache security settings.
     AllowOverride all
     # MultiViews must be turned off.
     Options -MultiViews
   </Directory>
   AllowEncodedSlashes on
   PassengerAllowEncodedSlashes on
   # ruby21.example.jp では Ruby 2.1.1 を利用する
   PassengerRuby /root/.rbenv/versions/2.1.1/bin/ruby
   LimitRequestBody 41943040
</VirtualHost>

step4. 設定の反映

設定を反映させるため、Apacheを再起動します。

# rootで実行
/etc/init.d/httpd restart

これで以上です。
トリッキーな対応無しに複数バージョンの使い分けが出来るようになりましたね。

手元のVagrantでも試せます

本記事で紹介した環境を配布しています。VirtualboxVagrantが入っている環境であれば、次の手順で試せます。 具体的な構築手順については、provision.shファイルをご覧ください。

$ git clone https://github.com/y-ken/multiple_ruby_version_in_apache_passenger.git
$ cd multiple_ruby_version_in_apache_passenger
$ vagrant up
$ vagrant ssh

構築完了後にはその端末へvagrant sshコマンドでログインし、curlコマンドでサンプルアプリを叩いて結果を見てみましょう。
ruby19.example.jp ではruby-1.9.3-p545が、ruby21.example.jp ではruby-2.1.1がインタプリタとして動いていることが確認できます。

$ curl -s -H "Host: ruby19.example.jp" http://127.0.0.1/ | grep exec_prefix
"exec_prefix"=>"/root/.rbenv/versions/1.9.3-p545",

$ curl -s -H "Host: ruby21.example.jp" http://127.0.0.1/ | grep exec_prefix
"exec_prefix"=>"/root/.rbenv/versions/2.1.1",

まとめ

これでサーバ構成のシンプルさや運用の手間を抑えたまま、年に何度もにリリースされる最新のRubyバージョンでも受け入れられるインフラが作れます。
古いバージョンが必要なアプリケーションはそのままに、どんどん最新のRubyを追いかけていけますね!