systemdサービスユニットファイルの作成と安全な運用
要件と前提
Linuxシステム上でバックグラウンドプロセスを管理するsystemdサービスユニットおよびタイマーユニットの作成と運用について解説します。特に、スクリプトの冪等性 (idempotent)と安全性、そしてroot権限の適切な扱いと権限分離に焦点を当てます。
前提として、以下の環境を想定します。
systemdが動作するLinuxディストリビューション(例: CentOS, Ubuntu, RHEL)。bashシェル、curlコマンド、jqコマンドが利用可能であること。systemdサービスユニットファイルや関連する設定ファイルの配置にはroot権限が必要となります。ただし、サービスが実行する実際の処理は非特権ユーザーで実行することを推奨し、その設定方法を説明します。
実装
ここでは、外部APIからJSONデータを取得し、処理してログに記録する架空のバッチ処理をsystemdサービスとして実装する例を示します。さらに、このサービスを定期実行するためのsystemdタイマーユニットも作成します。
1. 安全なBashスクリプトの作成
まず、サービスが実行するメインスクリプトを作成します。このスクリプトは、冪等性を保ちつつ、エラーハンドリングと一時ファイルの安全な管理を含みます。
/usr/local/bin/my-api-processor.sh として以下の内容で保存します。
#!/bin/bash
# my-api-processor.sh - 外部APIからデータを取得し処理するスクリプト
# 冪等性確保とエラー処理のベストプラクティス
set -euo pipefail # -e: エラーで即終了, -u: 未定義変数でエラー, -o pipefail: パイプ中のエラーを捕捉
# スクリプト名を取得しログ出力に使用
SCRIPT_NAME=$(basename "$0")
# 一時ディレクトリの作成とクリーンアップ
# mktemp -d: 安全な一時ディレクトリを作成
TMP_DIR=$(mktemp -d -t "${SCRIPT_NAME}.XXXXXXXX")
# trap: スクリプト終了時に一時ファイルを確実にクリーンアップ
trap 'rm -rf "$TMP_DIR"; logger -t "$SCRIPT_NAME" "Temporary directory $TMP_DIR cleaned up."' EXIT
logger -t "$SCRIPT_NAME" "Script started. PID: $$"
# 外部APIのURL
API_URL="https://jsonplaceholder.typicode.com/posts/1" # 例としてJSONPlaceholderを使用
# curlコマンドでAPIを呼び出し、再試行とTLS検証を設定
RESPONSE=$(curl \
--retry 5 \
--retry-delay 5 \
--retry-max-time 30 \
--fail-with-body \
--silent \
--show-error \
"$API_URL" \
--output "$TMP_DIR/api_response.json" \
2>"$TMP_DIR/curl_error.log") # エラーをログファイルにリダイレクト
# curlの終了ステータスを確認
if [ $? -ne 0 ]; then
ERROR_MSG=$(cat "$TMP_DIR/curl_error.log")
logger -t "$SCRIPT_NAME" "ERROR: curl failed. $ERROR_MSG"
exit 1 # エラー終了
fi
# jqでJSONを処理する例
if ! API_TITLE=$(jq -r '.title' "$TMP_DIR/api_response.json"); then
logger -t "$SCRIPT_NAME" "ERROR: Failed to parse JSON with jq or 'title' field not found."
exit 1
fi
logger -t "$SCRIPT_NAME" "Successfully fetched data from $API_URL."
logger -t "$SCRIPT_NAME" "Processed API title: $API_TITLE"
logger -t "$SCRIPT_NAME" "Script finished successfully."
exit 0 # 正常終了
スクリプトの権限設定:
sudo chmod 755 /usr/local/bin/my-api-processor.sh
2. サービスユニットファイルの作成 (.service)
次に、上記スクリプトを実行するためのsystemdサービスユニットファイルを作成します。非特権ユーザーでの実行、セキュリティ強化のためのディレクティブを適切に設定します。
/etc/systemd/system/my-api-processor.service として以下の内容で保存します。
[Unit] Description=My API Processor Service After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/my-api-processor.sh User=myuser Group=mygroup WorkingDirectory=/home/myuser/api-processor Restart=no PrivateTmp=true ProtectSystem=full ProtectHome=true NoNewPrivileges=true StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
事前準備: サービスを実行するユーザーとグループを作成します。
sudo groupadd mygroup || true sudo useradd -r -s /sbin/nologin -g mygroup -d /home/myuser myuser || true sudo mkdir -p /home/myuser/api-processor sudo chown myuser:mygroup /home/myuser/api-processor
3. タイマーユニットファイルの作成 (.timer)
サービスを定期的に実行するために、systemdタイマーユニットを作成します。
/etc/systemd/system/my-api-processor.timer として以下の内容で保存します。
[Unit] Description=Run My API Processor every 10 minutes Requires=my-api-processor.service [Timer] OnCalendar=*:0/10:00 AccuracySec=1min Persistent=true [Install] WantedBy=timers.target
4. systemdへの登録と有効化
ユニットファイルを配置した後、systemdにそれらを認識させ、有効化します。
sudo systemctl daemon-reload sudo systemctl enable my-api-processor.timer sudo systemctl start my-api-processor.timer
systemdサービス起動フロー
Mermaid形式で、systemdがサービスを起動するまでの基本的なフローを示します。
graph TD
A["システム起動/daemon-reload"] --> B{"systemdユニットファイルを読み込む"};
B --> C{"タイマーユニットが有効化されているか?"};
C -- YES --> D["my-api-processor.timer を起動"];
C -- NO --> Z["手動でサービス起動が必要"];
D --> E{"OnCalendar のスケジュール到達"};
E -- YES --> F["my-api-processor.service を起動"];
F --> G["ExecStartスクリプト実行"];
G --> H["curlで外部API呼び出し"];
H --> I["jqでJSON処理"];
I --> J["ログ出力 (journaldへ)"];
J --> K["一時ファイルクリーンアップ"];
K --> L["サービス終了"];
検証
サービスが正しく設定され、期待通りに動作しているかを確認します。
サービスのステータス確認
タイマーとサービスの両方のステータスを確認します。
sudo systemctl status my-api-processor.timer sudo systemctl status my-api-processor.service
my-api-processor.timerの出力でNext: ...の項目を確認し、次の実行時刻が正しいことを検証します。my-api-processor.serviceの出力でActive: inactive (dead) と表示されるのが正常です(oneshotタイプのため実行後終了します)。
ログの確認
スクリプトからのログ出力はjournaldに送られます。
sudo journalctl -u my-api-processor.service --since "today" sudo journalctl -f -u my-api-processor.service
運用
サービスユニットファイルの更新手順
サービスユニットファイルやスクリプトの内容を変更した場合、以下の手順で変更を反映します。
サービスユニットファイルやスクリプトを更新。
systemdに新しい設定を読み込ませる:sudo systemctl daemon-reload
サービスを再起動:
sudo systemctl restart my-api-processor.service
タイマーの設定を変更した場合は、タイマーも再起動します:
sudo systemctl restart my-api-processor.timer
トラブルシュート
1. サービスが起動しない、または即座に終了する
ログの確認:
sudo journalctl -xeu my-api-processor.serviceで詳細を確認します。ユニットファイルの構文エラー:
sudo systemctl status my-api-processor.serviceの出力にエラーがないか確認します。権限問題: 指定したユーザーが存在するか、スクリプトの実行権限があるか確認します。
2. タイマーが機能しない
タイマーの有効化と起動:
sudo systemctl status my-api-processor.timerで状態を確認します。
まとめ
本記事では、systemdのサービスユニットおよびタイマーユニットの作成、そしてそれらを安全かつ堅牢に運用するためのベストプラクティスを解説しました。適切な権限分離とセキュリティディレクティブを活用し、信頼性の高いバックグラウンド処理を実現してください。
この記事の更新履歴
この記事は、生成AIを活用した自動レビュー・更新フローにより内容を見直し、必要な修正を反映しています。
2026年9月15日
- 削除冒頭のGemini未検証ドラフト表記を削除しました。
- 変更要件と前提セクションの不自然な読点始まりの文章を修正しました。

