DelayedJobの死活監視＆自動再起動設定

# はじめに
私はシステムの運用保守を担当しているエンジニアです。
今回はsupervisorを導入してDelayedJobの死活監視＆自動再起動をする方法を共有します。

# 経緯(なぜ死活監視が必要か？)
本システムはDelayedJob(後述)を導入してジョブの非同期処理を実現しており、Dockerで開発環境を構築しています。
また本番・検証環境はDockerイメージをECRでバージョン管理し、ECSとRDSを接続しています(実際はもっと複雑ですが割愛します)。

ある日ECSとRDSの接続が切れてしまい、DelayedJobが停止していることを発見しました。
たまたま発見できたから良かったものの、もしジョブが停止してしまうとメッセージが送信されないなどの障害が発生してしまいます。
また人が監視不可能な時間帯(深夜や早朝など)に停止した場合、発見までに時間がかかってしまいます。

そこでsupervisor(後述)を導入して死活監視＆自動再起動を設定しました。

# こんな人に読んでほしい

- 運用保守をしている
- 常時起動のサービスやライブラリを使用している
- supervisorを使ってみたい

# DelayedJobとは
Ruby on Railsアプリケーションライブラリのひとつで、バックグラウンドジョブを非同期処理するためのキューイングシステムです。
処理すべきジョブがdelayed_jobsテーブルに格納され、ワーカーがそのテーブルからジョブを取り出して順次処理します。
もしDelayedJobが何らかの理由で停止した場合ジョブの処理は中断されますが、すでにキューに保存されたジョブはデータベースに残っています。
そのためワーカーを再起動すれば停止中に実行されなかったジョブが再びキューから取り出され、順次処理されます。
本システムではバッチにより大量のユーザーへメッセージ送信をする場合などに使用しています。
[DelayedJob参考](https://github.com/collectiveidea/delayed_job)

# supervisorとは
プロセス管理のためのツールで、特にLinux環境でバックグラウンドプロセスをデーモン化し、監視と管理を行うために使用されます。
各プロセスの設定は設定ファイルで定義します。
これにより「プロセスの起動」「停止」「再起動」「ログ管理」などを一元管理できることが特徴です。

[プロセスとは IT用語辞典より](https://e-words.jp/w/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9.html)
[ジョブとは IT用語辞典より](https://e-words.jp/w/%E3%82%B8%E3%83%A7%E3%83%96.html)
[デーモンとは IT用語辞典より](https://e-words.jp/w/%E3%83%87%E3%83%BC%E3%83%A2%E3%83%B3.html)

# 設定
## Dockerfileへの設定
まずDockerへ以下を記述します。
あくまで一例ですので、既存のDockerfileの設定にうまく組み込んでください。
※Linux環境へ直接インストールする場合は適宜読み替えて必要なコマンドを実行してください。

```Dockerfile
# ベースイメージの指定
FROM ubuntu:20.04

# supervisorのインストール
RUN apt-get update && \
    apt-get install -y \
    supervisor

# アプリケーションの依存関係をインストール
# COPY ./app /app

# supervisorの設定ファイルをコンテナにコピー
COPY ./supervisord.conf /etc/supervisor/supervisord.conf

# supervisorの設定ファイルをデフォルトのログファイルのパスにコピー
RUN mkdir -p /var/log/supervisor

# コンテナのエントリーポイントとしてsupervisorを指定
CMD ["/usr/bin/supervisord"]
```

`supervisord.conf`はsupervisorの設定ファイルで、アプリケーションの設定に合わせて中身を変更します。

## 設定ファイル作成
では`supervisord.conf`ファイルを作成し、管理するプロセスの設定を追加していきましょう。
今回はタイトルの通りDelayedJobを管理します。

```supervisord.conf
# UNIXドメインソケットでリッスンするHTTPサーバーの構成パラメータを挿入。
# 本セクションが無い場合HTTPサーバーが起動しないので注意。
[unix_http_server]
file=/var/run/supervisor.sock
chmod=0700

# supervisordプロセスに関連するグローバル設定を挿入
# supervisordプロセスログを指定ファイルに出力
[supervisord]
nodaemon=true
loglevel=info
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid
user=root

# supervisordサーバーと通信するためのUNIXドメインソケットファイルを指定
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock

# supervisordがdelayed_jobを起動して制御するための設定
[program:delayed_job]
command=bundle exec bin/delayed_job run RAILS_ENV=production
startretries=5
startsecs=5
numprocs=1
autostart=true
autorestart=true
stdout_logfile=/var/log/delayed_job_from_supervisor.log
stderr_logfile=/var/log/delayed_job_error_from_supervisor.log
user=root
```

各セクションに設定している値の詳細については[supervisorドキュメント](http://supervisord.org/introduction.html)をご覧いただき、環境に合わせて適切な値を設定してください。
※コメント(#から始まる文)は説明のために記述しているだけですので、設定ファイルからは削除してください。

# 検証
それではsupervisorを起動してDelayedJobを死活監視してくれるかどうかを検証してみましょう。
Dockerfileを起動すると`CMD ["/usr/bin/supervisord"]`の記述によりsupervisorが起動します。

## DelayedJob起動確認

Docker内で以下コマンドを実行します。

```
supervisorctl status
```

出力結果からDelayedJobが`起動(RUNNING)`していることがわかりますね。
もし他にも監視対象のプロセスを設定していれば、続けて他プロセスの状態も表示されます。

```
delayed_job                      RUNNING   pid 83, uptime 0:02:30
```

## RDS再起動

ではsupervisorがDelayedJobを再起動してくれることを確認するために、AWSコンソールから意図的にRDSを再起動(停止)させてみます。

## もう一度DelayedJob起動確認

RDSが再起動中になり、すこし経ってからDelayedJobの状態を確認します。

```
supervisorctl status
```

ちょうど再起動を実行中のようです。

```
delayed_job                      STARTING
```

さらに数秒経ってから確認すると、、、無事起動できたようです！
pid、起動時間(uptime)が変更されていることことから再起動が完了したことが確認できます。

```
delayed_job                      RUNNING   pid 233, uptime 0:01:31
```

# まとめ
今回はsupervisorを使用してDelayedJobの死活監視＆再起動をするための設定をしました。
システムを運用していると、思わぬことから事故が発生することがあります。
ぜひ皆さんもsupervisorを導入して対策をしていきましょう。

ここまで読んでくださりありがとうございました！