事象
RailsアプリをCapistranoでAWS EC2へデプロイした時、以下エラーが発生してデプロイに失敗する。
$ bundle exec cap production deploy
00:14 bundler:install
01 $HOME/.rbenv/bin/rbenv exec bundle install --path /var/www/アプリ名/shared/bundle --without development test --deployment --quiet
01 Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
01
01 current directory:
01 /var/www/アプリ名/shared/bundle/ruby/2.3.0/gems/unf_ext-0.0.7.5/ext/unf_ext
01 /home/ユーザ名/.rbenv/versions/2.3.1/bin/ruby -r
01 ./siteconf20180501-3814-oif23n.rb extconf.rb
01 checking for main() in -lstdc++... yes
01 creating Makefile
01
01 To see why this extension failed to compile, please check the mkmf.log which can
01 be found here:
01
01 /var/www/アプリ名/shared/bundle/ruby/2.3.0/extensions/x86_64-linux/2.3.0-static/unf_ext-0.0.7.5/mkmf.log
01
01 current directory:
01 /var/www/アプリ名/shared/bundle/ruby/2.3.0/gems/unf_ext-0.0.7.5/ext/unf_ext
01 make "DESTDIR=" clean
01
01 current directory:
01 /var/www/アプリ名/shared/bundle/ruby/2.3.0/gems/unf_ext-0.0.7.5/ext/unf_ext
01 make "DESTDIR="
01 compiling unf.cc
01 cc1plus: warning: command line option ‘-Wdeclaration-after-statement’ is valid
01 for C/ObjC but not for C++ [enabled by default]
01 cc1plus: warning: command line option ‘-Wimplicit-function-declaration’ is valid
01 for C/ObjC but not for C++ [enabled by default]
01 virtual memory exhausted: メモリを確保できません
01 make: *** [unf.o] エラー 1
01
01 make failed, exit code 2
〜中略〜
cap aborted!
〜中略〜
** DEPLOY FAILED
** Refer to log/capistrano.log for details. Here are the last 20 lines:
環境
Ruby 2.3.1
Rails 5.0.7
Capistrano 3.10.2
サーバ Amazon Linux EC2 t2.micro 4.14.26-46.32.amzn1.x86_64
原因
エラーメッセージに日本語が混じってるので非常に見つけやすかったです。
virtual memory exhausted(仮想メモリが不足)という事で、プログラムが使用できるメモリが不足したことがエラーの原因と推定。
bundlerがサーバに不足しているgemファイルをインストール(bundle install)している際、メモリ不足によってシステムエラーが発生し、gemインストール失敗。
対象のgemはこの場合、unf_extですが、このgemが悪い訳ではありません。
gemのインストール実行中に「メモリを確保できません」とエラーが出ているので、EC2のメモリ不足と推定できます。
調査
実際、メモリの使用状況を見てみると、、、
$ top
top - 12:43:48 up 3 days, 7:33, 2 users, load average: 0.12, 0.03, 0.01
Tasks: 81 total, 1 running, 57 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.3%us, 0.3%sy, 0.0%ni, 99.3%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 1011172k total, 803516k used, 207656k free, 17532k buffers
Swap: 0k total, 0k used, 0k free, 102308k cached
こんな感じ。4行目のMem行が物理メモリに関する統計。
合計(total) = 1011172kバイト = 987Mバイト
残り(free) = 207656kバイト = 202Mバイト
確かに、こんな容量じゃ負荷のかかる処理はこなせないですね。
5行目はスワップ領域(仮想メモリ)。totalが0kなのでスワップ領域がなく、今回のメモリ不足に対応できなかったと想定されます。
対策
サーバの仮想メモリを増やします。
必要な仮想メモリサイズを調べるために、RedHatの文献を参考にしたサイトを確認。
t2.microのメモリサイズは1GiB(Giは1024を単位とするギガの事)なので、この場合は物理メモリの2倍が目安とのこと。よって、2GiBを用意することにします。
手順は以下の通り。
- 2GiBのファイルを作成
- 作成したファイルをスワップファイルシステムとして割り当てる(スワップ領域化)
- スワップ領域を有効にする
- 常に使用する設定にする(オートマウント化)
(1)2GiBのファイルを作成
$ sudo dd if=/dev/zero of=/swap bs=1M count=2048
2048+0 レコード入力
2048+0 レコード出力
2147483648 バイト (2.1 GB) コピーされました、 32.5177 秒、 66.0 MB/秒
$ ll /swap
-rw-r--r-- 1 root root 2147483648 5月 2 10:48 swap
ちなみにllはls -l
の略(エイリアスコマンド。Amazon Linuxのデフォルト。aliasコマンドで確認可。)
または
$ sudo dd if=/dev/zero of=/swap bs=1G count=2
これで2GBのファイルが作成されました。
補足:ddコマンド
入力内容を出力ファイルにディスクイメージでコピーするコマンド。
Disk to Diskの略だと思っています。(調べてないです)
if=入力ファイル
input fileの略(だと思っている)。
/dev/zeroは0埋めされたデバイスファイル。
デバイスファイルとは、ファイルへ読み書きする感覚でデバイスへアクセスすることが可能となる抽象化されたデバイスのこと。
of=出力ファイル
output fileの略。
/swapと指定し、ルートディレクトリ下にswapという名称のファイルを作成します。
bs=ブロックサイズ
作成するファイルの容量の単位を指定。
count=ブロック数
bsで指定した単位で何ブロック作成するかを指定する。
bs=1Mで、count=2048で2048MiB = 2GiBのファイルを作成することになる。
(2)作成したファイルをスワップファイルシステムとして割り当てる(スワップ領域化)
$ sudo mkswap /swap
スワップ空間バージョン1を設定します、サイズ = 2097148 KiB
ラベルはありません, UUID=###########################
mkなんちゃら、はmakeなんちゃらの略です。
(3)スワップ領域を有効にする
$ sudo swapon /swap
swapon: /swap: 安全でない権限 0644 を持ちます。 0600 がお勧めです。
権限に関するメッセージが出力されたので、アドバイスに従って権限を変更します。
$ sudo chmod 600 /swap
実際にスワップファイルが有効になっているか確認します。
$ cat /proc/swaps または sudo swapon -s
Filename Type Size Used Priority
/swap file 2097148 0 -2
無効化されてると行が表示されないので、これで問題なし。
(4)常に使用する設定にする(オートマウント化)
エディタで/etc/fstab
ファイルを編集し、以下の1行を挿入します。
/swap swap swap defaults 0 0
fstabはシステム起動時に自動でマウント(認識)するファイルシステム(参考文献参照)を一覧です。
恐らく、File System Tableの略。Linuxでは一覧形式の設定ファイルが「なんちゃらtab」という名称であることがあります。crontabとか。
列 | 指定する値 | 用途 |
---|---|---|
1列目 | デバイスファイル名 | マウント(認識)したいデバイス。ddのofで指定したファイル。 |
2列目 | マウントポイント | マウントさせる場所(普通はディレクトりを指定)。スワップ領域ならswapを指定。 |
3列目 | ファイルシステムの種類 | スワップ領域の場合は、swapを指定。 |
4列目 | マウントオプション | マウント時の付随動作を指定。特に要件がなければデフォルトの動作(表下参照)であるdefaultsを指定。 |
5列目 | dumpフラグ | システムクラッシュ時などにダンプファイル(メモリの内容をファイル出力する)の出力対象かどうかを指定するフラグを指定。swapとかの特殊な場合は0を指定して対象外にするのが普通です。 |
6列目 | ブート時のfsckがチェックする順序 | 0を指定するとfsckコマンドによるファイルシステムのエラーチェックを実行しません。swapみたいな特殊領域は通常チェックしません。 |
マウントオプションのデフォルト動作は以下の通り。
マウントオプション | 意味 |
---|---|
async | 非同期入出力を設定 |
auto | mountコマンドの-aオプション指定時にマウントする対象にする |
dev | キャラクタデバイスまたはブロックデバイス(任意の位置にアクセス可能なデバイス)のどちらかで解釈する |
exec | バイナリの実行を許可する |
nouser | 一般ユーザによるマウントを許可しない |
rw | 読み書きを許可する |
suid | SUIDとSGIDを有効にする。つまりどのユーザでも使用可能にする。 |
結果
一応、cap production deploy
で突っかかったエラーからは脱出しました。
例のgemインストール中に仮想メモリを使用している様子も伺えました。
$ cat /proc/swaps
Filename Type Size Used Priority
/swap file 2097148 98304 -2
ただ、次のエラーが出たので、次はそちらに取り掛かります。
参考文献
・スワップ領域のサイズ目安
http://server.etutsplus.com/allocate-swap-space/
・ファイルシステムとは
https://hnavi.co.jp/knowledge/blog/filesystem/