systemdサービスの作成:unit・セキュリティ・ログ・再起動の検証
EdwardMoon
独自にインストールしたプログラムをOS起動時に開始し、障害時に再起動して、実行ユーザーとログを一貫して管理するには、systemdサービスとして登録します。この記事はRHEL・Rocky Linux 8/9のシステムサービスを対象にしています。アプリケーションはバックグラウンドに分岐せず、フォアグラウンドで実行を継続する必要があります。

1. 実行ファイルと設定を先に準備する
例の/usr/local/bin/myappと/etc/myapp/config.ymlは、実際のアプリケーションに合わせて変更します。以下の手順は、プログラム自体のインストールや設定ファイルの内容の作成を行うものではありません。正常に動作する起動コマンド、作業ディレクトリ、必要なファイル・ポート、正常終了の方法を先に確認してください。既存サービスに適用する場合は、変更するunitと設定ファイルのコピーを別の場所に保存します。
command -v systemctl
systemd --version
sudo test -x /usr/local/bin/myapp
sudo test -f /etc/myapp/config.yml確認に失敗した場合は、次の手順に進まないでください。パッケージがサービスを提供している場合は、新しいunitを作る前にsystemctl cat サービス名で確認します。提供されたunitのdrop-inを利用するほうが、保守しやすくなります。
2. 専用アカウントと書き込み用ディレクトリを作成する
getent passwd myapp
getent group myapp
# アカウントが存在しない場合に、一度だけ実行します。
sudo useradd --system --user-group --home-dir /var/lib/myapp --shell /sbin/nologin myapp
sudo install -d -o myapp -g myapp -m 0750 /var/lib/myapp
sudo chown root:myapp /etc/myapp/config.yml
sudo chmod 0640 /etc/myapp/config.yml
sudo -u myapp test -x /usr/local/bin/myapp
sudo -u myapp test -r /etc/myapp/config.ymlmyappアカウントがすでにある場合は、用途とプライマリグループを確認し、アカウント作成コマンドを省略します。親ディレクトリにもアクセス権が必要なので、namei -l /etc/myapp/config.ymlでパス全体を確認してください。サービスアカウントが実行ファイルや設定を変更できないようにし、アプリケーションが書き込むデータディレクトリだけに権限を与えます。SELinuxが有効な環境では、AVCログとファイルコンテキストも確認します。
3. サービスunitを作成する
sudoedit /etc/systemd/system/myapp.serviceで、以下の内容を保存します。
[Unit]
Description=My application
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/var/lib/myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml
Restart=on-failure
RestartSec=5s
TimeoutStopSec=30s
NoNewPrivileges=yes
PrivateTmp=yes
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetType=simpleは起動したプロセスをサービスのメインプロセスとして追跡します。アプリケーションの準備完了までは保証しないため、別途機能を確認する必要があります。ExecStartはシェルのコマンドラインではありません。&、パイプ、リダイレクトをそのまま記述しないでください。Afterは起動順序を、Wantsは同時に起動するunitとの関係を定めます。ネットワークがオンラインでも、リモートDBやAPIの正常応答まで保証されるわけではありません。Restart=on-failureと起動回数の制限を組み合わせ、短時間の障害ループを止めます。正常終了後にも再起動するかどうかは、プログラムの性質に合わせて決めます。PrivateTmpは専用の一時領域を作ります。他のサービスと/tmp内のファイルを共有するプログラムでは、互換性を確認してください。
パスワードやトークンをunitのEnvironment=に直接記載しないでください。アプリケーションが対応するアクセス制限付き設定ファイルや、使用中のsystemdバージョンが対応する資格情報の受け渡し機能を利用します。
4. 構文を確認して起動し、実際の機能を検証する
sudo systemd-analyze verify /etc/systemd/system/myapp.service
# 上の検証でエラーがない場合に進みます。
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl is-enabled myapp.service
systemctl is-active myapp.service
sudo systemctl status myapp.service --no-pager --full
sudo journalctl -u myapp.service -b -n 100 --no-pagerdaemon-reloadはunitを再読み込みし、enableはOS起動時にサービスを開始するためのリンクを作ります。--nowを付けると、その場でも起動します。すでに実行中のサービスの設定を変更した場合は、プログラムが対応するreload、または計画したrestartを別途実施する必要があります。
activeの確認だけで終えないでください。Webサービスなら実際のヘルスチェックURLにリクエストを送り、ジョブ処理サービスならテストジョブの処理結果を確認します。メンテナンス時間中に、OS再起動後の自動起動と正常終了も試します。例の終了待機時間30秒は、処理を失わずに終了できる時間に合わせて調整してください。
5. よくある起動失敗を切り分ける
| 症状 | 確認する内容 |
|---|---|
| 203/EXEC | 実行ファイルのパス、実行権限、スクリプトのインタープリター、SELinuxによる拒否 |
| 200/CHDIR | WorkingDirectoryの存在と、サービスアカウントのディレクトリアクセス権 |
| 217/USER | User・Groupに指定したアカウントの存在 |
| 起動直後に終了 | 実際の引数や設定の誤り、プログラムがデーモンモードに分岐していないか |
| start-limit-hit | ログから繰り返し失敗する原因を修正し、制限をリセットして再起動 |
sudo journalctl -u myapp.service --since '-10 minutes' --no-pager
# 原因を修正してから実行します。
sudo systemctl reset-failed myapp.service
sudo systemctl restart myapp.service変更を元に戻す場合
新規サービスなら、sudo systemctl disable --now myapp.serviceで停止と自動起動登録の解除を行います。既存サービスの変更なら、保存しておいたunitと設定を復元し、daemon-reloadの後に計画した再起動と機能確認を行います。サービスアカウントやデータディレクトリは、依存関係と保存の必要性を確認するまで削除しないでください。