web-dev-qa-db-ja.com

UNIXで任意のスクリプトをデーモン化するにはどうすればよいですか?

任意の汎用スクリプトまたはコマンドを daemon に変換できるデーモン化ツールが欲しい。

対処したい2つの一般的なケースがあります。

  1. 永久に実行するスクリプトがあります。死ぬ(または再起動する)場合は、再起動します。一度に2つのコピーが実行されないようにしてください(コピーが既に実行されているかどうかを検出し、その場合は起動しないでください)。

  2. シンプルなスクリプトまたはコマンドラインコマンドがあり、それを永久に繰り返し実行したい(実行の間に短い休止を入れて)。繰り返しますが、スクリプトの2つのコピーが一度に実行されることを許可しないでください。

もちろん、ケース2のスクリプトの周りに「while(true)」ループを記述してからケース1の解決策を適用するのは簡単ですが、ケース1のスクリプトに適用されるため、より一般的な解決策はケース2を直接解決しますスクリプトが死ぬことを意図していない場合は、短くするか、一時停止しないでください(もちろん、スクリプトが本当にdoes一時停止は実際には重要ではありません))。

ソリューションには、たとえば、ファイルロックコードまたはPID記録を既存のスクリプトに追加することを含めないでください。

より具体的には、次のように実行できるプログラムを「デーモン化」したい

% daemonize myscript arg1 arg2

または、たとえば、

% daemonize 'echo `date` >> /tmp/times.txt'

times.txtに追加される日付のリストが増え続けます。 (デーモン化する引数が上記のケース1のように永久に実行されるスクリプトである場合、デーモン化は依然として正しいことを行い、必要に応じて再起動することに注意してください。)上記のようなコマンドを.loginに入れることができますおよび/または1時間ごとまたは1分ごとにcronを実行します(予期せずに死んでしまうのを心配しているかどうかによって異なります)。

注意:daemonizeスクリプトは、デーモン化するコマンド文字列を記憶する必要があるため、同じコマンド文字列が再度デーモン化された場合、2番目のコピーは起動しません。

また、このソリューションはOS XとLinuxの両方で理想的に機能するはずですが、どちらか一方のソリューションは大歓迎です。

編集:Sudo daemonize myscript myargsで呼び出す必要がある場合は問題ありません。

(これをすべて間違っていると思っている場合や、手早く汚い部分的な解決策がある場合は、それも聞きたいです。)


PS:役に立つ場合は、 here's Pythonに固有の同様の質問です。

this 同様の質問への回答には、任意のスクリプトをすばやく悪用するための便利なイディオムと思われるものがあります。

91
dreeves

Nohupと&演算子を使用して、Unixの実行可能ファイルをデーモン化できます。

Nohup yourScript.sh script args&

Nohupコマンドを使用すると、スクリプトを強制終了せずにシェルセッションをシャットダウンできます。&は、スクリプトをバックグラウンドに配置して、セッションを続行するシェルプロンプトを取得します。これに関する唯一の小さな問題は、標準出力と標準エラーの両方が./Nohup.outに送信されるためです。より良いコマンドは次のとおりです。

Nohup yourScript.sh script args >script.out 2>script.error&

これにより、選択したファイルに標準出力が送信され、選択した別のファイルに標準エラーが送信されます。標準出力と標準エラーの両方に1つのファイルのみを使用する場合は、次の方法を使用できます。

Nohup yourScript.sh script args >script.out 2>&1 &

2>&1は、標準エラー(ファイル記述子2)を標準出力(ファイル記述子1)と同じファイルにリダイレクトするようシェルに指示します。

コマンドを一度だけ実行し、コマンドが死んだ場合に再起動するには、次のスクリプトを使用できます。

#!/bin/bash

if [[ $# < 1 ]]; then
    echo "Name of pid file not given."
    exit
fi

# Get the pid file's name.
PIDFILE=$1
shift

if [[ $# < 1 ]]; then
    echo "No command given."
    exit
fi

echo "Checking pid in file $PIDFILE."

#Check to see if process running.
PID=$(cat $PIDFILE 2>/dev/null)
if [[ $? = 0 ]]; then
    ps -p $PID >/dev/null 2>&1
    if [[ $? = 0 ]]; then
        echo "Command $1 already running."
        exit
    fi
fi

# Write our pid to file.
echo $$ >$PIDFILE

# Get command.
COMMAND=$1
shift

# Run command until we're killed.
while true; do
    $COMMAND "$@"
    sleep 10 # if command dies immediately, don't go into un-ctrl-c-able loop
done

最初の引数は、使用するpidファイルの名前です。 2番目の引数はコマンドです。そして、他のすべての引数はコマンドの引数です。

このスクリプトにrestart.shという名前を付けると、次のようになります。

Nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 &
88
Robert Menteer

長い回答をおaびします(私の回答が仕様にどのように影響するかについてのコメントをご覧ください)。私は包括的になろうとしているので、あなたは可能な限り脚を上げています。 :-)

プログラムをインストールでき(ルートアクセスがあり)、デーモンを実行するためのスクリプトをセットアップするために1回限りのレッグワークを実行する場合(つまり、コマンドラインで実行するコマンドライン引数を指定するだけでなく、しかし、サービスごとに1回だけ行う必要があります)、より堅牢な方法があります。

daemontools を使用する必要があります。投稿の残りでは、daemontoolsを使用してサービスをセットアップする方法について説明します。

初期設定

  1. daemontoolsのインストール方法 の指示に従ってください。一部のディストリビューション(Debian、Ubuntuなど)には既にパッケージがありますので、それを使用してください。
  2. /serviceというディレクトリを作成します。インストーラーは既にこれを実行しているはずですが、確認するか、手動でインストールする場合のみです。この場所が気に入らない場合は、svscanbootスクリプトで変更できますが、ほとんどのdaemontoolsユーザーは/serviceの使用に慣れており、使用しないと混乱します。
  3. Ubuntuまたは標準のinitを使用しない(つまり、/etc/inittabを使用しない)別のディストリビューションを使用している場合、プリインストールされたinittabを使用する必要がありますsvscanbootによって呼び出されるinitを配置するためのベース。難しいことではありませんが、OSが使用するinitの設定方法を知る必要があります。 svscanbootは、svscanを呼び出すスクリプトです。これは、サービスを探す主な作業を行います。 initから呼び出されるため、initは何らかの理由で死んだ場合に再起動するように手配します。

サービスごとのセットアップ

  1. 各サービスには、サービスに関するハウスキーピング情報を格納するserviceディレクトリが必要です。また、これらのサービスディレクトリがすべて1か所に収まるように、これらのサービスディレクトリを格納する場所を作成することもできます。通常、/var/lib/svscanを使用しますが、新しい場所は問題ありません。
  2. 私は通常 スクリプト を使用してサービスディレクトリを設定し、手作業の繰り返し作業を大幅に節約します。例えば。、

    Sudo mkservice -d /var/lib/svscan/some-service-name -l -u user -L loguser "command line here"
    

    ここで、some-service-nameはサービスに付ける名前、userはそのサービスを実行するユーザー、loguserはロガーを実行するユーザーです。 (ロギングについては少し説明しています。)

  3. サービスは、フォアグラウンドで実行する必要があります。プログラムがデフォルトでバックグラウンドになっているが、それを無効にするオプションがある場合は、そうします。プログラムをバックグラウンドで無効にする方法がない場合は、 fghack を読んでください。ただし、これにはトレードオフがあります。svcを使用してプログラムを制御することはできません。
  4. runスクリプトを編集して、希望どおりに動作することを確認します。サービスが頻繁に終了すると予想される場合は、sleep呼び出しを上部に配置する必要がある場合があります。
  5. すべてが正しく設定されたら、/serviceにサービスディレクトリを指すシンボリックリンクを作成します。 (サービスディレクトリを/service内に直接配置しないでください。svscanのウォッチからサービスを削除するのが難しくなります。)

ロギング

  1. Daemontoolsのロギング方法は、サービスに標準出力(またはmkserviceで生成されたスクリプトを使用している場合は標準エラー)にログメッセージを書き込ませることです。 svscanは、ログメッセージをログサービスに送信します。
  2. ロギングサービスは、標準入力からログメッセージを取得します。 mkserviceによって生成されたログサービススクリプトは、log/mainディレクトリに自動ローテーションされたタイムスタンプ付きログファイルを作成します。現在のログファイルの名前はcurrentです。
  3. ロギングサービスは、メインサービスとは無関係に開始および停止できます。
  4. tai64nlocal を介してログファイルをパイピングすると、タイムスタンプが人間が読める形式に変換されます。 (TAI64Nは、ナノ秒カウントの64ビットのアトミックタイムスタンプです。)

制御サービス

  1. svstat を使用して、サービスのステータスを取得します。ロギングサービスは独立しており、独自のステータスを持っていることに注意してください。
  2. svc を使用して、サービスを制御(開始、停止、再起動など)します。たとえば、サービスを再起動するには、svc -t /service/some-service-name;を使用します。 -tは、「SIGTERMを送信」を意味します。
  3. 使用可能なその他の信号には、-hSIGHUP)、-aSIGALRM)、-1SIGUSR1)、-2SIGUSR2)、および-kSIGKILL)。
  4. サービスを停止するには、-dを使用します。サービスディレクトリにdownという名前のファイルを作成することにより、起動時にサービスが自動的に開始しないようにすることもできます。
  5. サービスを開始するには、-uを使用します。これは、以前にダウンした(または自動起動しないように設定した)場合を除き、必要ありません。
  6. スーパーバイザーに終了するように依頼するには、-x;を使用します。通常、-dとともに使用して、サービスも終了します。これはサービスの削除を許可する通常の方法ですが、最初に/serviceからサービスのリンクを解除する必要があります。そうしないと、svscanがスーパーバイザーを再起動します。また、ロギングサービス(mkservice -l)を使用してサービスを作成した場合は、サービスディレクトリを削除する前にロギングスーパーバイザー(たとえば、svc -dx /var/lib/svscan/some-service-name/log)も終了することを忘れないでください。

概要

長所:

  1. daemontoolsは、サービスを作成および管理するための防弾方法を提供します。私は自分のサーバーにそれを使用し、私はそれを強くお勧めします。
  2. サービスの自動再起動機能と同様に、そのロギングシステムは非常に堅牢です。
  3. 記述/調整したシェルスクリプトでサービスを開始するため、好きなようにサービスを調整できます。
  4. 強力なサービス制御ツール:ほとんどの信号をサービスに送信でき、サービスを確実に上下させることができます。
  5. サービスには、クリーンな実行環境が保証されています:initが提供するものと同じ環境、プロセス制限などで実行されます。

短所:

  1. 各サービスには少しセットアップが必要です。ありがたいことに、これはサービスごとに一度だけ行う必要があります。
  2. フォアグラウンドで実行されるようにサービスを設定する必要があります。また、最良の結果を得るには、syslogやその他のファイルではなく、標準出力/標準エラーに記録するように設定する必要があります。
  3. Daemontoolsのやり方に慣れていないなら、急な学習曲線。 svcを使用してサービスを再起動する必要があり、実行スクリプトを直接実行することはできません(これらのサービスはスーパーバイザーの制御下にないため)。
  4. 多くのハウスキーピングファイル、および多くのハウスキーピングプロセス。各サービスには独自のサービスディレクトリが必要で、各サービスは1つのスーパーバイザープロセスを使用して、サービスが停止した場合にサービスを自動再起動します。 (多くのサービスがある場合は、プロセステーブルにlotsof superviseプロセスが表示されます。)

バランスとして、daemontoolsはあなたのニーズに最適なシステムだと思います。設定と保守の方法に関する質問は歓迎します。

32

daemonize をご覧ください。 2番目のコピーを検出できます(ただし、ファイルロックメカニズムを使用します)。また、異なるUNIXおよびLinuxディストリビューションで動作します。

アプリケーションをデーモンとして自動的に起動する必要がある場合は、適切なinit-scriptを作成する必要があります。

次のテンプレートを使用できます。

#!/bin/sh
#
# mydaemon     This Shell script takes care of starting and stopping
#               the <mydaemon>
#

# Source function library
. /etc/rc.d/init.d/functions


# Do preliminary checks here, if any
#### START of preliminary checks #########


##### END of preliminary checks #######


# Handle manual control parameters like start, stop, status, restart, etc.

case "$1" in
  start)
    # Start daemons.

    echo -n $"Starting <mydaemon> daemon: "
    echo
    daemon <mydaemon>
    echo
    ;;

  stop)
    # Stop daemons.
    echo -n $"Shutting down <mydaemon>: "
    killproc <mydaemon>
    echo

    # Do clean-up works here like removing pid files from /var/run, etc.
    ;;
  status)
    status <mydaemon>

    ;;
  restart)
    $0 stop
    $0 start
    ;;

  *)
    echo $"Usage: $0 {start|stop|status|restart}"
    exit 1
esac

exit 0
12
uthark

start-stop-daemon(8) を試してみたいと思うかもしれません。 /etc/init.d Linuxディストリビューションの例では。起動されたプロセスは、呼び出されたコマンドラインまたはPIDファイルで検出できるため、スクリプトのウォッチドッグであることを除き、すべての要件に一致します。ただし、必要に応じてスクリプトを再起動するだけの別のデーモンウォッチドッグスクリプトをいつでも開始できます。

11
Alex B

すでに説明したdaemonizeおよびdaemontoolsの代替として、libslackパッケージの daemon コマンドがあります。

daemonは非常に構成可能であり、自動再起動、ロギング、またはpidfile処理などの退屈なデーモンのすべてを気にします。

7
jefz

特にOS Xを使用している場合は、launchdの動作を確認することをお勧めします。スクリプトが実行されていることを自動的に確認し、必要に応じて再起動します。また、あらゆる種類のスケジューリング機能などが含まれます。要件1と2の両方を満たす必要があります。

スクリプトのコピーを1つだけ実行できるようにするには、PIDファイルを使用する必要があります。通常、現在実行中のインスタンスのPIDを含むファイルを/var/run/.pidに書き込みます。プログラムの実行時にファイルが存在する場合、ファイル内のPIDが実際に実行されているかどうかを確認します(プログラムがクラッシュしたか、PIDファイルの削除を忘れた可能性があります)。存在する場合、中止します。そうでない場合は、実行を開始してPIDファイルを上書きします。

5
Kamil Kisiel

Daemontools( http://cr.yp.to/daemontools.html )は、dj bernsteinによって作成された、これを行うために使用されるかなりハードコアなユーティリティのセットです。私はこれを使って成功しました。面倒な部分は、スクリプトを実行しても目に見える結果が返されないことです-目に見えない戻りコードだけです。しかし、一度実行されると防弾になります。

5

最初にcreateDaemon()http://code.activestate.com/recipes/278731/ から取得します

次に、メインコード:

import subprocess
import sys
import time

createDaemon()

while True:
    subprocess.call(" ".join(sys.argv[1:]),Shell=True)
    time.sleep(10)
3
Douglas Leeder

Monit を試すこともできます。 Monitは、他のサービスを監視および報告するサービスです。主に実行時の問題を(電子メールとsmsを介して)通知する方法として使用されますが、ここで提案されている他のほとんどの提案を実行することもできます。プログラムの自動(再)起動と停止、電子メールの送信、他のスクリプトの開始、および取得可能な出力のログの維持が可能です。さらに、ドキュメントが充実しているため、インストールと保守が簡単であることがわかりました。

1
Adestin

これは、空のディレクトリにコピーして試すことができる作業バージョンです(CPAN依存関係をインストールした後、 Getopt :: LongFile :: SpecFile :: Pid 、および IPC :: System :: Simple -すべてが非常に標準的であり、すべてのハッカーに強くお勧めします:一度にインストールできますcpan <modulename> <modulename> ...)。


keepAlive.pl:

#!/usr/bin/Perl

# Usage:
# 1. put this in your crontab, to run every minute:
#     keepAlive.pl --pidfile=<pidfile> --command=<executable> <arguments>
# 2. put this code somewhere near the beginning of your script,
#    where $pidfile is the same value as used in the cron job above:
#     use File::Pid;
#     File::Pid->new({file => $pidfile})->write;

# if you want to stop your program from restarting, you must first disable the
# cron job, then manually stop your script. There is no need to clean up the
# pidfile; it will be cleaned up automatically when you next call
# keepAlive.pl.

use strict;
use warnings;

use Getopt::Long;
use File::Spec;
use File::Pid;
use IPC::System::Simple qw(system);

my ($pid_file, $command);
GetOptions("pidfile=s"   => \$pid_file,
           "command=s"   => \$command)
    or print "Usage: $0 --pidfile=<pidfile> --command=<executable> <arguments>\n", exit;

my @arguments = @ARGV;

# check if process is still running
my $pid_obj = File::Pid->new({file => $pid_file});

if ($pid_obj->running())
{
    # process is still running; nothing to do!
    exit 0;
}

# no? restart it
print "Pid " . $pid_obj->pid . " no longer running; restarting $command @arguments\n";

system($command, @arguments);

example.pl:

#!/usr/bin/Perl

use strict;
use warnings;

use File::Pid;
File::Pid->new({file => "pidfile"})->write;

print "$0 got arguments: @ARGV\n";

これで、上記の例を呼び出すことができます:./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3とファイルpidfileが作成され、出力が表示されます。

Pid <random number here> no longer running; restarting ./example.pl 1 2 3
./example.pl got arguments: 1 2 3
1
Ether

不滅 これは* nixクロスプラットフォーム(OSに依存しない)スーパーバイザーです。

MacOSを簡単に試すには:

brew install immortal

ポートから FreeBSD を使用している場合、またはpkgを使用している場合:

pkg install immortal

Linux の場合、プリコンパイル済みバイナリをダウンロードするか、ソースから: https://immortal.run/source/

次のように使用できます。

immortal -l /var/log/date.log date

または、より多くのオプションを提供する configuration YAML ファイルにより、例えば:

cmd: date
log:
    file: /var/log/date.log
    age: 86400 # seconds
    num: 7     # int
    size: 1    # MegaBytes
    timestamp: true # will add timesamp to log

標準エラー出力も別のファイルに保存したい場合は、次のようなものを使用できます。

cmd: date
log:
    file: /var/log/date.log
    age: 86400 # seconds
    num: 7     # int
    size: 1    # MegaBytes
stderr:
    file: /var/log/date-error.log
    age: 86400 # seconds
    num: 7     # int
    size: 1    # MegaBytes
    timestamp: true # will add timesamp to log
1
nbari

その他の回答 について一連の改善を行いました。

  1. このスクリプトの標準出力は、コマンドが既に実行されていることを検出するために終了しない限り、純粋にその子からの標準出力で構成されます
  2. 終了時にpidファイルの後にクリーンアップします
  3. オプションの構成可能なタイムアウト期間(正の数値引数を受け入れ、sleepに送信)
  4. -hの使用プロンプト
  5. 単一のコマンド実行ではなく、任意のコマンド実行。最後の引数OR残りの引数(最後の引数が複数ある場合))はevalに送信されるため、任意の種類のシェルスクリプトをこのスクリプトに送信する文字列として作成できます。デーモン化する最後の引数(または末尾の引数)として
  6. -ltの代わりに<で行われた引数カウント比較

スクリプトは次のとおりです。

#!/bin/sh

# this script builds a mini-daemon, which isn't a real daemon because it
# should die when the owning terminal dies, but what makes it useful is
# that it will restart the command given to it when it completes, with a
# configurable timeout period elapsing before doing so.

if [ "$1" = '-h' ]; then
    echo "timeout defaults to 1 sec.\nUsage: $(basename "$0") sentinel-pidfile [timeout] command [command arg [more command args...]]"
    exit
fi

if [ $# -lt 2 ]; then
    echo "No command given."
    exit
fi

PIDFILE=$1
shift

TIMEOUT=1
if [[ $1 =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
        TIMEOUT=$1
        [ $# -lt 2 ] && echo "No command given (timeout was given)." && exit
        shift
fi

echo "Checking pid in file ${PIDFILE}." >&2

#Check to see if process running.
if [ -f "$PIDFILE" ]; then
    PID=$(< $PIDFILE)
    if [ $? = 0 ]; then
        ps -p $PID >/dev/null 2>&1
        if [ $? = 0 ]; then
            echo "This script is (probably) already running as PID ${PID}."
            exit
        fi
    fi
fi

# Write our pid to file.
echo $$ >$PIDFILE

cleanup() {
        rm $PIDFILE
}
trap cleanup EXIT

# Run command until we're killed.
while true; do
    eval "$@"
    echo "I am $$ and my child has exited; restart in ${TIMEOUT}s" >&2
    sleep $TIMEOUT
done

使用法:

$ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/'
Checking pid in file pidfilefortesting.
azzzcd
I am 79281 and my child has exited; restart in 0.5s
azzzcd
I am 79281 and my child has exited; restart in 0.5s
azzzcd
I am 79281 and my child has exited; restart in 0.5s
^C

$ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/' 2>/dev/null
azzzcd
azzzcd
azzzcd
^C

このスクリプトを異なるディレクトリから実行する場合、異なるpidfileを使用し、既存の実行中のインスタンスを検出しない可能性があることに注意してください。引数を介して提供される一時的なコマンドを実行および再起動するように設計されているため、何かがすでに開始されているかどうかを知る方法はありません。それは同じコマンドであるかどうかだからです。何かの単一のインスタンスのみを実行するというこの施行を改善するには、状況に固有のソリューションが必要です。

また、適切なデーモンとして機能するためには、他の回答で言及されているように(最低限)Nohupを使用する必要があります。プロセスが受信する可能性のある信号に回復力を提供する努力はしていません。

注意すべきもう1つの点は、このスクリプトを(強制終了された別のスクリプトから、またはシグナルで呼び出された場合に)強制終了すると、特にその子がまだ別の場合スクリプト。これがなぜなのかはわかりませんが、evalの動作に関連するもののようです。したがって、その行を、他の回答のように単一のコマンドのみを受け入れるものに置き換えるのが賢明かもしれません。

0
Steven Lu