#デーモンプロセス
デーモンプロセスとは、端末ログインとは独立してバックグラウンドで常駐し、継続的に機能を提供するプロセスのこと。
代表例は Web サーバー (nginx)、SSH サーバー (sshd)、コンテナランタイム (dockerd)や DBMS など。
名前の末尾に d が付く慣習は daemon に由来する。
通常のプロセスは、以下のように起動した端末やセッションに紐づいている。
ターミナル(tty)
└── セッション
└── プロセスグループ
└── プロセス
そのままだとログアウトや端末終了で後述する SIGHUP が飛びプロセスが停止するため、プロセスがバックグラウンドで動作するには以下が必要になる。
- 端末 (TTY) からの分離
- 親プロセスからの独立
- 標準入出力の扱いを明確化
- 安全な作業ディレクトリと
umask設定
##伝統的なデーモン化手順
古典的には次の流れを取る。
forkする
- POSIX ではプロセスグループのリーダーが新規にセッションを開始することを禁止しているため
setsidで新しいセッションリーダーになり、親プロセスのセッション制御対象から外れる- 再度
forkする。setsidでセッションリーダーになると、制御端末をうっかり取得できてしまうことがある。そのためもう一度forkする。 chdir("/")でマウントを掴み続けないようにする
- 各プロセスは現在の cwd を持っており、そのディレクトリはアンマウント、削除ができなくなるため
umaskを適切に設定する
- 昔の Unix だと
umaskを0にするらしいが、実際には適切な値にするのが良さそうだが、よくわかっていない
stdinstdoutstderrを/dev/nullやログ先に切り替える- メインループを実行し、
SIGTERMで安全に終了する
実際の Linux では、アプリ側で複雑なデーモン化を実装せず、systemd などのプロセスマネージャに監督させる構成が主流となっている。
##デーモンを制御するシグナル
SIGTERM(terminated): 通常停止 (systemctl stopやkillのデフォルト)SIGINT(interrupt): 対話実行時の停止 (Ctrl+C)SIGHUP(hangup): 制御端末の切断時に送られるシグナル。デーモンは制御端末を持たないため、慣例として設定ファイルの再読み込みシグナルとして扱われる (nginx -s reloadやsystemctl reloadなど)SIGKILL(kill): 強制終了。INTやTERMで終了できないようなプロセスを終了させるために使われる
##簡単なハンズオン
ここでは、シグナルを受け取る Python スクリプトを systemd に管理させてみる。
import signal
import sys
import time
import os
from types import FrameType
running = True # メインループを継続するかのフラグ
def handle_sigterm(signum: int, _frame: FrameType | None) -> None:
"""シグナルハンドラ。シグナルを受け取ったら `running=False` にしてメインループを終了させる。
Args:
signum (int): 受信したシグナル番号
_frame (FrameType): 受信時点でのスタックフレーム
"""
global running
print(f"[PID {os.getpid()}] Received signal {signum}. Shutting down...")
running = False
# シグナルハンドラの登録
signal.signal(signal.SIGTERM, handle_sigterm) # SIGTERM: `systemd stop` や `kill` のデフォルト
signal.signal(signal.SIGINT, handle_sigterm) # SIGINT: `Ctrl+C`など端末からの割り込み
print(f"[PID {os.getpid()}] Starting process")
# メインループ
while running:
print(f"[PID {os.getpid()}] Working...")
time.sleep(3)
print(f"[PID {os.getpid()}] Clean exit")
sys.exit(0)
##systemd で管理する
Docker コンテナ内に systemd をインストールして実行させる。
###1. Dockerfile (検証用)
FROM ubuntu:22.04
RUN apt-get update \
&& apt-get install -y init systemd systemd-sysv dbus python3 \
&& apt-get clean
RUN mkdir -p /opt/simple-daemon
COPY simple_daemon.py /opt/simple-daemon/simple_daemon.py
COPY simple_daemon.service /etc/systemd/system/simple_daemon.service
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod 0644 /opt/simple-daemon/simple_daemon.py \
&& chmod 0644 /etc/systemd/system/simple_daemon.service \
&& chmod +x /usr/local/bin/entrypoint.sh
VOLUME ["/sys/fs/cgroup"]
STOPSIGNAL SIGRTMIN+3
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
systemd をコンテナ内で動かすためにいくつかの工夫が必要(このあたりはまだ理解できていない…)。
init systemd systemd-sysv dbus:systemd本体と PID 1 として動作するために必要なパッケージ群VOLUME ["/sys/fs/cgroup"]:systemdはプロセス管理に cgroup (Control Groups) を使うため、ホストの cgroup ファイルシステムをマウントするSTOPSIGNAL SIGRTMIN+3:docker stop時にsystemdへ送るシグナル。通常のSIGTERM(15) ではなく、systemdが正常シャットダウンするための専用シグナル
entrypoint.sh
コンテナ起動時に実行されるスクリプト。systemd を PID 1 として起動し、その配下でサービスを管理させる。
#!/bin/bash
set -e
# Enable the simple_daemon service
systemctl enable simple_daemon.service
# Start the init system
exec /sbin/init
systemctl enable: コンテナ起動時にsimple_daemonサービスが自動起動するよう登録するexec /sbin/init: 現在のシェルプロセスをsystemd(=/sbin/init) に置き換えて PID 1 として起動する
###2. Unit ファイル
[Unit]
Description=Simple Python Daemon (hands-on)
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/simple-daemon/simple_daemon.py
Restart=on-failure
RestartSec=2
WorkingDirectory=/opt/simple-daemon
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
After=network.target: ネットワークが利用可能になってからこのサービスを起動するType=simple:ExecStartで起動したプロセスそのものをsystemdが監督する方式。手動で daemonize するプログラムを使う場合はType=forkingを検討するRestart=on-failure: プロセスが異常終了した場合に自動で再起動するStandardOutput=journal/StandardError=journal: 標準出力・エラーをjournaldに転送する。journalctl -u simple_daemonで確認できるWantedBy=multi-user.target: 通常のマルチユーザー起動時にこのサービスを有効にする
その他の詳細はこちらに書いていそう。
余談だが、Docker のコア実装となる moby のリポジトリにも Unit ファイルを見つけた
###3. コンテナ起動と確認
docker build -t ubuntu-systemd-sandbox .
docker run --name ubuntu-systemd-sandbox --rm -d \
--privileged \
--cgroupns=host \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
ubuntu-systemd-sandbox:latest
--privileged: コンテナに特権を付与する。systemdがカーネル機能(cgroup, mount 等)を利用するために必要--cgroupns=host: ホストの cgroup 名前空間を共有する-v /sys/fs/cgroup:/sys/fs/cgroup:rw: cgroup ファイルシステムをコンテナにマウントする
コンテナ内で以下を実行してデーモンが起動しているかを確認する。
systemctl status simple_daemon
# ● simple_daemon.service - Simple Python Daemon (hands-on)
# Loaded: loaded (/etc/systemd/system/simple_daemon.service; enabled; vendor preset: enabled)
# Active: active (running) since Thu 2026-03-05 09:57:06 UTC; 32s ago
# Main PID: 45 (python3)
# Tasks: 1 (limit: 2298)
# Memory: 2.8M
# CPU: 23ms
# CGroup: /docker/afe0a22201f9c1d6ecaab2a2fcf0213afbf61c0ba1413766a90f29d31c85048c/system.slice/simple_daemon.service
# └─45 /usr/bin/python3 /opt/simple-daemon/simple_daemon.py
#
# Mar 05 09:57:06 afe0a22201f9 systemd[1]: Started Simple Python Daemon (hands-on).
journalctl -u simple_daemon | head -n 5
# Mar 05 09:57:06 afe0a22201f9 systemd[1]: Started Simple Python Daemon (hands-on).
# Mar 05 10:17:33 afe0a22201f9 python3[45]: [PID 45] Starting process
# Mar 05 10:17:33 afe0a22201f9 python3[45]: [PID 45] Working...
# Mar 05 10:17:33 afe0a22201f9 python3[45]: [PID 45] Working...
# Mar 05 10:17:33 afe0a22201f9 python3[45]: [PID 45] Working...
ハンズオンはこれで以上。