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

Fullstaq Rubyの第一印象とDocker/Kubenetes Rubyアプリとの統合(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Fullstaq Rubyの第一印象とDocker/Kubenetes Rubyアプリとの統合(翻訳)

Fullstaq Rubyとは

Fullstaq Rubyは、標準のMRI Rubyインタプリターのカスタムビルドです。メモリアロケーターの置き換え、セキュリティパッチの適用などさまざまな手を加えてあります。

Rubyを長くやっている人なら、なつかしのREE(Ruby Enterprise Edition)を思い出すことでしょう。REEはRuby 1.8.7やRuby on Rails 2.2といういにしえの代物です(ほぼ10年前!)。あの時代が懐かしくなります。REEは今でもRVMやrbenvでインストールできますし、レガシーアプリケーションの中には今も引き続き実行できるものもあれば、とっくに移行したものもあります。REEはパフォーマンス向上、メモリ削減、古いセキュリティ設定の調整などのため、Ruby 1.8.7にさまざまなパッチを適用していました。

それらの問題はMRI 1.9.xでほとんど解決し、MRI 1.9.xの利用が広まるにつれてREEは時代遅れになってきました。しかし現代の「vanilla(訳注: ピュアな)」MRIですら、比較的簡単に修正できるほころびがいくつもあります。中でも腹立たしいのはメモリ断片化によるメモリ肥大化です。

そんなわけで、REEの作者であるHongli LaiがFullstaq Rubyをリリースしたことはまったく不思議ではありません。

REEは死んだ、Fullstaq Ruby万歳!

Fullstaq Rubyが必要な理由があるとすれば

Evil Martiansが手掛けるプロジェクトのひとつで、深刻なメモリ肥大化が発生したことがありました。アプリケーションには膨大なIOがあり、高コンカレンシー設定を施したSidekiqプロセスも山のようにありました(1プロセスあたり20スレッド)。この設定はパフォーマンスの観点から最適です。理由は、ワーカーがさまざまなリモートAPIや自社内データベースやキャッシュにリクエストを送信することがほとんどだからです。しかしコンカレンシーをこれほど高く設定すると、メモリ断片化も大きく進みます。私たちのSidekiqプロセスのひとつひとつが「数GB単位で」メモリを食らうのです。

Sidekiqのコンカレンシー設定について詳しくはNate Berkopecの「Sidekiq in Practice part 1」記事をご覧ください。

私たちはMRI 2.6.3から、Fullstaq Ruby 2.6.3+jemallocに移行することを決定し、どんなふうに動くかをチェックすることにしました。

たしかに違う!

私たちは、production環境で動作する商用アプリケーションでFullstaq Rubyを試してみました。このアプリケーションは、支払いクライアントからのリクエストを24時間ぶっ通しでさばきます。

結論: どこもぶっ壊れず、ダウンタイムもゼロでした!

今度は監視グラフを見てみましょう。長時間実行しているプロセスのメモリ肥大化は、事実上消え去りました!

  • Webアプリケーションプロセスのメモリ消費はきわめて安定しました(1/4以下です!)。肥大化はその後も散発的に発生してはいますが、スパイク時のメモリ消費は50%以下にまで削減されたことが示されています。

Web pods memory consumption before and after switching to Fullstaq Ruby

Webポッドのメモリ消費推移
  • バックグラウンドジョブワーカー(私たちの場合はSidekiq)の重さも2/3までダイエットできました。Fullstaq Ruby移行前は1.5〜2 GBだったのが、移行後500〜700 MBになりました。

Sidekiq pods memory consumption before and after switching to Fullstaq Ruby

Sidekiqポッドのメモリ消費推移
  • 短命なプロセスのメモリ消費についてはこれといった違いは生じませんでした(cronジョブなど)。

  • レスポンスタイムやCPU利用率には何の違いも見いだせませんでした。

上のグラフから、メモリ消費がかさんだ原因はメモリ断片化であったことが証明されました。

Rubyのバイナリを差し替えるだけで相当の改善を得られたのです。

他の手段は?

jemallocをオプションとして選択できない、またはMRIを他のものに差し替えるわけにいかないのであれば、Ruby標準のglibc mallocの振る舞いを調整するMALLOC_ARENA_MAX=2という呪文をお試しください。結果はFullstaq Rubyとほぼ同じと言ってよいレベルです。

Fullstaq Ruby with jemalloc on the left and MRI with two malloc arenas on the right

Fullstaq Ruby + jemallocが左、MRI + mallocアリーナ2つが右
私たちの場合、mallocアリーナの個数に制約をかけた場合(右)では、Ruby + jemalloc(左)に比べて50〜100 MB程度メモリ消費が増えました。

MALLOC_ARENA_MAX=2のベンチマークについて詳しくは弊社のブログ「Cables vs. malloc_trim, or yet another Ruby memory usage benchmark」をご覧ください。

かくして私たちはFullstaq Rubyを使い続けることに決めました。

Fullstaq Rubyのインストール方法

記事執筆時点では、Fullstaq Rubyをインストールする方法はdebパッケージまたはrpmパッケージしかありません(直接またはリポジトリ経由でのインストール)。しかし私たちのアプリはKubernetesクラスタにデプロイするので、Dockerイメージが必要になりました。Fullstaq Rubyには公式の「コンテナエディション」がないので、独自のイメージをビルドしてみましょう。大した手間ではありません!

訳注: 翻訳記事公開時点では、Fullstaq Rubyサイトのコンテナエディションは"coming soon"となっています。

fullstaqruby.orgより

公式のRuby Dockerイメージで使われているLinuxディストリビューションに合わせてDebian 9を使うことにし、Rubyのバージョンを定義します。

FROM debian:stretch-slim

ARG RUBY_VERSION=2.6.3-jemalloc

続いて必要なソフトウェアをインストールし、Fullstaq Ruby APTリポジトリを追加し、Rubyそのものをインストールしてaptキャッシュをクリーンアップします。以上を1つのコマンドに集約することで、Dockerのレイヤのサイズを削減します。

RUN apt-get update -q\
    && apt-get dist-upgrade --assume-yes\
    && apt-get install --assume-yes -q --no-install-recommends curl gnupg apt-transport-https ca-certificates\
    && curl -SLf https://raw.githubusercontent.com/fullstaq-labs/fullstaq-ruby-server-edition/master/fullstaq-ruby.asc | apt-key add -\
    && echo "deb https://apt.fullstaqruby.org debian-9 main" > /etc/apt/sources.list.d/fullstaq-ruby.list\
    && apt-get update -q\
    && apt-get install --assume-yes -q --no-install-recommends fullstaq-ruby-${RUBY_VERSION}\
    && apt-get autoremove --assume-yes\
    && rm -fr /var/cache/apt

Fullstaq Rubyではrbenvも依存関係としてインストールしますが、Dockerでは不要なので、以下のようにRubyの公式Dockerイメージと同じ方法でシステムの$PATHにRubyとgemのバイナリを追加しましょう。

ENV GEM_HOME /usr/local/bundle
ENV BUNDLE_PATH="$GEM_HOME"\
    BUNDLE_SILENCE_ROOT_WARNING=1\
    BUNDLE_APP_CONFIG="$GEM_HOME"\
    RUBY_VERSION=$RUBY_VERSION\
    LANG=C.UTF-8 LC_ALL=C.UTF-8

# おすすめのパス: https://github.com/bundler/bundler/pull/6469#issuecomment-383235438
ENV PATH $GEM_HOME/bin:$BUNDLE_PATH/gems/bin:/usr/lib/fullstaq-ruby/versions/${RUBY_VERSION}/bin:$PATH

CMD [ "irb" ]

以上でできあがりです!

このイメージは既にビルドして公開してあります。quay.ioにある私たちのリポジトリからpullしていただいて構いません。

docker pull quay.io/evl.ms/fullstaq-ruby:2.6.3-jemalloc-stretch-slim

Dockerfileはgithub.com/evilmartians/fullstaq-ruby-dockerにあります。

私たちのアプリケーションのDockerfileでベースイメージを以下のように置き換えられます。

-ARG RUBY_VERSION=2.6.3
+ARG RUBY_VERSION=2.6.3-jemalloc

-FROM ruby:${RUBY_VERSION}-stretch-slim
+FROM quay.io/evl.ms/fullstaq-ruby:${RUBY_VERSION}-stretch-slim

あとはstagingにデプロイし、最後にproductionにデプロイします。

皆さんもどうぞご自由にお使いください!

まとめ

  • Fullstaq Rubyへの移行はスムーズです。Rubyとgemを再インストールするだけですべて問題なくやれます。
  • アプリケーションサーバーやバックグラウンドジョブワーカーのプロセスが消費するメモリ量が劇的に削減されます。
  • 短期的なプロセス(cronジョブやスクリプトなど)でのメモリ消費は大して変わりません。
  • パフォーマンスもわずかに向上する可能性がありますが、ワークロードのプロファイル次第です。

関連記事

Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)


CONTACT

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