Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rubyバイナリのビルドでenable-load-relativeフラグを活用する(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

Rubyバイナリのビルドでenable-load-relativeフラグを活用する(翻訳)

私はneetoCIというCI/CDソリューションの構築を手がけています。コンパイル済みのバイナリをビルドするときに、いくつかの課題に直面しました。本記事では、私たちが直面した問題の内容と、その解決方法について解説します。

🔗 プリコンパイル済みRubyバイナリとは

プリコンパイル済みRubyバイナリは、特定のシステムを対象に最適化した機能を含む、配布可能なRubyバージョンです。このようなRubyバイナリを利用することで、Rubyをソースコードから手動でコンパイルする必要がなくなり、時間を節約できます。プリコンパイル済みRubyバイナリを使っていれば、さまざまなバージョンのRubyを利用するアプリケーションを複数のマシンに短時間でデプロイできます。

RVM(Ruby Version Manager)は、UnixライクなシステムでRubyインストールを管理するために広く利用されており、さまざまなCPUアーキテクチャに合わせてカスタマイズされたプリコンパイル済みRubyバイナリを提供しています。これらのバイナリでは、readlineサポートやSSL/TLSサポートなどの追加機能が提供されており、RVM binariesで探せるようになっています。

🔗 プリコンパイル済みRubyバイナリが必要になるとき

neetoCIは、ユーザーコードをコンテナ化された環境で実行しなければなりません。Ruby on Railsアプリケーションを実行するにはRuby環境が不可欠ですが、システムに元からインストールされているRubyに依存すると、ユーザーが必要とするRubyバージョンと合わなくなる可能性があるため、実用性に欠けます。rbenvやRVMを使えば、必要なRubyバージョンをインストールできますが、この方法だと時間がかかる可能性があります。私たちは時間を節約するため、プリコンパイル済みRubyバイナリを利用することにしました。

neetoCIは、その名の通りCI/CDシステムなので、アプリケーションで必要なRubyバージョンをもれなく提供することを保証しなければなりません。そういうわけで、RVMが提供するRubyバイナリに依存するのではなく、自分たちで独自バイナリをビルドすることにしました。そうすることで、ビルド時にRubyバイナリにシステム固有の最適化をさらに加えられるようになります。

🔗 プリコンパイル済みRubyバイナリを独自にビルドする

Rubyバイナリのビルドは、Ruby公式ドキュメントに沿って行いました。ローカルの開発マシンではビルドに成功したのですが、同じバイナリがCI/CD環境ではエラーになりました。

$ bundle config path vendor/bundle
./ruby: bad interpreter: No such file or directory

問題をデバッグするにあたり、最初に着目したのは$PATHでしたが、$PATHの問題を解決しても問題は解決しませんでした。そこで、根本原因を特定するために徹底調査を行いました。

運悪く、このエラーに関する情報はネット上にあまり見当たらず、Ruby公式ドキュメントにも記述されていませんでした。

次なる手段として、RVMからRuby 3.2.2バイナリ版をダウンロードすることにしました。コンフィグファイルを調べてみると、Rubyバイナリのビルド中に以下の引数がconfigureコマンドに渡されていることが判明しました。

configure_args="'--prefix=/usr/share/rvm/rubies/ruby-3.2.2' '--enable-load-relative' '--sysconfdir=/etc' '--disable-install-doc' '--enable-shared'"

コンフィグ引数の説明は以下のとおりです。

  1. --prefix=/usr/share/rvm/rubies/ruby-3.2.2:
    インストール完了後に、Rubyバイナリやライブラリなどのファイルを保存するディレクトリを指定します。

  2. --enable-load-relative:
    これは、ダイナミックリンクライブラリ(DLL: dynamically linked library)を相対パスで読み込むようRubyに指示します。これにより、共有ライブラリを絶対パスではなく相対パスで読み込むようになります。この機能は、特定の導入シナリオで役立つ場合があります。

  3. --sysconfdir=/etc:
    この引数は、Rubyのシステム設定ファイルをインストールするディレクトリを指定します。この場合は/etcディレクトリが指定されています。

  4. --disable-install-doc:
    このオプションを有効にすると、ビルド中にRubyのドキュメントファイルをインストールしなくなります。Rubyドキュメントが不要な場合は、ビルドを高速化してディスク領域を節約するのに有効です。

  5. --enable-shared:
    このオプションを有効にすると、Rubyで利用する共有ライブラリをビルドできるようになります。Rubyで共有ライブラリを利用すると、実行時に特定機能を動的にリンクして読み込み可能になるので、パフォーマンス向上やメモリ使用量の削減が期待できます。

要するに、--enable-load-relativeフラグを有効にすると、コンパイル済みRubyバイナリが$ORIGIN変数を用いて自分のディレクトリ内にある共有ライブラリを探索可能になるということです。

RubyバイナリをDockerレジストリ上でビルドしたときに渡した--prefixは、たしか/usr/share/neetociのような感じになっていました。バイナリをビルドすると、この/usr/share/neetociがさまざまな場所にハードコードされます。このバイナリをダウンロードしてCIで使うと、Rubyは依存関係を読み込むためにCI環境の/usr/share/neetociを探索します。

しかし、--enable-load-relativeフラグを有効にしてビルドすれば、Rubyはハードコードされた値を利用しなくなり、代わりに$ORIGIN変数で指定されたディレクトリ内で依存関係を探索するようになります。

これは、Rubyバイナリを別のディレクトリや別のシステムに再配置する場合に特に有用です。相対パスを$ORIGINで指定するようにしておけば、Rubyバイナリをどこに配置しても共有ライブラリを探索できるようになります。

逆に言えば、この--enable-load-relativeフラグを指定しておかないと、共有ライブラリの読み込みに絶対パスが使われるため、Rubyバイナリを別の場所に移動すると共有ライブラリを見つけられなくなり、問題が発生する可能性があります。私たちのユースケースでは、コンテナごとにRubyバイナリを作成してダウンロードするので、絶対パスが原因となってエラーが発生していたのでした。

これを解決するために、--enable-load-relativeフラグを有効にしました。これで、Rubyバイナリが共有ライブラリを無事探索できるようになり、CI/CD環境で期待通り動作するようになりました。

関連記事

M1 MacでRuby 2.4〜3.2をrbenvでビルドする最小限のセットアップを全部調べた

Ruby 3.3.0: aarch64-linux環境でFiber.new{ }.resumeを呼ぶと落ちる問題


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。