ElasticBeanstalkで動かしているRailsプロジェクトにpuma_worker_killerを導入する

## 起きた問題
ElasticBeanstalkで動かしているRailsプロジェクトのメモリ使用率が高止まりしていました。
プロセスを見てみるとPumaが主な要因でした。
[![Image from Gyazo](https://i.gyazo.com/1b8f9882a59b3dace952534fe78ea802.png)](https://gyazo.com/1b8f9882a59b3dace952534fe78ea802)

メモリを解放するために、インスタンスの自動入れ替えも検討しましたが、ElasticIPの自動付与問題の課題があり、今回は`puma_worker_killer`というgemを使用してみることにしました。

puma_worker_killerの公式ページ
https://github.com/zombocom/puma_worker_killer

## 前提
- ElasticBeanstalk環境 (Ruby 2.7 running on 64bit Amazon Linux 2/3.4.7)
- EC2インスタンス数:1台 (ElasticIP付き)
- Puma worker数:2

## 手順
### 1.Gemfileの修正
```ruby:Gemfile
group :production do
  gem 'puma', '5.6.4'
  gem 'puma_worker_killer' #追加
end
```

```terminal
$ bundle install
```

### 2. config/puma.rbに設定の追加
```ruby:puma.rb
if ENV.fetch("RAILS_ENV") == "production"
  # ElasticBeanstalkのpumaconf.rbのデフォルト設定
  directory '/var/app/current'
  threads 8, 32
  workers %x(grep -c processor /proc/cpuinfo)
  bind 'unix:///var/run/puma/my_app.sock'
  stdout_redirect '/var/log/puma/puma.log', '/var/log/puma/puma.log', true

  # PumaWorkerKiller設定
  before_fork do
    require 'puma_worker_killer'
    PumaWorkerKiller.config do |config|
      config.ram = 4096 # EC2インスタンスのメモリ量(MB)
      config.frequency = 5 * 60 # 何秒ごとに現在のメモリ使用量を確認するか(5分ごと)
      config.percent_usage = 0.80 # Pumaがメモリを何%使ったら再起動するか(アラート発砲が90%、他のプロセスもメモリを喰うことを考えて80%にする)
      config.rolling_restart_frequency = 24 * 60 * 60 # 上記の設定とは別に、1日に1回は再起動する
      config.reaper_status_logs = true # ログ出力(デバッグ用)
    end
    PumaWorkerKiller.start
  end
end
```

### 3. Procfileの準備
```Procfile
web: mkdir -p /var/app/current/tmp/pids && bundle exec puma -C /var/app/current/config/puma.rb
```

## 効果確認

EB環境でpuma.logを確認した結果です
```terminal
cat /var/log/puma/puma.log | grep "PumaWorkerKiller"
```

#### config.percent_usage によるWorker再起動
[![Image from Gyazo](https://i.gyazo.com/c9d19aa7619ae0af654e632420364b5a.jpg)](https://gyazo.com/c9d19aa7619ae0af654e632420364b5a)

#### config.rolling_restart_frequency によるWorker再起動
[![Image from Gyazo](https://i.gyazo.com/29552b7b788561a8ea294e500fe03fcb.jpg)](https://gyazo.com/29552b7b788561a8ea294e500fe03fcb)

#### CloudWatchによるメモリ使用率のグラフ
[![Image from Gyazo](https://i.gyazo.com/8e5bc3eab77447996d79086b6001b3b3.png)](https://gyazo.com/8e5bc3eab77447996d79086b6001b3b3)

## ハマったポイント
#### 1.EB環境でPumaWorkerKillerが起動しない
デフォルトのElasticBeanstalk環境だと`config/puma.rb`の設定を見ずに、`/opt/elasticbeanstalk/config/private/pumaconf.rb`の設定を利用してPumaを起動しているようでした。
そのため、`config/puma.rb`に設定を記載しているのにEB環境ではPumaWorkerKillerが起動せずに、ローカルでのテストでは起動するという現象が発生しました。
ルートディレクトリに`Procfile`を作成し、`config/puma.rb`を使用してPumaを起動させることで解決しています。
併せて、`/opt/elasticbeanstalk/config/private/pumaconf.rb`にあったデフォルト設定を、`/config/puma.rb`にも記載しています。

Procfileについて
https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/ruby-platform-procfile.html

#### 2.pumaconf.rbを.ebextentionsで修正するも、PumaWorkerKillerを読み込めない
`/opt/elasticbeanstalk/config/private/pumaconf.rb`の設定を利用すると知った最初は、`.ebextentions`を利用して`pumaconf.rb`にPumaWorkerKillerの設定を追記しようとしましたが、以下のエラーが出て上手くいきませんでした。

```
/opt/rubies/ruby-2.7.6/lib/ruby/site_ruby/2.7.0/rubygems/core_ext/kernel_require.rb:85:in `require': cannot load such file -- puma_worker_killer (LoadError)
```

```
puma/puma.log:[8772] WARNING hook before_fork failed with exception (NameError) uninitialized constant #<Class:#<Puma::DSL:0x0000000001df7130>>::PumaWorkerKiller
```

解決の糸口が見つからず、最終的には`Procfile`を使用して、`config/puma.rb`を利用する方針で落ち着きました。