1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

新人エンジニアは「デーモン」がわからない ~ 3. Pythonでデーモンを作るとどんな感じ?~

Posted at

はじめに

やっとPythonでデーモンを作るところまで来ました〜。
サクッと行ってみましょ〜。

Pythonでデーモンを書く

✅ Pythonでデーモンを作る「要点」

項目 内容
1. forkで親子分離 プロセスを2つに分けて親を終了し、端末との関係を切る
2. セッションの切り離し setsid() により新しいセッションを開始(独立)
3. 標準出力・入力・エラーの切断 /dev/null などにリダイレクトする
4. 作業ディレクトリとumaskの変更 安全な初期化(例:/に移動、umask=0など)
5. 無限ループで処理待機 常駐動作の実装
6. PIDファイルやログ出力 状態を確認しやすくする

手順と解説

最小構成のデーモン

pydaemon.py

import os
import sys
import time

def daemonize():
    # 1回目のforkで親プロセスを終了
    if os.fork() > 0:
        sys.exit(0)

    # セッションを切り離す
    os.setsid()

    # 2回目のfork(セッションリーダーを回避)
    if os.fork() > 0:
        sys.exit(0)

    # カレントディレクトリをルートに変更
    os.chdir("/")
    os.umask(0)

    # 標準入出力を /dev/null に向ける
    sys.stdout.flush()
    sys.stderr.flush()
    with open('/dev/null', 'rb', 0) as f:
        os.dup2(f.fileno(), sys.stdin.fileno())
    with open('/dev/null', 'ab', 0) as f:
        os.dup2(f.fileno(), sys.stdout.fileno())
        os.dup2(f.fileno(), sys.stderr.fileno())

def main():
    # 無限ループ
    while True:
        with open("/tmp/pydaemon.log", "a") as f:
            f.write(f"{time.ctime()}: I'm alive!\n")
        time.sleep(60)

if __name__ == "__main__":
    daemonize()
    main()

解説

ステップ 処理内容 解説
os.fork() 子プロセスを作る 親を終了して端末との関係を切る
os.setsid() 新しいセッションを作る 親セッションから完全に切り離す
2回目の os.fork() 再び親を終了 セッションリーダーをやめるため(安全策)
os.chdir("/") ルートに移動 カレントディレクトリをアンマウントから守る
os.umask(0) umaskの初期化 セキュアなファイル作成を保証
os.dup2 標準入出力の切断 ターミナルがなくても動けるように
main() 無限ループで動作 デーモンとして常駐する処理

実行方法

python3 pydaemon.py
→ 端末には何も出ませんが、/tmp/pydaemon.log に1分ごとにログが記録されていきます。

補足:停止方法

ps aux | grep pydaemon.py
kill <PID>

あるいは pidファイル を作るようにすれば kill $(cat /var/run/pydaemon.pid) のように扱えます。

まとめ:Pythonでデーモンを作るには

必須の処理 理由
fork() → setsid() → fork() 完全な独立プロセス化
chdir("/"), umask(0) 安全な実行環境に初期化
/dev/null への入出力切断 端末切断時にも落ちないように
無限ループで処理継続 常駐プロセスとしての基本動作

PIDファイルを出力して管理できるようにする

PIDファイルを出力して kill $(cat ~.pid) で管理できるようにする方法について。

目標:

  • デーモンが起動したときに 自分のプロセスID(PID)をファイルに書き出す
  • 終了するときに自動でそのPIDファイルを削除する
  • kill $(cat /var/run/pydaemon.pid) で簡単に停止できる

改良版:PIDファイル付き pydaemon.py

import os
import sys
import time
import atexit

# PIDファイルのパス(通常は /var/run)
PIDFILE = "/tmp/pydaemon.pid"

def daemonize():
    if os.fork() > 0:
        sys.exit(0)

    os.setsid()

    if os.fork() > 0:
        sys.exit(0)

    os.chdir("/")
    os.umask(0)

    sys.stdout.flush()
    sys.stderr.flush()
    with open('/dev/null', 'rb', 0) as f:
        os.dup2(f.fileno(), sys.stdin.fileno())
    with open('/dev/null', 'ab', 0) as f:
        os.dup2(f.fileno(), sys.stdout.fileno())
        os.dup2(f.fileno(), sys.stderr.fileno())

    # PIDファイルの作成
    with open(PIDFILE, 'w') as f:
        f.write(str(os.getpid()) + '\n')

    # 終了時にPIDファイルを削除する
    atexit.register(lambda: os.remove(PIDFILE))

def main():
    while True:
        with open("/tmp/pydaemon.log", "a") as f:
            f.write(f"{time.ctime()}: I'm alive!\n")
        time.sleep(60)

if __name__ == "__main__":
    daemonize()
    main()

実行手順

python3 pydaemon.py
→ /tmp/pydaemon.pid にプロセスIDが書き込まれます。

停止方法

kill $(cat /tmp/pydaemon.pid)

→ 終了すると、自動的に .pid ファイルも削除されます(atexit のおかげ)。

補足:パーミッションの注意

  • 実際の運用では PIDファイルは /var/run/pydaemon.pid に置くのが慣習ですが、書き込みに root 権限が必要です。
  • 一般ユーザーでテストする場合は /tmp/pydaemon.pid でOK。

まとめ

機能 実現方法
PIDを保存する with open(PIDFILE, 'w') で自プロセスIDを書き出す
終了時に削除 atexit.register() でクリーンに削除
kill管理 kill $(cat PIDFILE) で確実に終了できる

systemd で本格的に運用してみよう!

ではいよいよ Pythonデーモンを systemd に登録して、Linuxのサービスとして起動(start)・停止(stop)・自動起動(enable)管理できるようにしましょう!

ゴール:

  • Pythonデーモン(pydaemon.py)を systemd サービスとして登録
  • systemctl start pydaemon で起動
  • systemctl enable pydaemon で自動起動
  • systemctl stop pydaemon で安全に停止

① スクリプトの準備

たとえば以下のように設置:

/home/youruser/pydaemon.py

忘れずに実行権限を付けておきます:

chmod +x /home/youruser/pydaemon.py

② systemd ユニットファイルの作成

sudo nano /etc/systemd/system/pydaemon.service

中身は以下のようにします:

[Unit]
Description=Python Daemon Example
After=network.target

[Service]
ExecStart=/usr/bin/python3 /home/youruser/pydaemon.py
Restart=always
User=youruser
Group=youruser

[Install]
WantedBy=multi-user.target

youruser は自分のユーザー名に置き換えてください
ExecStart は python3 のフルパス( which python3 で確認)

③ systemd に反映

sudo systemctl daemon-reload

④ 起動してみる!

sudo systemctl start pydaemon.service

状態確認:

sudo systemctl status pydaemon.service

ログの確認(Python側がログ出力している場合):

cat /tmp/pydaemon.log

  • 自動起動設定(再起動後も常駐)

sudo systemctl enable pydaemon.service

  • 停止方法

sudo systemctl stop pydaemon.service

補足:ログを journal に流したいとき

Pythonスクリプト内の標準出力/標準エラーを journald に直接出したいなら、以下のように systemd 側で設定します:

StandardOutput=journal
StandardError=journal

ただし、スクリプト側が /dev/null に dup2 していると journal に流れないので、そちらの調整が必要です。

まとめ

操作 コマンド
サービス起動 sudo systemctl start pydaemon
停止 sudo systemctl stop pydaemon
自動起動ON sudo systemctl enable pydaemon
状態確認 sudo systemctl status pydaemon
1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?