백그라운드 프로세스로 스크립트를 적절하게 관리하려면 어떻게 시스템화해야 합니까?

백그라운드 프로세스로 스크립트를 적절하게 관리하려면 어떻게 시스템화해야 합니까?

나는 백그라운드에서 세 개의 프로그램을 실행하고 포그라운드에서 몇 개의 프로그램을 실행하는 쉘 스크립트를 가지고 있으며 trap, 시작하고 실패할 경우 다시 시작할 수 wait있도록 유닛 파일을 설정했습니다 .systemd

그러나 프로세스가 종료되면 해당 스크립트의 모든 항목이 종료되고 다시 시작되지는 않는다는 사실을 발견했습니다. 이 애플리케이션의 경우 둘 중 하나가 종료되면 다시 시작해야 합니다.

두 가지 합리적인 경로가 있습니다.

  1. 유닛 파일을 하이브하고 예외가 감지되고 모든 예외가 종료되도록 스크립트를 변경한 다음 스크립트를 다시 실행하세요. 나는 무엇을 해야할지 모르겠습니다.
  2. 세 가지 백그라운드 프로세스를 각각 별도의 파일이 있는 자체 단위로 구성합니다 .service. 하지만 .service실패한 파일 중 하나를 종료하고 다시 시작하기 위해 파일을 작성하는 방법을 모르겠습니다 . 나는 그들의 종속성을 순서대로 시작하도록 정렬할 수 있다는 것을 알고 있지만 #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

이 모든 작업은 로그에 기록하는 등 작동하지만 세 프로세스 중 하나라도 실패하면 프로세스를 모두 종료하고 다시 시작하지 않고 계속 실행됩니다.

답변1

그러나 프로세스가 종료되면 해당 스크립트의 모든 항목이 종료되고 다시 시작되지는 않는다는 사실을 발견했습니다. 이 애플리케이션의 경우 둘 중 하나가 종료되면 다시 시작해야 합니다.

systemd는 아래 첨자가 아닌 쉘 스크립트를 모니터링합니다. 당신은하지 않을 것입니다생각하다systemd는 하위 프로세스의 종료에 응답합니다. 이로 인해 다시 시작됩니다.명령을 실행할 때마다. 생각해 보세요. 실행되는 쉘 스크립트가 있다면...

date

방금 자식 프로세스를 생성하고 실행한 다음 종료했습니다. 이로 인해 프로세스 감독자가 어떤 조치를 취하는 것을 원하지 않습니다.

systemd가 하위 프로세스를 모니터링하도록 하려면 각 프로세스에 대해 별도의 단위 파일을 생성하십시오.

  1. 직렬 포트를 구성하고 읽는 장치
  2. 하나를 위해/home/user/transport
  3. 하나를 위해/home/user/backgroundprogram

systemd 종속성을 사용하여 서비스의 올바른 시작 순서를 보장하고(한 서비스를 중지하면 모두 중지되도록 할 수 있음) 지시문을 사용하여 EnvironmentFile파일에서 구성을 로드할 수 있습니다(예:).$tty_port

일부 설정 명령("tty 장치에 명령 보내기...")을 한 줄에 입력하거나 ExecStartPre자체 Type=oneshot서비스를 받을 수도 있습니다.

답변2

기본 스크립트를 별도의 서비스로 분할할 수 있으면 다음과 같이 쉽게 해결할 수 있습니다.

아래 예에는 s1, s2, s3의 세 가지 생성 서비스가 있으며 대상 s.target을 통해 그룹으로 제어합니다.

참고: 세 가지 서비스가 다음
과 같이 구성된 경우Requires모두이 그룹에 참여하는 프로세스가 다시 시작됩니다.
또는 s.target 에서 구성하는 경우 Wants그 중 하나가 충돌하고 다시 생성되면 해당 개별 프로세스만 다시 시작됩니다.




각 서비스에 대해 서비스 파일 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

(참고: 서비스가 동일한 경우하나 [이메일 보호됨]여러 파일 대신 파일. @ 및 %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을 중지하여 세 가지 서비스를 모두 중지 할 수 있습니다 .

물론, 평소대로 개별 서비스를 시작/중지/다시 시작/상태 설정할 수 있습니다.
systemctl status s1

s1, s2 또는 s3 프로세스를 종료하면 자동으로 다시 생성됩니다(Restart=always).
을 사용하면 Requires그룹의 모든 프로세스가 다시 시작됩니다.

PS: systemctl enable s.target부팅 시 서비스를 시작하려면 다음을 실행하세요.

추신: 불행하게도 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종료되었는지 확인하는 것 같습니다. 그렇다면 대기는 나머지 두 프로세스에만 집중합니다. 아직 종료되지 않은 경우 세 프로세스 중 하나가 종료되면 대기가 종료됩니다.

따라서 내 스크립트의 마지막 줄은 wait다음으로 대체됩니다.

jobs
wait -n

wait를 호출한 후 작업이 종료되면 wait가 종료되고 systemd는 나머지 하위 프로세스를 모두 종료하고 스크립트를 다시 시작합니다.

관련 정보