code

Python 데몬 및 systemd 서비스

codestyles 2020. 11. 4. 08:00
반응형

Python 데몬 및 systemd 서비스


데몬으로 작동하는 간단한 Python 스크립트가 있습니다. 시작하는 동안이 스크립트를 시작할 수 있도록 systemd 스크립트를 만들려고합니다.

현재 시스템 스크립트 :

[Unit]
Description=Text
After=syslog.target

[Service]
Type=forking
User=node
Group=node
WorkingDirectory=/home/node/Node/
PIDFile=/var/run/zebra.pid
ExecStart=/home/node/Node/node.py

[Install]
WantedBy=multi-user.target

node.py :

if __name__ == '__main__':
    with daemon.DaemonContext():
        check = Node()
        check.run()

runwhile True루프를 포함 합니다.

이 서비스를 systemctl start zebra-node.service. 불행히도 서비스가 순서를 설명하는 것을 완료하지 못했습니다. Ctrl + C를 눌러야합니다. 스크립트가 실행 중이지만 상태가 활성화 중이고 잠시 후 비활성화 중으로 변경됩니다. 이제 나는 python-daemon을 사용하고 있습니다 (하지만 그것없이 시도하기 전에 증상은 비슷했습니다).

스크립트에 몇 가지 추가 기능을 구현해야합니까? 아니면 시스템 파일이 올바르지 않습니까?


시작 순서가 완료되지 않는 이유는 Type forkingyour startup process가 fork하고 종료 할 것으로 예상되기 때문입니다 ($ man systemd.service-forking 검색 참조).

단순히 메인 프로세스 만 사용하고 데몬 화하지 마십시오.

한 가지 옵션은 덜하는 것입니다. systemd를 사용하면 종종 데몬을 만들 필요가 없으며 데몬 화하지 않고 코드를 직접 실행할 수 있습니다.

#!/usr/bin/python -u
from somewhere import Node
check = Node()
check.run()

이를 통해라는 더 간단한 유형의 서비스를 사용할 수 simple있으므로 단위 파일이 다음과 같이 보일 것입니다.

[Unit]
Description=Simplified simple zebra service
After=syslog.target

[Service]
Type=simple
User=node
Group=node
WorkingDirectory=/home/node/Node/
ExecStart=/home/node/Node/node.py
StandardOutput=syslog
StandardError=syslog

[Install]
WantedBy=multi-user.target

참고는 것을 -u파이썬 오두막에서이 필요하지 않지만 경우에 당신은 표준 출력 또는 표준 오류에 뭔가를 출력의 -u차종은 물론, 즉시 systemd에 의해 체포 및 저널에 기록됩니다 장소 및 인쇄 라인에는 출력 버퍼링이 없습니다. 그것 없이는 약간의 지연으로 나타날 것입니다.

이를 위해 단위 파일에 라인 StandardOutput=syslogStandardError=syslog. 저널에 인쇄 된 출력물에 관심이 없다면이 줄은 신경 쓰지 마십시오 (존재할 필요는 없습니다).

systemd 데몬 화를 쓸모 없게 만든다

질문의 제목은 데몬 화에 대해 명시 적으로 묻지 만 질문의 핵심은 "내 서비스를 실행하는 방법"이고 메인 프로세스사용하는 것이 훨씬 간단 해 보이지만 (데몬에 대해 전혀 신경 쓸 필요가 없습니다) 귀하의 질문에 대한 답변으로 간주 될 수 있습니다.

많은 사람들이 "모두가 그렇게하기 때문에"데몬 화를 사용한다고 생각합니다. systemd를 사용하면 데몬 화의 이유는 종종 쓸모가 없습니다. 데몬 화를 사용하는 데는 몇 가지 이유가있을 수 있지만 지금은 드문 경우입니다.

편집 : python -p적절한 python -u. 감사합니다 kmftzg


Schnouki와 Amit가 설명하는 것처럼 데몬 화하는 것이 가능합니다. 그러나 systemd에서는 이것이 필요하지 않습니다. 데몬을 초기화하는 더 좋은 방법은 소켓 활성화와 sd_notify ()를 통한 명시 적 알림 두 가지입니다.

소켓 활성화는 네트워크 포트 또는 UNIX 소켓 등에서 수신하려는 데몬에 대해 작동합니다. Systemd는 소켓을 열고 청취 한 다음 연결이 들어 오면 데몬을 생성합니다. 이것은 관리자에게 가장 큰 유연성을 제공하기 때문에 선호되는 접근 방식입니다. [1]과 [2]는 멋진 소개를 제공하고 [3]은 C API를 설명하고 [4]는 Python API를 설명합니다.

[1] http://0pointer.de/blog/projects/socket-activation.html
[2] http://0pointer.de/blog/projects/socket-activation2.html
[3] http : //www.freedesktop .org / software / systemd / man / sd_listen_fds.html
[4] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.listen_fds

명시 적 알림은 데몬이 소켓 자체를 열고 / 또는 다른 초기화를 수행 한 다음 init에 준비가되었으며 요청을 처리 할 수 ​​있음을 알립니다. 이것은 "포킹 프로토콜"로 구현할 수 있지만 실제로는 sd_notify ()로 systemd에 알림을 보내는 것이 더 좋습니다. 파이썬 래퍼는 systemd.daemon.notify라고하며 [5]를 사용하는 한 줄이됩니다.

[5] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.notify

이 경우 유닛 파일은 Type = notify를 가지며 소켓을 설정 한 후 systemd.daemon.notify ( "READY = 1")를 호출합니다. 분기 나 데몬 화가 필요하지 않습니다.


PID 파일을 생성하지 않습니다.

systemd expects your program to write its PID in /var/run/zebra.pid. As you don't do it, systemd probably thinks that your program is failing, hence deactivating it.

To add the PID file, install lockfile and change your code to this:

import daemon
import daemon.pidlockfile 

pidfile = daemon.pidlockfile.PIDLockFile("/var/run/zebra.pid")
with daemon.DaemonContext(pidfile=pidfile):
    check = Node()
    check.run()

(Quick note: some recent update of lockfile changed its API and made it incompatible with python-daemon. To fix it, edit daemon/pidlockfile.py, remove LinkFileLock from the imports, and add from lockfile.linklockfile import LinkLockFile as LinkFileLock.)

Be careful of one other thing: DaemonContext changes the working dir of your program to /, making the WorkingDirectory of your service file useless. If you want DaemonContext to chdir into another directory, use DaemonContext(pidfile=pidfile, working_directory="/path/to/dir").


Also, you most likely need to set daemon_context=True when creating the DaemonContext().

This is because, if python-daemon detects that if it is running under a init system, it doesn't detach from the parent process. systemd expects that the daemon process running with Type=forking will do so. Hence, you need that, else systemd will keep waiting, and finally kill the process.

If you are curious, in python-daemon's daemon module, you will see this code:

def is_detach_process_context_required():
    """ Determine whether detaching process context is required.

        Return ``True`` if the process environment indicates the
        process is already detached:

        * Process was started by `init`; or

        * Process was started by `inetd`.

        """
    result = True
    if is_process_started_by_init() or is_process_started_by_superserver():
        result = False

Hopefully this explains better.


I came across this question when trying to convert some python init.d services to systemd under CentOS 7. This seems to work great for me, by placing this file in /etc/systemd/system/:

[Unit]
Description=manages worker instances as a service
After=multi-user.target

[Service]
Type=idle
User=node
ExecStart=/usr/bin/python /path/to/your/module.py
Restart=always
TimeoutStartSec=10
RestartSec=10

[Install]
WantedBy=multi-user.target

I then dropped my old init.d service file from /etc/init.d and ran sudo systemctl daemon-reload to reload systemd.

I wanted my service to auto restart, hence the restart options. I also found using idle for Type made more sense than simple.

Behavior of idle is very similar to simple; however, actual execution of the service binary is delayed until all active jobs are dispatched. This may be used to avoid interleaving of output of shell services with the status output on the console.

More details on the options I used here.

I also experimented with keeping the old service and having systemd resart the service but I ran into some issues.

[Unit]
# Added this to the above
#SourcePath=/etc/init.d/old-service 

[Service]
# Replace the ExecStart from above with these
#ExecStart=/etc/init.d/old-service start
#ExecStop=/etc/init.d/old-service stop

The issues I experienced was that the init.d service script was used instead of the systemd service if both were named the same. If you killed the init.d initiated process, the systemd script would then take over. But if you ran service <service-name> stop it would refer to the old init.d service. So I found the best way was to drop the old init.d service and the service command referred to the systemd service instead.

Hope this helps!

참고URL : https://stackoverflow.com/questions/13069634/python-daemon-and-systemd-service

반응형