概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Fullstaq Ruby: First impressions, and how to migrate your Docker/Kubernetes Ruby apps today — Martian Chronicles, Evil Martians’ team blog
- 原文公開日: 2019/08/10
- 著者: Andrey Novikov
- サイト: Evil Martians -- ニューヨークやロシアを中心に拠点を構えるRuby on Rails開発会社です。良質のブログ記事を多数公開し、多くのgemのスポンサーでもあります。
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%以下にまで削減されたことが示されています。
- バックグラウンドジョブワーカー(私たちの場合はSidekiq)の重さも2/3までダイエットできました。Fullstaq Ruby移行前は1.5〜2 GBだったのが、移行後500〜700 MBになりました。
- 短命なプロセスのメモリ消費についてはこれといった違いは生じませんでした(cronジョブなど)。
-
レスポンスタイムやCPU利用率には何の違いも見いだせませんでした。
上のグラフから、メモリ消費がかさんだ原因はメモリ断片化であったことが証明されました。
Rubyのバイナリを差し替えるだけで相当の改善を得られたのです。
他の手段は?
jemallocをオプションとして選択できない、またはMRIを他のものに差し替えるわけにいかないのであれば、Ruby標準のglibc mallocの振る舞いを調整するMALLOC_ARENA_MAX=2
という呪文をお試しください。結果はFullstaq Rubyとほぼ同じと言ってよいレベルです。
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ジョブやスクリプトなど)でのメモリ消費は大して変わりません。
- パフォーマンスもわずかに向上する可能性がありますが、ワークロードのプロファイル次第です。