0
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?

systemd-run を使ってサブプロセスを一時的に別のサービスとして動かす

0
Last updated at Posted at 2026-05-31

はじめに

Raspberry Pi と Raspberry Pi Pico 2 W を使って、LAN 内で完結する IoT アプリケーションを作っているのですが、OTA アップデートの機構を追加したくなりました。

↑ のような構成をとっていて、インターネットに繋げたタイミングで、GitHub Release に上げている新しいバージョンのファイルを取りに行き、アップデートするようにしました。

OTA アップデートの対象は、

  • Raspberry Pi Pico 2 W の firmware
  • nginx から配信しているフロントエンド用の静的ファイル
  • Raspberry Pi で動かしている Flask アプリケーション

の 3 つですが、
今回の対象は Raspberry Pi で動かしている systemd で管理された Flask アプリケーションです。

  1. GitHub Release から新しいバージョンのファイルをダウンロード
  2. 動作中の Flask アプリケーションの停止(systemctl stop
  3. 新しいバージョンに入れ替え
  4. 再起動

という過程で、フロントエンドからのリクエストをトリガーに、Flask アプリケーションが内部でサブプロセスを起動して、アップデートを実行するようにしました。

2 の「動作中の Flask アプリケーションの停止」をサブプロセス内から実行しようとすると、親のプロセスが停止したタイミングで、サブプロセスも死んでしまうので、アップデートがうまく行きません。

これを systemd-run を使って、サブプロセスを別のサービスとして動かすことによって解決したので、それについて書こうと思います。

課題

上で説明した、systemd で管理しているアプリケーションからサブプロセスを作成する簡易的なコードは以下です

  • python スクリプト(/opt/demo/parent.py

    import subprocess
    import time
    
    
    def main() -> None:
        subprocess.Popen(["sleep", "3600"])
        while True:
            time.sleep(60)
    
    if __name__ == "__main__":
        main()
    
  • unit ファイル

    [Unit]
    Description=systemd-managed demo parent
    After=network.target
    
    [Service]
    Type=simple
    ExecStart=python3 /opt/demo/parent.py
    StandardOutput=journal
    Restart=no
    

これを起動した際、プロセスは以下の状態になります。

$ sudo systemctl status demo.service
● demo.service - systemd-managed demo parent
     Loaded: loaded (/etc/systemd/system/demo.service; static)
     Active: active (running) since Sun 2026-05-31 14:16:07 JST; 6min ago
   Main PID: 16787 (python3)
      Tasks: 2 (limit: 38003)
     Memory: 5.0M (peak: 5.4M)
        CPU: 26ms
     CGroup: /system.slice/demo.service
             ├─16787 python3 /opt/demo/parent.py
             └─16788 sleep 3600

$ pstree -p -s 16788
systemd(1)───python3(16787)───sleep(16788)

$ systemd-cgls -u demo.service
Unit demo.service (/system.slice/demo.service):
├─16787 python3 /opt/demo/parent.py
└─16788 sleep 3600

$ ps -o pid,sid,comm -p 16787,16788
    PID     SID COMMAND
  16787   16787 python3
  16788   16787 sleep
  • 親プロセスとサブプロセスのセッション(SID)が同じ
  • 親プロセスとサブプロセスの cgroup が同じ

の状態です。

SID の共有

SID を共有しているセッションリーダー(親プロセス)が閉じられた場合、
同一セッションのプロセスに対して SIGHUP が送られることになるので、デフォルトの設定だとサブプロセスも同様に終了します。

ただし今回は親プロセスを systemd で管理しているので、この死に方は発生しないです。

Python の subprocess.Popen では、start_new_session を有効化することによって、別のセッションでプロセスを起動することができます。

import subprocess
import time


def main() -> None:
-   subprocess.Popen(["sleep", "3600"])
+   subprocess.Popen(["sleep", "3600"], start_new_session=True)
    while True:
        time.sleep(60)

if __name__ == "__main__":
    main()
$ sudo systemctl restart demo.service

$ sudo systemctl status demo.service
● demo.service - systemd-managed demo parent
     Loaded: loaded (/etc/systemd/system/demo.service; static)
     Active: active (running) since Sun 2026-05-31 15:19:31 JST; 2s ago
   Main PID: 20489 (python3)
      Tasks: 2 (limit: 38003)
     Memory: 5.4M (peak: 5.5M)
        CPU: 30ms
     CGroup: /system.slice/demo.service
             ├─20489 python3 /opt/demo/parent.py
             └─20491 sleep 3600

$ ps -o pid,sid,comm -p 20489,20491
    PID     SID COMMAND
  20489   20489 python3
  20491   20491 sleep

cgroup の共有

対象の unit と cgroup を共有しているプロセスは、unit が終了した時点で、デフォルトでは親プロセスもろともすべて終了します。

KillMode=

Specifies how processes of this unit shall be killed. One of control-group, mixed, process, none.

If set to control-group, all remaining processes in the control group of this unit will be killed on unit stop (for services: after the stop command is executed, as configured with ExecStop=). If set to mixed, the SIGTERM signal (see below) is sent to the main process while the subsequent SIGKILL signal (see below) is sent to all remaining processes of the unit's control group. If set to process, only the main process itself is killed (not recommended!). If set to none, no process is killed (strongly recommended against!). In this case, only the stop command will be executed on unit stop, but no process will be killed otherwise. Processes remaining alive after stop are left in their control group and the control group continues to exist after stop unless empty.

子プロセスが死なないような他のオプションもありますが、非推奨になっています。

Note that it is not recommended to set KillMode= to process or even none, as this allows processes to escape the service manager's lifecycle and resource management, and to remain running even while their service is considered stopped and is assumed to not consume any resources.

systemd-run を使って解決する

systemd-run を使うと、実行したいコマンドを現在のサービス(cgroup)から切り離して、systemd 管理下で、一時的な別サービス(transient service)として独立して実行することができます。
これにより、親プロセスが停止しても、影響を受けずに処理を続行させることが可能になります。

import subprocess
import time


def main() -> None:
-   subprocess.Popen(["sleep", "3600"])
+   subprocess.Popen([
+       "systemd-run",
+       "--unit=demo-worker",
+       "--description=Transient Sleep Worker",
+       "sleep", "3600"
+   ])
    
    while True:
        time.sleep(60)

if __name__ == "__main__":
    main()
$ sudo systemctl restart demo.service

$ sudo systemctl status demo.service
● demo.service - systemd-managed demo parent
     Loaded: loaded (/etc/systemd/system/demo.service; static)
     Active: active (running) since Sun 2026-05-31 16:36:18 JST; 3s ago
   Main PID: 24406 (python3)
      Tasks: 2 (limit: 38003)
     Memory: 5.2M (peak: 6.5M)
        CPU: 24ms
     CGroup: /system.slice/demo.service
             └─24406 python3 /opt/demo/parent.py

$ sudo systemctl status demo-worker.service
● demo-worker.service - Transient Sleep Worker
     Loaded: loaded (/run/systemd/transient/demo-worker.service; transient)
  Transient: yes
     Active: active (running) since Sun 2026-05-31 16:36:18 JST; 1min 57s ago
   Main PID: 24408 (sleep)
      Tasks: 1 (limit: 38003)
     Memory: 184.0K (peak: 512.0K)
        CPU: 2ms
     CGroup: /system.slice/demo-worker.service
             └─24408 /usr/bin/sleep 3600

$ systemd-cgls -u demo.service
Unit demo.service (/system.slice/demo.service):
└─24406 python3 /opt/demo/parent.py

$ systemd-cgls -u demo-worker.service
Unit demo-worker.service (/system.slice/demo-worker.service):
└─24408 /usr/bin/sleep 3600

cgroup を共有しない状態になっています。
なので、元のプロセスを停止しても、サブプロセスとして起動したプロセスに影響が出ないようになります。

$ sudo systemctl stop demo.service
$ sudo systemctl status demo-worker.service
● demo-worker.service - Transient Sleep Worker
     Loaded: loaded (/run/systemd/transient/demo-worker.service; transient)
  Transient: yes
     Active: active (running) since Sun 2026-05-31 16:36:18 JST; 6min ago
   Main PID: 24408 (sleep)
      Tasks: 1 (limit: 38003)
     Memory: 184.0K (peak: 512.0K)
        CPU: 2ms
     CGroup: /system.slice/demo-worker.service
             └─24408 /usr/bin/sleep 3600

参考

0
0
0

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
0
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?