systemdのserviceファイルを自作して常駐プロセスを管理する

※本ページには広告(アフィリエイトプログラム等)が含まれます。

常駐プロセスをどう管理するか、地味だけど毎回悩むところ。nohup command &screen/tmuxでバックグラウンド実行するのは手軽だが、サーバー再起動後に自動起動しない、プロセスが落ちても気づかない、ログがどこかに散らばる、といった問題を抱える。systemdのserviceユニットを自作すれば、この3つが一気に解決する。今回は自前で書いたNode.jsのワーカープロセスを常駐させる想定で、unitファイルの書き方から運用コマンドまでまとめる。

なぜ nohup / screen ではなく systemd か

nohupscreenは「ターミナルを閉じてもプロセスを維持する」ことはできるが、サーバー再起動時の自動起動や、プロセスが異常終了した時の自動再起動は自分で仕組みを作る必要がある。systemdならRestart=alwaysの一行で自動再起動が手に入るし、journalctlでログが一元管理される。さくらのVPSのような自分で運用するサーバーでは、この差はそのままメンテナンスコストに直結する。

unitファイルの置き場所

自作のserviceファイルは/etc/systemd/system/配下に置く。ここではNode.jsのワーカーをmyapp.serviceとして登録する例で進める。

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

[Unit]セクション

起動順序や説明を書く。ネットワークが必要なプロセスならAfter=network.targetを入れておく。

[Unit]
Description=My App Worker
After=network.target

[Service]セクション

実行コマンド・作業ディレクトリ・実行ユーザー・再起動ポリシーを指定する。

[Service]
Type=simple
WorkingDirectory=/home/deploy/myapp
ExecStart=/usr/bin/node /home/deploy/myapp/worker.js
User=deploy
Restart=always
RestartSec=5
EnvironmentFile=/home/deploy/myapp/.env

ExecStartは絶対パス必須。node worker.jsのような相対コマンドやシェルのエイリアスは解決されないので、which nodeで実体パスを確認してから書く。EnvironmentFileで読み込む.envKEY=VALUE形式の単純な行のみ対応で、シェルのexport構文やコマンド置換は使えない点も注意したい。

[Install]セクション

[Install]
WantedBy=multi-user.target

これで通常のマルチユーザー起動時に自動起動する対象になる。全体を通すとこうなる。

[Unit]
Description=My App Worker
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/deploy/myapp
ExecStart=/usr/bin/node /home/deploy/myapp/worker.js
User=deploy
Restart=always
RestartSec=5
EnvironmentFile=/home/deploy/myapp/.env

[Install]
WantedBy=multi-user.target

反映と操作

unitファイルを置いたらdaemon-reloadで読み込ませ、enable --nowで自動起動設定と即時起動を同時に行う。

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

コード修正後の再起動や、一時停止は次のコマンドで行う。

sudo systemctl restart myapp
sudo systemctl stop myapp

ログの確認

標準出力・標準エラーは自動的にjournaldに吸われるので、アプリ側でログファイルを設計しなくても最低限の運用ログは残る。

journalctl -u myapp -f
journalctl -u myapp --since "1 hour ago"

-ftail -f相当のリアルタイム表示、--sinceで時間範囲を絞った過去ログ確認ができる。障害調査の第一歩はだいたいこの2つで足りる。

ハマりどころ

  • ExecStartは絶対パス必須。PATHが通っていても相対コマンド名だけでは起動に失敗する
  • 環境変数はシェルを介さないので、.bashrc.profileで設定した値は反映されない。EnvironmentFileEnvironment=で明示的に渡す
  • Type=simpleはExecStartのプロセスがそのままメインプロセスになる前提。デーモン化(fork)して親プロセスが先に終了するタイプのプログラムにはType=forkingを使い、その場合はPIDFile=も併せて指定する必要がある
  • unitファイルを編集した後にdaemon-reloadを忘れると、変更前の定義のまま起動してしまう

まとめ

自作のsystemd serviceは、書くこと自体はunitファイル数行で済むわりに、自動起動・自動再起動・ログ集約という運用上の面倒事をまとめて解決してくれる。PHPのキューワーカーやNode.jsの常駐プロセスを本番に置くなら、nohupで済ませず最初からsystemd化しておくのが結局は近道だ。

読んで頂いて有り難うございます!