マルチアーキテクチャなステージング環境で bundler を利用する。
Ruby on Rails 等で使われる bundler はアプリケーションに依存する gem パッケージをバンドルすることができ、パッケージの管理に役に立つ。しかしアーキテクチャ固有のパッケージを保持してしまうことで、ステージング環境で複数のアーキテクチャが混在している場合にデプロイや CI において混乱をきたしやすい。ここでは経験から得られたノウハウとハマリポイントをまとめる。
bundle install は path を指定して実行する。
なにも考えず bundle install すると $GEM_HOME が汚染される。そこでアーキテクチャごとに以下のようにパスを指定して実行するのがいいだろう。
bundle install --path vendor/bundler64
混乱を避けるため、アーキテクチャごとに PATH を変えておくのが良さそうだ。同一アーキテクチャであれば rsync 等で丸々ディレクトリをコピーすることが可能となり便利である。
サイトローカルな設定ファイルとパッケージをリポジトリに含めない。
Rails の場合 bundle install により $RAILS_APP 配下に Gemfile.lock, .bundle/config, vendor/bundler64 が作られる。これらはサイトローカルなものなのでバージョン管理ソフトウェアで管理しないように ignore にする。
CI やデプロイを実行する場合は各々のスクリプト内で以下のように bundle exec を付けて rake タスクを実行する。これにより .bundle/config 内部で指定されたパスにあるパッケージが利用される。
export RAILS_ENV=production # または test bundle exec rake assets:precompile bundle exec rake db:migrate
開発の場合も同様に bundle exec を利用する。
bundle exec rails server -p 9999 -e development
リンケージされるモジュールの場所に気をつける。
Ruby のコード自体は共通でもリンクされるモジュールが正しくない場合は当然エラーとなる。
ruby: symbol lookup error: /var/lib/jenkins/jobs/rails_app/workspace/vendor/bundler64/ruby/1.9.1/gems/sqlite3-1.3.5/lib/sqlite3/sqlite3_native.so: undefined symbol: sqlite3_open_v2
このような場合は Gemfile.lock と .bundle/config 及びバンドルされたパッケージを一旦消去して、再度 bundle install する。また Ruby を起動するソフトウェアのライブラリのパスをよく確認する。
たとえば上記の例で該当モジュールを ldd したところ以下の結果が得られたものとする。この場合、読み込むモジュールを LD_LIBRARY_PATH で指定すれば正常に動作しそうである。
ldd /var/lib/jenkins/jobs/rails_app/workspace/vendor/bundler64/ruby/1.9.1/gems/sqlite3-1.3.5/lib/sqlite3/sqlite3_native.so linux-vdso.so.1 => (0x00007fff86783000) libsqlite3.so.0 => /usr/local/lib/libsqlite3.so.0 (0x00002ab07381c000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00002ab073aa5000) librt.so.1 => /lib64/librt.so.1 (0x00002ab073cc0000) libdl.so.2 => /lib64/libdl.so.2 (0x00002ab073ec9000) libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00002ab0740ce000) libm.so.6 => /lib64/libm.so.6 (0x00002ab074306000) libc.so.6 => /lib64/libc.so.6 (0x00002ab074589000) /lib64/ld-linux-x86-64.so.2 (0x00000032dd800000)
Jenkins CI の場合はビルド後の追加タスクとしてシェルスクリプトを別途用意して、スクリプト内部で環境変数を export すれば良い。
RHEL + Passenger の場合は Apache の起動スクリプト /etc/init.d/httpd で以下のように環境変数を指定する。
export LD_LIBRARY_PATH=/usr/local/lib:/usr/lib
Apache は設定ファイル httpd.conf で以下のように指定しないと環境変数が有効にならないのでこちらも忘れずに指定する。
PassEnv LD_LIBRARY_PATH