今回はcrontabでforeverコマンドを永続化する方法について紹介します。
nodeモジュールやPythonのプロセスを永続化できるforeverコマンドですが、プログラムでエラーが発生した場合には停止してしまうケースがあります。
 プロセスが停止してしまうとサーバーにアクセスして再起動するしかないので手間がかかります。
プロセスが落ちていた場合に自動で再起動することができればサーバーにアクセスする手間が減るだけでなく、システムの安定性も上がります。
foreverコマンドをcrontabで監視する方法
◆今回使用した環境
 サーバー:Xサーバー
 作業PC:MacBook Pro
起動処理を実装する
forever listでプロセスの状態を確認し、プロセスが落ちていた場合やストップしていた場合は再起動をかけます。
 プロセスの状態としては主に「正常」、「異常終了」、「プロセスなし」があります。
正常な場合
 プロセスが正常な場合はuptimeが表示されています。
data: uid command script forever pid id logfile uptime
data: [0] xxxx python sample.py 84104 84107 /home/user/.forever/xxxx.log 0:2:25:38.169
プログラムが異常終了した場合
 異常終了した場合はuptimeに「STOPPED」が表示されます。
data: uid command script forever pid id logfile uptime
data: [0] xxxx python sample.py 84104 84107 /home/user/.forever/xxxx.log STOPPED
プロセスが落ちた場合
 プロセスが落ちた場合はリストからプロセスが消えます。
info: No forever processes running
よってこの3つが切り分けができれば状態を見て再起動をかけることが可能となります。
今回作成した処理
・crontabでシェルスクリプトを起動
 ・シェルスクリプトからPythonを起動
 ・異常終了していた場合やプロセスが落ちていた場合はPythonからforeverコマンドを起動
という流れになっています。
 Pythonかスクリプト一本にしたかったのですが、シェルスクリプトの判定が思いの外わかりにくかったのでPythonで記述することにしました。
 環境変数はPythonで読み込もうとすると面倒な処理が必要となるのでシェルスクリプトで読み込んでから記述しています。
◆crontabの記述
 15分ごとに監視する場合の記述例です。
◆スクリプトの記述
source /home/user/.bash_profile
source /home/user/.bash_profile
# Pythonのスクリプトを読み込む
python3 /home/user/project/sample.py
Pythonの記述
◆起動する為のモジュール(sample.py)
 スクリプトのあるディレクトリは環境変数「PROJECT_DIR」として予め定義しておいてください。
# モジュールを読み込む
from filereadwrite import FileReadWrite
import re
import os
import datetime
forever_st = 'ok'
# 正規表現の定義
uptime_st = re.compile('^(.*)STOPPED(.*)$')
forever_process = re.compile('^(.*)No forever processes running$')
# 環境変数からプロジェクトディレクトリを読み込む
project_dir = os.environ['PROJECT_DIR']
# ファイルの絶対パスを定義
forever_list = project_dir + 'forever_list.txt'
crontab_log = project_dir + 'crontab.log'
crontab_log = project_dir + 'sample.py'
now = datetime.datetime.today().strftime('%Y/%m/%d %H:%S')
# foreverの状態を書き出す
os.system('forever list > %s' % forever_list)
fw = FileReadWrite()
text_data = fw.file_read(forever_list)
text_lines = text_data.split('n')
for text in text_lines:
    if forever_process.match(text):
        forever_st = 'ng'
        break
    elif uptime_st.match(text):
        forever_st = 'ng'
        break
# 起動していない場合は起動のみ
if forever_st == 'ng':
    os.system(forever -c ptyhon3 sample.py)
    text = 'forever process stopped. try boot now.n'
else:
    text = 'skipn'
fw.file_write(crontab_log, '%s: %s' % (now, text), 'a')
◆ファイルに書き込む為のモジュール
 ◆記述内容(filereadwrite.py)
 実装する内容は以下の通りです。
import codecs
# ファイルのリードライトを行うクラス
class FileReadWrite:
    # ファイルを書き込むメソッド
    def file_write(self, path, text):
        try:
                         # ファイルを開く(挿入モード、UTF-8)
            target_file = codecs.open(path, "a", "utf_8")
            # ファイルに記載する
            target_file.write(text)
            result = "ok"
            # ファイルを閉じる
            target_file.close()
        except:
            # エラーが出た時の例外処理
            result = "ng"
        return result
# クラスのテストコード(ファイル書き込み)
def main():
    c = FileReadWrite()
    answer =  c.file_write("./test1.txt", "かきくけこn")
    print(answer)
if __name__ == "__main__":
    main()
こちらのモジュールの使い方は「Pythonでファイルの読み書きを制御する」にて説明しています。
以上で設定は完了です。
foreverコマンドをcrontabで監視する時に詰まりやすい点
詰まりやすい点というか私が詰まった点です(笑)
環境変数の定義
crontabでは、環境変数をほとんど読み込みません。よってPythonなどのPATH指定などプログラム中の環境変数は改めて読み込ませる必要があります。
環境変数を読み込む方法としてはcrontabに書き込む方法と起動スクリプトに書き込む方法があります。
◆crontabに書き込む方法
 .bash_profileや.bashrcに書かれているものを転記します。
 exportの記述は不要で、
のように記述します。また、
のようにコマンドとして記述することも可能です。この記述をする場合はexportをつけます。
 セミコロンで区切ることで複数のコマンドを書くことも可能です。
◆スクリプトに書き込む方法
 スクリプト中でsourceコマンドによる読み込むを行います。([user名]にはuserディレクトリを入れてください)
source /home/[user名]/.bashrc
crontabに環境変数を書き込むとごちゃごちゃして見にくくなります。特にパスを通したい場合などは指定したい環境変数も多くなってしまいます。crontabに定義する環境変数は必要最低限にしてスクリプトで読み込むようにした方が良いでしょう。
パスの指定
crontabはルートディレクトリより実行する為、プログラム中で相対パスを多用していると止まる可能性があります。
 ファイルを出力する位置にも注意が必要です。
相対パスを絶対パスに変更するか、プログラムの先頭に「cdコマンド」を記述しておきアクセスするパスを明確にしておきましょう。
まとめ
foreverコマンドは自作プログラムを永続化できる便利なコマンドではありますが、一度停止してしまうと自力で起動ができません。crontabなどの他のツールを活用して監視することはforeverコマンドを運用していく上では必須となります。
停止した場合は自動で再起動をするようにして起動の手間を減らすと共にシステムの安定性の向上を目指していきましょう。
おすすめ記事Python入門者のための100日勉強方法






