バックグラウンドプロセスでスクリプトを適切に管理するには、どのようにシステム化する必要がありますか?

バックグラウンドプロセスでスクリプトを適切に管理するには、どのようにシステム化する必要がありますか?

私はバックグラウンドで3つのプログラムを実行し、フォアグラウンドでいくつかのプログラムを実行するシェルスクリプトを持っており、起動しtrapて失敗した場合に再起動できるwaitようにユニットファイルを設定しました。systemd

しかし、プロセスが終了すると、そのスクリプトのすべてのエントリが終了して再起動されるわけではないことがわかりました。このアプリケーションでは、どちらか一方が終了したら再起動する必要があります。

2つの合理的なパスがあります。

  1. ユニットファイルをハイブし、例外が検出され、すべての例外が終了するようにスクリプトを変更してから、スクリプトを再実行してください。私は何をすべきかわかりません。
  2. 3つのバックグラウンドプロセスをそれぞれ別々のファイルを持つ独自の単位で構成します.service。しかし、.service失敗したファイルの1つを終了して再起動するためにファイルを作成する方法がわかりません。私は彼らの依存関係を順番に始めるようにソートすることができることを知っていますが、#2が死んだときに#1を殺すか、その逆にする方法がわかりません。

私は管理者を書いたり、プログラムにそれを見つけて自分で終了させたりしたくありません。それが目的ですsystemd。私は正しい注文を見逃していることを願っています。

.サービスファイル:

[Unit]
Description=Foobar Interface
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/user/scripts
ExecStart=/home/user/scripts/myscript.sh
Restart=always

[Install]
WantedBy=multi-user.target

クンクンスクリプト:

#!/usr/bin/env bash

tty_port=/dev/ttyUSB0

#Clean up any old running processes
pkill -f "cat ${tty_port}"
pkill transport
pkill backgroundprogram

#Configure the target
source /home/user/somescript.sh
foregroundprogram

#Set up the serial port
stty -F $tty_port 115200 

#Read from the port in the background
cat $tty_port &
tty_pid=$!

#Wait for tty device to waken
sleep 15

#Send commands to tty device
echo "command1" > $tty_port
sleep 1
echo "command2" > $tty_port
sleep 1

#Start up the transport
/home/user/transport &>> /dev/null &
transport_pid=$!

#Wait a bit for the transport to start
sleep 1

#Start up the main process
/home/user/backgroundprogram &
background_pid=$!

#Wait a bit for it to start
sleep 1

#Finally, start the tty device
echo "command3" > $tty_port

trap "kill ${background_pid} ${tty_pid} ${transport_pid}; exit 1" INT
wait

これらの操作はすべてログに記録するのと同じように機能しますが、3つのプロセスのいずれかが失敗した場合は、すべてのプロセスを終了して再起動せずに実行を続けます。

答え1

しかし、プロセスが終了すると、そのスクリプトのすべてのエントリが終了して再起動されるわけではないことがわかりました。このアプリケーションでは、どちらか一方が終了したら再起動する必要があります。

systemd は、下付き文字ではなくシェルスクリプトを監視します。あなたはしません考えるsystemd は子プロセスの終了に応答します。これにより再起動されます。コマンドを実行するたびに。考えてみてください。実行されるシェルスクリプトがある場合...

date

子プロセスを作成して実行し、終了しました。これにより、プロセスの監督者が何らかの措置を講じたくない。

systemdに子プロセスを監視させるには、各プロセスごとに別々の単位ファイルを生成します。

  1. シリアルポートを設定して読み取るデバイス
  2. 一つのために/home/user/transport
  3. 一つのために/home/user/backgroundprogram

systemd依存関係を使用してサービスの正しい開始順序を確保し(1つのサービスを停止するとすべて停止することができます)、ディレクティブを使用してEnvironmentFileファイルから構成をロードできます。$tty_port

一部の設定コマンド(「ttyデバイスにコマンドを送信...」)を1行に入力するか、ExecStartPre独自のType=oneshotサービスを受けることもできます。

答え2

基本スクリプトを別のサービスに分割できる場合は、次のように簡単に解決できます。

以下の例では、s1、s2、s3 の 3 つの生成サービスがあり、ターゲット s.target を介してグループに制御します。

注:3つのサービスが次の
ように構成されている場合Requiresみんなこのグループに参加するプロセスが再開されます。
あるいは、s.targetで設定している場合は、Wantsそれらの1つがクラッシュして再生成されると、その個々のプロセスのみが再起動されます。




各サービスに対してサービスファイルs1、s2、s3を作成します。

/etc/systemd/system/s1.service:

[Unit]
Description=my worker s1
After=network.target
Before=foobar.service
PartOf=s.target

[Service]
Type=simple
ExecStart=/usr/local/bin/s1.sh
Restart=always

(注:サービスが同じ場合一つ [Eメール保護]複数のファイルの代わりにファイル。 @および%iを使用するサービスインスタンスについては、マニュアルを参照してください。 )


次に、s1、s2、およびs3サービスを必要とするデフォルトの宛先(グループ)ファイルを作成します。

/etc/systemd/system/s.target:

[Unit]
Description=main s service
Requires=s1.service s2.service s3.service
# or
# Wants=s1.service s2.service s3.service

[Install]
WantedBy=multi-user.target

完璧。
いつものように今走らなければなりませんsystemctl daemon-reload

これでサービスを開始し、systemctl start s.target
s1、s2、s3 を起動できるようになりました。

systemctl stop s.target
s1、s2、s3を停止して3つのサービスをすべて停止できます。

もちろん、通常どおり個々のサービスを開始/停止/再起動/状態設定することができます。
systemctl status s1

s1、s2、またはs3プロセスを終了すると、自動的に再生成されます(Restart = always)。
を使用すると、Requiresグループ内のすべてのプロセスが再開されます。

PS:systemctl enable s.target起動時にサービスを開始するには、次の手順を実行します。

PS:残念ながら、systemctlを使用しているときに「s1.service」全体を入力する代わりに、「s1」などの「s.target」に省略形「s」を使用することはできません。このグループを管理するには、「s.target」と入力する必要があります。

答え3

#!/usr/bin/env/python3
# POSIX shell and bash < 4.3 doesn't want to do this.
# https://unix.stackexchange.com/questions/285156/exiting-a-shell-script-if-certain-child-processes-exit
#
# If you haven't written python3 before, be aware the string type
# is Unicode (UTF-8).  Python 3.0 aborts on invalid UTF-8.
# Python 3.1 aims to round-trip invalid UTF-8 using "surrogateescape".
# Python 3.2 may accept non-UTF-8 encoding according to your locale.
# ...
#
# * Functions should be better tested.
#
# * Doesn't bother killing (and waiting for) child processes.
#   Assumes systemd does it for us.
#   Convenient, but I'm not 100% happy about it.
#
# * Otherwise direct translation of nasty script, e.g. use of "sleep".

import sys
import os
import time

tty_port = "/dev/ttyS0"  # or: tty_port = sys.environ["tty_port"]

def die(msg):
    sys.exit(msg)

# Run program in background
def bg(*argv):
    pid = os.fork()
    if pid == 0:
        # Child process: exec or die
        # Either way, we never return from this function.
        try:
            os.execvp(argv[0], argv)
        except Exception as e:
            # By convention, child always uses _exit()
            sys._exit(e)
        assert False
    return pid

def __fg(*argv):
    pid = bg(*argv)
    (_, status) = os.waitpid(pid, 0)
    return status

# Run program, wait for exit, die if the program fails
def fg(*argv):
    status = __fg(*argv)
    if os.WIFEXITED(status):
        code = os.WEXITSTATUS(status)
        if code != 0:
            die("exit status {} from running {}".format(code, argv))
    elif os.WIFSIGNALED(status):
        die("signal {} when running {}"
            .format(os.WTERMSIG(status), argv))
    else:
        assert False, "Unexpected result from waitpid()"

# Use with care.
# "Any  user input that is employed as part of command should be carefully sanitized, to ensure that unexpected shell commands or command options are  not executed."
#
def bg_shell(cmd):
    return bg("/bin/sh", "-c", cmd)

def fg_shell(cmd):
    return fg("/bin/sh", "-c", cmd)


fg("stty", "-F", tty_port, "115200")

tty_pid = bg("cat", tty_port)
print("\"cat {}\" started as pid {}".format(tty_port, tty_pid))

time.sleep(15)

tty_out = open(tty_port, "w")
def tty_print(msg):
    tty_out.write(msg)
    tty_out.flush()

tty_print("command1")
time.sleep(1)

tty_print("command2")
time.sleep(1)

transport_pid = bg_shell("exec /home/user/transport >/dev/null 2>&1")
print("transport started as pid {}".format(transport_pid))
time.sleep(1)

tty_print("command3")
time.sleep(1)

background_pid = bg("/home/user/backgroundprogram")
print("backgroundprogam started as pid {}".format(background_pid))

(pid, status) = os.wait()

# This could be modified to accept exit code 0 as a success,
# and/or accept exit due to SIGTERM as a success.

if os.WIFEXITED(status):
    die("exit status {} from pid {}".format(os.WEXITSTATUS(status)), pid)
elif os.WIFSIGNALED(status):
    die("signal {} when running {}".format(os.WTERMSIG(status), pid))
else:
    assert False, "Unexpected result from wait()"

答え4

bash最新バージョンのコマンドには、バックグラウンドプロセスが終了するのを待ってから終了する waitオプションがあります。-n

また、まだ不明な理由でcat開始と待機の間で時々終了しますが、これまでは終了を通知しませんwait。だからjobs待つ前にコマンドを追加しましたが、whcihがcat終了したことを確認するようです。その場合、待機は残りの2つのプロセスにのみ集中します。まだ終了していない場合、3 つのプロセスのいずれかが終了すると待機が終了します。

したがって、私のスクリプトの最後の行はwait次のように置き換えられます。

jobs
wait -n

waitを呼び出した後にジョブが終了すると、waitが終了し、systemdは残りのすべての子プロセスを終了し、スクリプトを再起動します。

関連情報