【要約】subprocess.run(shell=True, timeout=) で死なない子が Pi 5 を食い潰した話 [Zenn_Python] | Summary by TechDistill
> Source: Zenn_Python
Execute Primary Source
// Problem
開発者がRaspberry Pi 5で監視スクリプトを運用中、12時間後にシステム負荷が異常上昇する問題に直面した。タイムアウトを設定していたにもかかわらず、プロセスが蓄積し続けていた。詳細は以下の通りである。
- ・subprocess.run(shell=True, timeout=5)を使用。
- ・タイムアウト時、Pythonはシェルのみをkillし、孫プロセス(curl, jq)を放置した。
- ・放置されたプロセスが孤児プロセスとなり、12時間で287個まで蓄積。
- ・蓄積したプロセスがCPUリソースを食いつぶし、load averageを8まで押し上げた。
// Approach
発生したプロセスリークを解消するため、プロセス管理の粒度を変える3つの手法が検討された。状況に応じた適切な制御方法を選択する必要がある。
- ・プロセスグループによる一括停止: start_new_session=Trueで新しいセッションを作成し、os.killpgでグループ全体にシグナルを送る。
- ・シェル経由の回避: shell=Trueを廃止し、コマンドをリスト形式で渡すことでプロセス階層を浅く保つ。
- ・ネイティブ実装への移行: requests等のライブラリを用い、外部プロセス呼び出し自体を排除する。
// Result
開発者は、最も低コストで安全な「shell=Trueを捨てる」手法を採用し、問題を解決した。これにより、リソース消費の抑制とコードの簡素化を同時に実現している。
- ・subprocess関連のコードを6行削減し、実装をシンプル化した。
- ・Shell Injectionのリスクを排除し、セキュリティを向上させた。
- ・セッション内のプロセス数を監視するコードを追加し、異常の早期検知を可能にした。
Senior Engineer Insight
> 極めて実践的な教訓である。shell=Trueは利便性が高いが、プロセス管理の責任を放棄することに等しい。特にパイプラインを利用する場合、タイムアウト設定が「嘘」になるリスクを理解せねばならない。本番環境では、外部コマンドへの依存を最小限にするか、プロセスグループ単位での制御を徹底すべきである。また、異常検知のためにセッション内のプロセス数を監視する仕組みを組み込む判断は、運用フェーズにおいて非常に高く評価できる。