web-dev-qa-db-ja.com

Pythonデーモンとsystemdサービス

単純なPythonデーモンとして動作するスクリプトがあります。起動中にこのスクリプトを開始できるようにsystemdスクリプトを作成しようとしています。

現在の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 __== '__main__':
    with daemon.DaemonContext():
        check = Node()
        check.run()

runにはwhile Trueループが含まれます。

systemctl start zebra-node.serviceでこのサービスを実行しようとしました。残念ながら、サービスはシーケンスの記述を完了していません-Ctrl + Cを押す必要があります。スクリプトは実行中ですが、ステータスはアクティブ化中で、しばらくすると非アクティブ化に変わります。今、私はpython-daemonを使用しています(ただし、それを試してみる前は症状は似ていました)。

スクリプトにいくつかの追加機能を実装する必要がありますか、またはsystemdファイルが正しくありませんか?

68
pawelbial

理由は、スタートアップシーケンスを完了しないためです。Typeforkingの場合、スタートアッププロセスは分岐して終了することが予想されます($ man systemd.service-分岐の検索を参照)。

メインプロセスのみを使用し、デーモン化しないでください

1つのオプションは、実行回数を減らすことです。 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 in python Shebangは必要ありませんが、stdoutまたはstderrに何かを出力する場合、-uは、出力バッファリングが適所にあり、印刷された行はすぐにsystemdによってキャッチされ、ジャーナルに記録されます。

このために、ユニットファイルにStandardOutput=syslog行とStandardError=syslog行を追加しました。ジャーナルの印刷出力を気にしない場合は、これらの行を気にしないでください(存在する必要はありません)。

systemdはデーモン化を廃止します

あなたの質問のタイトルは明示的にデーモン化について尋ねていますが、質問の核心は「私のサービスを実行する方法」とメインプロセスを使用する方がはるかに簡単です(あなたは気にする必要はありません)デーモンについて)、それはあなたの質問への答えと考えることができます。

「誰もがやる」という理由だけで、多くの人がデーモン化を使用していると思います。 systemdでは、デーモン化の理由はしばしば時代遅れです。デーモン化を使用する理由はいくつかありますが、現在ではまれなケースです。

編集:python -pを適切なpython -uに修正しました。ありがとうkmftzg

103
Jan Vlcinsky

SchnoukiやAmitのようにデーモン化することは可能です。しかし、systemdではこれは必要ありません。デーモンを初期化するより良い方法が2つあります。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に通知することです。これは「forking protocol」を使用して実装できますが、実際には、sd_notify()を使用してsystemdに通知を送信する方が適切です。 Pythonラッパーはsystemd.daemon.notifyと呼ばれ、[5]を使用する1行になります。

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

この場合、ユニットファイルにはType = notifyがあり、ソケットを確立した後にsystemd.daemon.notify( "READY = 1")を呼び出します。フォークやデーモン化は必要ありません。

21
zbyszek

PIDファイルを作成していません。

systemdは、プログラムが_/var/run/zebra.pid_にPIDを書き込むことを期待しています。あなたがそれをしないので、systemdはおそらくあなたのプログラムが失敗していると考えて、それでそれを無効にします。

PIDファイルを追加するには、 lockfile をインストールし、コードを次のように変更します。

_import daemon
import daemon.pidlockfile 

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

(クイックノート:lockfileの最近の更新によりAPIが変更され、python-daemonとの互換性がなくなりました。修正するには、_daemon/pidlockfile.py_を編集し、インポートからLinkFileLockを削除し、 _from lockfile.linklockfile import LinkLockFile as LinkFileLock_。)

別のことに注意してください:DaemonContextは、プログラムの作業ディレクトリを_/_に変更し、サービスファイルのWorkingDirectoryを使用不能にします。 DaemonContextを別のディレクトリにchdirしたい場合は、DaemonContext(pidfile=pidfile, working_directory="/path/to/dir")を使用します。

14
Schnouki

また、DaemonContext()を作成するときにdaemon_context=Trueを設定する必要があります。

これは、python-daemonがinitシステムで実行されている場合、親プロセスから切り離されないことを検出した場合です。 systemdは、Type=forkingで実行されているデーモンプロセスがそうすることを期待しています。したがって、それが必要です。そうでなければ、systemdが待機し続け、最終的にプロセスを強制終了します。

興味がある場合は、python-daemonのデーモンモジュールに次のコードが表示されます。

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

うまくいけば、これがもっとよく説明できます。

3
user59634

いくつかのpython init.dサービスをCentOS 7でsystemdに変換しようとしたときにこの質問に出会いました。これは、このファイルを/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

次に、古いinit.dサービスファイルを/etc/init.dSudo systemctl daemon-reload systemdをリロードします。

サービスを自動的に再起動したかったため、再起動オプションがありました。また、idleTypeを使用すると、simpleよりも意味があります。

アイドルの動作は単純に非常に似ています。ただし、すべてのアクティブなジョブがディスパッチされるまで、サービスバイナリの実際の実行は遅延します。これは、シェルサービスの出力とコンソールのステータス出力のインターリーブを回避するために使用できます。

使用したオプションの詳細 here

また、古いサービスを保持し、systemdでサービスを再起動することを試みましたが、いくつかの問題に遭遇しました。

[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

私が経験した問題は、両方に同じ名前が付けられている場合、systemdサービスの代わりにinit.dサービススクリプトが使用されることでした。 init.dが開始したプロセスを強制終了すると、systemdスクリプトが引き継ぎます。ただし、service <service-name> stop古いinit.dサービスを参照します。そのため、古いinit.dサービスを削除し、代わりにsystemdサービスを参照するserviceコマンドを削除することが最善の方法であることがわかりました。

お役に立てれば!

1
radtek