はじめに
やっと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 |