Supervisorで簡単にデーモン化

## やりたいこと

常時起動させたいスクリプトなど、デーモンプロセスとして動かしたいことがあると思います。
しかし例えば/etc/init.dのスクリプトなどを自分で書くとなると・・・

* PIDファイルの管理をいちいち書くのが面倒でミスりやすい。
* ステータスの取得とかも同様。
* そもそもそんなローレベルのツールを使わなくてもよい。もっとユーザレベルに近いもので充分。
* 一般ユーザで起動停止くらいさせたい。

つまりは、大体同じ様になるはずの **「プロセス管理スクリプト書くのが面倒」** なのと、
サービスとして登録してしまうと **「基本はrootで起動停止管理」** になるので、
この２つを避けたいということになります。


systemdを使うとスクリプトを書く必要はなくなりますが、

* rootでなければ起動停止ができない、という扱いにしたくない。
* stdout/errに色々吐きまくって直したくない。
* **システムのサービス扱いではなくカジュアル起動停止させたい。**

といった場合にも需要があるかと思います。

## deamonize tool

この手の問題にはdaemon化のツールを使うことになります。
daemontoolsとかforeverとか。
ここではpythonで実装されている「supervisor」を使ってみます。安定性もよいので。

* **2〜3年ほど3.X系バージョンで運用しましたがsupervisordそのものが落ちたことはありません。**

## supervisor

* プロセス管理/デーモン化のツール。
* コンフィグちょっと書くだけで簡単にデーモンプロセスの生成/管理が可能。
* 本家のドキュメントはこちら
  * http://supervisord.org/

## 試しに

* 私はメトリクスのグラフ化にGrowthForecastとHRForecastを使用させてもらってますので、試しにこれらをデーモン化してみます。
  * GrowthForecast http://kazeburo.github.io/GrowthForecast/
  * HRForecast http://blog.nomadscafe.jp/2013/02/hrforecast--.html
* この時メインで試したOSはCentOS6.4です。

## インストール

方法は大きく2通り。

1. EPELリポジトリからインストール
2. pythonパッケージマネージャ(pip/easy_install)を使う。

1の方法ではバージョンが古いことがあります。
最新バージョンを使いたい場合には2の方法を使います。

* 特にバージョン2.X系を使うよりは様々な機能が追加された3.X系を使うべきでしょう。
* 古いバージョンだと例えばstopasgroup(stopの際にchild含むプロセスグループをkillする)が使えなかったりします。

#### 1. EPELからインストールする場合

* supervisord本体及び、supervisordの管理スクリプトが/etc/init.dに入り、serviceコマンドで管理できます。

```bash
yum install supervisor
```

* (CentOSではEPELリポジトリはデフォルトでは有効化されていないので、登録してない場合は事前にこちらを。)

```bash
rpm --import http://apt.sw.be/RPM-GPG-KEY.dag.txt
rpm -ivh http://apt.sw.be/redhat/el6/en/x86_64/rpmforge/RPMS/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm
```

* CentOS7系でも同じような手順を踏むと思われます。その場合supervisord本体の管理をsystemdで行えるようになるでしょう。

#### 2. pip/easy_installを使う。

pip/easy_installなどのpythonパッケージマネージャからインストールすると、
EPELリポジトリからのインストールとは異なりデフォルトではsupervisord自体のサービス管理は含まれません。
そちらは手動でやっておくと良いでしょう。

* supervisordのインストール

```bash
sudo easy_install supervisor

# または

sudo pip install supervisor
```

そもそもpythonパッケージマネージャを入れていない場合、
easy_installを使うには`sudo yum install python-setuptools`を、
pipを使うには、easy_installを入れた後`sudo easy_install pip`を実行しておきましょう。

* デフォルトコンフィグファイル生成

EPELリポジトリインストールでは自動的に/etc/supervisord.confに配置されますが、これを手動生成します。

```bash
sudo echo_supervisord_conf > /etc/supervisord.conf
```

* includeコンフィグ用のディレクトリ作成

EPELリポジトリインストールでは自動的に/etc/supervisord.dが生成されていますので、これを作っておきます。

```bash
sudo mkdir /etc/supervisord.d
```

* supervisordログファイル

supervisord本体が吐くログファイル周りの調整を行います。
デフォルトのコンフィグでは/tmp/supervisord.logなので、
/etc/supervisord.confを修正し、/var/log/supervisor/supervisord.logにします。

```ini
[supervisord]
# ログディレクトリ
;logfile=/tmp/supervisord.log
logfile=/var/log/supervisor/supervisord.log
```

ログ出力先のディレクトリを作成し、ついでにlog rotationの設定も行っておきます。

```bash
sudo mkdir /etc/supervisord.d
sudo sh -c "echo '/var/log/supervisor/*.log {
       missingok
       weekly
       notifempty
       nocompress
}' > /etc/logrotate.d/supervisor"
```

* pid, includeの設定

こちらも/etc/supervisord.confを修正しておきます。
pidファイルは/var/run/以下に生成するようにし、
/etc/supervisord.d/以下の設定ファイルをincludeできるようにします。

```ini
[supervisord]
# ...中略
# pidファイル
;pidfile=/tmp/supervisord.pid
pidfile=/var/run/supervisord.pid

# ...中略

# includeセクションがコメントアウトされているので、コメントインして下記の用に修正。
[include]
files = supervisord.d/*.ini
```

* supervisord本体のシステムサービス登録(CentOS6系)

initスクリプトは作成してくれた方がいらっしゃるようで、ありがたく利用させてもらいましょう。
https://github.com/Supervisor/initscripts

何種類かのディストロに対応していますが、今回は下記を使います。

https://github.com/Supervisor/initscripts/blob/master/redhat-init-equeffelec

```bash

sudo curl -o /etc/rc.d/init.d/supervisord https://raw.githubusercontent.com/Supervisor/initscripts/master/redhat-init-equeffelec
sudo chmod 755 /etc/rc.d/init.d/supervisord
sudo chkconfig --add supervisord
```

* supervisord本体のシステムサービス登録(CentOS7系)

CentOS7ではsystemdに登録することになります。
systemdのunitファイルは下記を参考にして作ります。

https://github.com/zokeber/supervisor-systemd/blob/master/etc/systemd/system/supervisord.service

下記の内容で/etc/systemd/system/supervisord.serviceに格納します。

```text
[Unit]
Description=Supervisor process control system for UNIX
Documentation=http://supervisord.org
After=network.target

[Service]
ExecStart=/usr/bin/supervisord -n -c /etc/supervisord.conf
ExecStop=/usr/bin/supervisorctl $OPTIONS shutdown
ExecReload=/usr/bin/supervisorctl $OPTIONS reload
KillMode=process
Restart=on-failure
RestartSec=50s

[Install]
WantedBy=multi-user.target
```

作成後は、 `systemctl list-unit-files --type=service` でsystemdへの管理登録を確認し、試しに起動してみます。

```
systemctl start supervisord
systemctl status supervisord
systemctl stop supervisord
```

問題なければ自動起動に登録しておきます。

```
systemctl enable supervisord.service
```


## supervisordのコンフィグ

* /etc/supervisord.confを編集して、以下をコメントインします。

```ini
[inet_http_server]
port=127.0.0.1:9001

[supervisorctl]
serverurl=unix:///var/tmp/supervisor.sock
serverurl=http://127.0.0.1:9001
```

* webの管理コンソールを開放し、同時にroot以外のユーザからのクライアントコマンド許可を行っています。

## プロセス管理登録

自分がインストールしたsupervisorはversion 3系で、2系とはコンフィグが若干異なるようです。
特にログ出力の辺りで。(2系は古いので3系使いましょう)

以下、ジョブの標準出力ログ格納ディレクトリを`/var/log/supervisor/jobs`にしています。そのまま使う場合は予め作成しておきます。また、ローテーションの設定も以下は適当です。

* GrowthForecast登録の例
 
```ini
[program:GrowthForecast]
command=/usr/local/bin/growthforecast.pl --enable-float-number --data-dir /data/gf    ; 起動コマンド
user=monadmin  ; 起動ユーザ
autorestart=true  ; プロセスダウン時に自動再起動
stdout_logfile=/var/log/supervisor/jobs/GrowthForecast-supervisord.log ; 標準出力ログ
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=5
stdout_capture_maxbytes=1MB
redirect_stderr=true  ; エラー出力を標準出力にリダイレクト
```

これを/etc/supervisord.d/GrowthForecast.iniにでも作成しておきます。

* HRForecast登録の例。
  * /opt/HRForecastディレクトリにインストールしてあります。

```ini
[program:HRForecast]
command=perl hrforecast.pl --config config.pl  ; direcotryからの相対パスでOK。
user=monadmin
directory=/opt/HRForecast  ; インストールディレクトリがある場合にはこちらに。
autorestart=true
stdout_logfile=/var/log/supervisor/jobs/HRForecast-supervisord.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=5
stdout_capture_maxbytes=1MB
redirect_stderr=true
```

こちらも同じく/etc/supervisord.d/HRForecast.iniにでも作成しておきます。

## 起動確認

```bash
sudo service supervisor start
```

 /var/log/supervisor以下のログをチェックしておくといいでしょう。
今回の設定なら、設定したプロセス（GrowthForecastやHRForecast）のstdout/errも出てきてます。

起動後は、root以外からクライアントコマンドのsupervisorctlを叩いてみます。
単独で叩けば、コマンドラインの管理インタフェースに入れます。
で、成功していれば、以下の様に見えます。

```
[monadmin@myhost]% supervisorctl
GrowthForecast                   RUNNING    pid 24955, uptime 0:12:00
HRForecast                       RUNNING    pid 24956, uptime 0:12:00
supervisor>
```

CLIコンソール上からは、start|stop|restart [プロセス名]、
statusで管理プロセスの一覧が出てきます。

CLIコンソールに入らなくても、supervisorctl　{start|stop|restart|status} [プロセス名] などが可能です。

rootにならなくてもできるので、
ユーザレベルから管理できるデーモンが出来上がりました。

## 失敗していると

```
[monadmin@myhost]% supervisorctl
GrowthForecast                   FATAL      can't find command 'growthforecast.pl'
HRForecast                       RUNNING    pid 8136, uptime 0:00:29
supervisor>
```

起動失敗時にはステータスがFATALになります。
PATHの通ってないコマンドを書いちゃったのがこれでした。

## やってないこと＆その他

* セキュリティ関連を普通は考えます。userやpasswordを設定しましょう。
  * このままだと一般ユーザ全てから出来てしまうので。
* psコマンドでプロセスツリーを見てみると、rootのsupervisor配下に登録プロセスがぶら下がってるのが解ります。
  * ps auxwwfとかで見てみましょう。
* webコンソールも持っています。http://127.0.0.1:9001で見えます。
* 登録したプロセスが異常終了した場合にメールで通知させる。
  * 下記の通りやってみました。
  * [Supervisorのプロセス異常終了をメールで通知](http://qiita.com/yushin/items/6c698f084b92c7868b69)