systemdサービスユニットファイルの作成と安全な運用

Linux・CLI・DevOpsカテゴリを表すパンダのイラスト Linux・CLI・DevOps

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

運用

サービスユニットファイルの更新手順

サービスユニットファイルやスクリプトの内容を変更した場合、以下の手順で変更を反映します。

  1. サービスユニットファイルやスクリプトを更新。

  2. systemdに新しい設定を読み込ませる:

    sudo systemctl daemon-reload
    
  3. サービスを再起動:

    sudo systemctl restart my-api-processor.service
    
  4. タイマーの設定を変更した場合は、タイマーも再起動します:

    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未検証ドラフト表記を削除しました。
  • 変更要件と前提セクションの不自然な読点始まりの文章を修正しました。

文書情報

記事タイトル
systemdサービスユニットファイルの作成と安全な運用
作成日
更新日
Source URL
https://papanda925.com/?p=4020

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

タイトルとURLをコピーしました