yamotonalds's blog

Webアプリケーション開発における技術メモが中心です。たまにWebサービス、興味を持ったデバイス、自作PCに関する話題もあるかも。Amazon好きなのでAmazon.co.jpアソシエイト使ってます。

ElasticBeanstalkで大きめのデータをPOSTするとInternalServerError(500)が発生する

AWSのElastic BeanstalkでRubyのアプリケーションを動かしていたところ、ある程度大きなデータ(リクエストのContent-Lengthが大きい)をPOSTするとInternal Server Error (HTTPエラーコード 500)になる不具合が発生した。

アプリケーションログにはエラーはおろかアクセスすら記録されていないし何事かと思ってたらPassengerの方のログにエラーが記録されていた。

# /var/app/support/logs/passenger.log
2013/xx/xx xx:xx:xx [crit] 1745#0: *170726 open() "/tmp/passenger-standalone.1667/client_body_temp/0000000003" failed (2: No such file or directory), client: xxx.xxx.xxx.xxx, server: _, request: "POST /xxx/xxx HTTP/1.1", host: "www.example.com", referrer: "http://www.example.com/xxx"

Passengerのことはまだよく知らないのだけれどどうやら大きなデータをPOSTされたときに作られる一時ファイルの読み込みに失敗しているようだ。
サーバにログインして /tmp の中を見たけれど passenger.x.x.xxxxx というディレクトリはあったが確かに passenger-standalone.xxx というディレクトリは無い。
エラーメッセージで検索かけて見つかったのが次のページ。

Issue Uploading Files from Rails app hosted on Elastic Beanstalk - Stack Overflow

なるほど。cronで /tmp の中を掃除するときにPassengerに必要なファイルまで消してしまっているということか。
BeanstalkのWebコンソールからアプリケーションをRestartさせたら /tmp に passenger-standalone.xxx ディレクトリができていた。
これがtmpwatchで消されているようだ(親ディレクトリごと削除されていることを考えると上記のPassengerのエラーは読み込みではなく書き込み時のエラーかも)。

解決方法

本来はAmazon側で解決すべき問題だけどそれを待ってもいられない。
上記のページにあるように /etc/cron.daily/tmpwatch で /tmp 内のPassenger関連ディレクトリを除外するようにした。

/etc/cron.daily/tmpwatch
#! /bin/sh
flags=-umc
/usr/sbin/tmpwatch "$flags" -x /tmp/.X11-unix -x /tmp/.XIM-unix \
        -x /tmp/.font-unix -x /tmp/.ICE-unix -x /tmp/.Test-unix \
        -X '/tmp/hsperfdata_*' -X '/tmp/passenger*' 10d /tmp
/usr/sbin/tmpwatch "$flags" 30d /var/tmp
for d in /var/{cache/man,catman}/{cat?,X11R6/cat?,local/cat?}; do
    if [ -d "$d" ]; then
        /usr/sbin/tmpwatch "$flags" -f 30d "$d"
    fi
done

ただ、今のインスタンスを直接いじってもインスタンスが変わったときに戻ってしまう。
なので「Custom AMIが必要か…」と思っていたら .ebextensions 以下の設定ファイルで対応可能だった。

EC2 インスタンス上のソフトウェアのカスタマイズ - AWS Elastic Beanstalk

アプリケーションのソースコードに以下のファイルを追加。

# .ebextensions/overwrite_cron_tmpwatch.config
files:
  "/etc/cron.daily/tmpwatch":
    mode: "000755"
    owner: root
    group: root
    content: |+
      #! /bin/sh
      flags=-umc
      /usr/sbin/tmpwatch "$flags" -x /tmp/.X11-unix -x /tmp/.XIM-unix \
        -x /tmp/.font-unix -x /tmp/.ICE-unix -x /tmp/.Test-unix \
        -X '/tmp/hsperfdata_*' -X '/tmp/passenger*' 10d /tmp
      /usr/sbin/tmpwatch "$flags" 30d /var/tmp
      for d in /var/{cache/man,catman}/{cat?,X11R6/cat?,local/cat?}; do
          if [ -d "$d" ]; then
        /usr/sbin/tmpwatch "$flags" -f 30d "$d"
      fi
      done

commands:
  delete_backup_file:
    command: "rm /etc/cron.daily/tmpwatch.bak"
    ignoreErrors: true

rmしているのはfilesでファイルを作成するときに元のファイルがバックアップされるため。

これでなんとかなったけど、大きなデータかつデプロイ直後には発生しない不具合とかわかりにくいので勘弁願いたい。