コンテンツにスキップ

systemdサービス管理完全ガイド:カスタムサービス作成から運用まで

この記事の対象者

  • Linuxサーバー運用を行っている中級者(基本的なコマンドライン操作ができる方)

この記事のポイント

  1. カスタムsystemdサービスを作成・登録できる
  2. サービスの自動起動とログ監視を設定できる
  3. systemdトラブルシューティングの基本手順を実行できる

なぜこの問題が今重要か

現代のLinux環境では、Webアプリケーション、APIサーバー、バッチ処理など多様なプログラムを安定稼働させる必要があります。systemdはプロセス管理、自動復旧、ログ管理を統合的に提供し、運用効率を大幅に向上させる重要技術です。

解決ステップ概要

ステップ内容到達指標
1サービスファイル作成.serviceファイルが/etc/systemd/systemに配置完了
2サービス登録・起動systemctl statusで"active (running)"表示
3自動起動設定とログ確認再起動後も自動で起動、journalctlでログ取得可能

ステップ1: サービスファイル作成

systemdサービスは.serviceファイルで定義します。実際のWebアプリケーションサーバーを例に詳しく解説します。

基本的なサービスファイルテンプレート

# /etc/systemd/system/webapp.service
[Unit]
Description=Web Application Server
Documentation=https://example.com/docs
After=network.target network-online.target
Wants=network-online.target
Requires=postgresql.service

[Service]
Type=simple
User=webapp
Group=webapp
WorkingDirectory=/opt/webapp
Environment=NODE_ENV=production
ExecStart=/usr/bin/node /opt/webapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5
TimeoutStartSec=30
TimeoutStopSec=20

# セキュリティ設定
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/var/lib/webapp /var/log/webapp

[Install]
WantedBy=multi-user.target

セクション別詳細解説

[Unit]セクション - サービスの基本情報

  • Description: サービスの説明文(systemctl statusで表示)
  • After: 指定サービスの後に起動(起動順序制御)
  • Wants: 推奨依存関係(対象が失敗しても起動継続)
  • Requires: 必須依存関係(対象が失敗すると起動中止)

[Service]セクション - 実行設定

  • Type=simple: プロセスがフォアグラウンドで実行(最も一般的)
  • Type=forking: デーモン化するプロセス用
  • Type=oneshot: 一度だけ実行するタスク用
  • User/Group: 実行ユーザー・グループ(セキュリティ向上)
  • WorkingDirectory: プロセスの作業ディレクトリ
  • Environment: 環境変数設定
  • ExecStart: 起動コマンド(絶対パス必須)
  • ExecReload: 再読み込みコマンド
  • Restart=always: 異常終了時の自動再起動
  • RestartSec: 再起動までの待機時間(秒)
  • TimeoutStartSec: 起動タイムアウト
  • TimeoutStopSec: 停止タイムアウト

用途別サービスファイル例

Node.jsアプリケーション

[Unit]
Description=Node.js Web Application
After=network.target

[Service]
Type=simple
User=nodejs
WorkingDirectory=/opt/nodejs-app
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node app.js
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Python Flaskアプリケーション

[Unit]
Description=Flask Web Application
After=network.target

[Service]
Type=simple
User=flask
WorkingDirectory=/opt/flask-app
Environment=FLASK_ENV=production
ExecStart=/opt/flask-app/venv/bin/python -m flask run --host=0.0.0.0
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

バッチ処理サービス

[Unit]
Description=Data Processing Batch Service
After=network.target

[Service]
Type=oneshot
User=batch
WorkingDirectory=/opt/batch
ExecStart=/usr/bin/python3 process_data.py
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

ステップ2: サービス登録・起動

作成したサービスファイルをsystemdに認識させ、起動します。実際のコマンド実行例と詳細な出力解説も含めて説明します。

基本的な登録・起動手順

# 1. サービス設定をリロード(新しい.serviceファイルを認識)
sudo systemctl daemon-reload

# 2. サービス開始
sudo systemctl start webapp

# 3. 状態確認
sudo systemctl status webapp

# 成功例の詳細出力 webapp.service - Web Application Server
   Loaded: loaded (/etc/systemd/system/webapp.service; disabled; vendor preset: enabled)
   Active: active (running) since Thu 2025-08-29 09:00:15 JST; 2min 30s ago
 Main PID: 12345 (node)
    Tasks: 11 (limit: 4915)
   Memory: 45.2M
   CGroup: /system.slice/webapp.service
           └─12345 /usr/bin/node /opt/webapp/server.js

8月 29 09:00:15 myserver systemd[1]: Started Web Application Server.
8月 29 09:00:16 myserver node[12345]: Server listening on port 3000

status出力の読み方

  • Loaded: サービスファイルが正常に読み込まれているか
  • Active: 現在の動作状態
  • active (running): 正常に動作中
  • inactive (dead): 停止中
  • failed: 起動に失敗
  • Main PID: メインプロセスのプロセスID
  • Memory: 使用メモリ量
  • CGroup: プロセスグループ情報

よく使うsystemctlコマンド

# サービス一覧表示(起動中のみ)
systemctl list-units --type=service

# 全サービス一覧表示
systemctl list-units --type=service --all

# サービス停止
sudo systemctl stop webapp

# サービス再起動
sudo systemctl restart webapp

# サービス設定再読み込み(プロセス再起動なし)
sudo systemctl reload webapp

# サービス有効化(自動起動設定)
sudo systemctl enable webapp

# サービス無効化
sudo systemctl disable webapp

# サービスの依存関係確認
systemctl list-dependencies webapp

トラブルシューティング - 起動失敗時の対処

起動に失敗した場合の診断手順:

# 1. 詳細なステータス確認
sudo systemctl status webapp -l

# 2. サービス固有のログ確認
sudo journalctl -u webapp -n 50

# 3. サービスファイルの構文チェック
sudo systemd-analyze verify /etc/systemd/system/webapp.service

# 4. 依存関係の問題確認
systemctl list-dependencies webapp --failed

よくある起動失敗パターンと解決法

パターン1: ExecStartのパスが間違っている

# エラーメッセージ例
webapp.service: Failed with result 'exit-code'.
systemd[1]: webapp.service: Main process exited, code=exited, status=2

# 解決法: 実行ファイルパスを確認
which node  # /usr/bin/node
ls -la /opt/webapp/server.js  # ファイル存在確認

パターン2: ユーザー権限不足

# エラーメッセージ例
webapp.service: Failed to set up mount namespacing: Permission denied

# 解決法: 適切なユーザーとディレクトリ権限を設定
sudo useradd -r -s /bin/false webapp
sudo chown -R webapp:webapp /opt/webapp

パターン3: ポート競合

# ログ確認でポート使用中エラー
Error: listen EADDRINUSE :::3000

# 解決法: ポート使用状況確認と変更
sudo netstat -tlnp | grep :3000
# 別のポートを使用するかアプリケーション設定変更

ステップ3: 自動起動設定とログ確認

運用に必要な自動起動設定とログ監視を詳細に設定します。本番環境での実際の運用を想定した手順を解説します。

自動起動設定の詳細

# 1. 自動起動有効化
sudo systemctl enable webapp
# Created symlink /etc/systemd/system/multi-user.target.wants/webapp.service

# 2. 自動起動設定の確認
systemctl is-enabled webapp
# enabled

# 3. 起動ターゲットの確認
systemctl get-default
# multi-user.target または graphical.target

# 4. 有効化されたサービス一覧
systemctl list-unit-files --type=service --state=enabled

起動レベルとターゲット

systemdでは従来のランレベルの代わりに「ターゲット」を使用:

ターゲット従来ランレベル用途
poweroff.target0システム停止
rescue.target1シングルユーザーモード
multi-user.target3マルチユーザー(CUI)
graphical.target5グラフィカルインターフェース
reboot.target6再起動

ログ監視と分析

journalctlの基本的な使い方

# サービス固有のログを表示
sudo journalctl -u webapp

# リアルタイムログ監視(tail -f相当)
sudo journalctl -u webapp -f

# 時間範囲指定
sudo journalctl -u webapp --since "2025-08-29 09:00:00"
sudo journalctl -u webapp --since "1 hour ago"
sudo journalctl -u webapp --since today
sudo journalctl -u webapp --until "30 minutes ago"

# ログレベル指定
sudo journalctl -u webapp -p err  # エラー以上
sudo journalctl -u webapp -p warning  # 警告以上

# 出力行数制限
sudo journalctl -u webapp -n 100  # 最新100行
sudo journalctl -u webapp --lines=50  # 最新50行

# JSON形式で出力(構造化ログ解析用)
sudo journalctl -u webapp -o json-pretty

# ログをファイルに出力
sudo journalctl -u webapp > /tmp/webapp.log

高度なログ分析

# エラーのみ抽出
sudo journalctl -u webapp -p err --since today

# 特定キーワードでフィルタ
sudo journalctl -u webapp | grep -i "error\|failed\|exception"

# 統計情報表示
sudo journalctl -u webapp --since today | \
  awk '{print $1, $2, $3}' | sort | uniq -c

# ログサイズ確認
sudo journalctl --disk-usage

本番運用での監視設定

ログローテーション設定

# journaldの設定確認
sudo journalctl --show-disk-usage

# ログ保持期間設定(/etc/systemd/journald.conf)
sudo nano /etc/systemd/journald.conf

# 推奨設定例
[Journal]
MaxRetentionSec=7day
MaxFileSec=1day
MaxFileSize=100M
SystemMaxUse=1G

アラート設定例(スクリプト化)

#!/bin/bash
# /opt/scripts/webapp-monitor.sh
# webappサービスの監視スクリプト

SERVICE="webapp"

# サービス状態確認
if ! systemctl is-active --quiet "$SERVICE"; then
    echo "ALERT: $SERVICE is not running!" | \
    mail -s "$SERVICE Alert" admin@example.com

    # 自動復旧試行
    systemctl restart "$SERVICE"
    sleep 10

    if systemctl is-active --quiet "$SERVICE"; then
        echo "INFO: $SERVICE restarted successfully" | \
        mail -s "$SERVICE Recovered" admin@example.com
    fi
fi

# エラーログ確認
ERROR_COUNT=$(journalctl -u "$SERVICE" --since "5 minutes ago" -p err | wc -l)
if [ "$ERROR_COUNT" -gt 0 ]; then
    journalctl -u "$SERVICE" --since "5 minutes ago" -p err | \
    mail -s "$SERVICE Errors Detected" admin@example.com
fi

cron設定で定期監視

# crontabに監視スクリプトを追加
sudo crontab -e

# 5分毎にサービス監視実行
*/5 * * * * /opt/scripts/webapp-monitor.sh

運用時の定期メンテナンス

# ログディスクサイズ確認
sudo journalctl --vacuum-size=100M

# 古いログ削除
sudo journalctl --vacuum-time=7d

# サービスパフォーマンス確認
sudo systemd-analyze blame | head -10

# 起動時間詳細分析
sudo systemd-analyze critical-chain webapp

# メモリ使用量確認
sudo systemctl status webapp | grep Memory

よくある落とし穴と対処

症状原因即時対処
サービス起動失敗実行ファイルパス間違いExecStart=のパスを絶対パスで確認
権限エラーユーザー権限不足User=で適切なユーザーを指定
自動起動しないenable設定忘れsystemctl enable サービス名 実行
詳細設定(高度最適化) ### 環境変数の詳細管理 #### ファイルベース環境変数
# /etc/webapp/environment
NODE_ENV=production
API_KEY=your-secret-api-key
DATABASE_URL=postgresql://user:pass@localhost/myapp
LOG_LEVEL=info
MAX_CONNECTIONS=100
# サービスファイルで環境変数ファイルを参照
[Service]
EnvironmentFile=/etc/webapp/environment
EnvironmentFile=-/etc/webapp/environment.local  # -は存在しなくても無視
#### 動的環境変数(スクリプト利用)
#!/bin/bash
# /opt/scripts/webapp-env.sh
# 環境に応じた設定を動的生成

HOSTNAME=$(hostname)
MEMORY_LIMIT=$(($(free -m | awk '/^Mem:/{print $2}') * 70 / 100))

cat << EOF > /etc/webapp/runtime.env
HOSTNAME=${HOSTNAME}
MEMORY_LIMIT=${MEMORY_LIMIT}M
STARTUP_TIME=$(date -Iseconds)
EOF
### 複数プロセス・複雑な起動処理 #### Type=forkingの詳細設定
[Service]
Type=forking
PIDFile=/var/run/webapp/webapp.pid
ExecStartPre=/usr/bin/mkdir -p /var/run/webapp
ExecStartPre=/usr/bin/chown webapp:webapp /var/run/webapp
ExecStart=/opt/webapp/bin/webapp-daemon --pid-file=/var/run/webapp/webapp.pid
ExecStartPost=/bin/sleep 2
ExecStartPost=/bin/systemctl --user daemon-reload
TimeoutStartSec=60
#### 複数ステップ起動処理
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=/opt/scripts/pre-deploy.sh
ExecStartPre=/usr/bin/docker-compose -f /opt/webapp/docker-compose.yml pull
ExecStart=/usr/bin/docker-compose -f /opt/webapp/docker-compose.yml up -d
ExecStartPost=/opt/scripts/health-check.sh
ExecStop=/usr/bin/docker-compose -f /opt/webapp/docker-compose.yml down
TimeoutStartSec=300
### 高度なセキュリティ設定 #### ファイルシステム制限
[Service]
# プライベート領域設定
PrivateTmp=true                 # /tmpを分離
PrivateDevices=true            # /devへのアクセス制限
PrivateNetwork=false           # ネットワークは必要なため無効
PrivateUsers=true              # ユーザー名前空間分離

# ファイルシステム保護
ProtectSystem=strict           # システムディレクトリを読み取り専用
ProtectHome=true               # ホームディレクトリアクセス禁止
ReadWritePaths=/var/lib/webapp # 書き込み許可パス
ReadOnlyPaths=/etc/webapp      # 読み取り専用パス

# 機能制限
NoNewPrivileges=true           # 特権昇格防止
ProtectKernelTunables=true     # カーネル設定変更禁止
ProtectControlGroups=true      # cgroup変更禁止
RestrictSUIDSGID=true         # SUID/SGIDビット無効
#### ネットワーク制限
[Service]
# IPアドレス制限
IPAddressAllow=127.0.0.1       # ローカルホストのみ許可
IPAddressAllow=10.0.0.0/8      # プライベートネットワーク許可
IPAddressDeny=any              # その他すべて拒否

# プロトコル制限
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SocketBindAllow=3000           # 特定ポートのみバインド許可
SocketBindDeny=any             # その他ポートバインド禁止
### パフォーマンス最適化 #### リソース制限
[Service]
# CPU制限
CPUQuota=50%                   # CPU使用率を50%に制限
CPUWeight=100                  # CPU重み(デフォルトは100)

# メモリ制限
MemoryMax=512M                 # 最大メモリ使用量
MemorySwapMax=0               # スワップ禁止

# I/O制限
IOWeight=100                   # I/O重み
BlockIOWeight=100             # ブロックI/O重み

# タスク制限
TasksMax=50                   # 最大プロセス/スレッド数
#### 起動最適化
[Service]
# 起動遅延設定
ExecStartPre=/bin/sleep 5      # 他サービス待機
StartLimitInterval=300         # 5分間での再起動制限
StartLimitBurst=3             # 5分間で最大3回まで再起動

# プロセス優先度
Nice=10                       # プロセス優先度(-20〜19)
IOSchedulingClass=2           # I/Oスケジューリング(0-3)
IOSchedulingPriority=6        # I/O優先度(0-7)
### 高可用性設定 #### 依存関係の詳細管理
[Unit]
# 順序依存(起動順序のみ)
After=network-online.target postgresql.service redis.service
Before=nginx.service

# 要求依存(必須サービス)
Requires=postgresql.service
Wants=redis.service           # 推奨だが必須ではない

# 衝突回避
Conflicts=apache2.service     # 同時実行不可のサービス

# 条件設定
ConditionPathExists=/opt/webapp/config.json  # ファイル存在時のみ起動
ConditionUser=!root          # rootユーザーでは起動しない
AssertPathIsDirectory=/var/lib/webapp        # ディレクトリ必須
#### 障害回復設定
[Service]
# 再起動ポリシー
Restart=always                # 常に再起動
RestartSec=10                # 10秒待機後再起動
StartLimitInterval=600       # 10分間での制限
StartLimitBurst=5           # 10分間で最大5回

# ヘルスチェック
ExecStartPost=/opt/scripts/health-check.sh
ExecReload=/opt/scripts/graceful-reload.sh

# Watchdog設定(プロセス監視)
WatchdogSec=30              # 30秒間のハートビート監視
NotifyAccess=main           # メインプロセスからの通知受信

実践的トラブルシューティング集

ケーススタディ1: Node.jsアプリが起動しない

状況: Node.jsアプリケーションサービスが起動に失敗し続ける

# 症状確認
sudo systemctl status webapp
# ● webapp.service - Web Application Server
#    Loaded: loaded (/etc/systemd/system/webapp.service; enabled; vendor preset: enabled)
#    Active: failed (Result: exit-code) since Thu 2025-08-29 10:30:15 JST; 1min ago
#   Process: 15678 (code=exited, status=127)

原因調査手順:

# 1. 詳細ログ確認
sudo journalctl -u webapp -n 20
# /usr/bin/node: No such file or directory

# 2. Node.jsインストール状況確認  
which node
# /usr/bin/node が存在しない

# 3. 正しいパス確認
sudo find /usr -name node 2>/dev/null
# /usr/local/bin/node

# 4. サービスファイル修正
sudo nano /etc/systemd/system/webapp.service
# ExecStart=/usr/local/bin/node /opt/webapp/server.js

# 5. 設定反映・起動
sudo systemctl daemon-reload
sudo systemctl start webapp

ケーススタディ2: メモリ不足でサービスが停止

状況: 高負荷時にサービスが突然停止する

# OOM Killerによる強制終了の確認
sudo journalctl -k | grep -i "killed process"
# Out of memory: Kill process 12345 (node) score 856 or sacrifice child

# メモリ使用量監視の設定
sudo systemctl edit webapp
# /etc/systemd/system/webapp.service.d/override.conf
[Service]
MemoryMax=1G
MemorySwapMax=0
# メモリ不足時の優雅な停止
OOMPolicy=continue
ExecStartPre=/opt/scripts/memory-check.sh

メモリチェックスクリプト例:

#!/bin/bash
# /opt/scripts/memory-check.sh
AVAILABLE_MEM=$(free -m | awk '/^Mem:/{print $7}')
MIN_REQUIRED=512

if [ "$AVAILABLE_MEM" -lt "$MIN_REQUIRED" ]; then
    echo "Insufficient memory: ${AVAILABLE_MEM}MB available, ${MIN_REQUIRED}MB required"
    exit 1
fi

ケーススタディ3: データベース接続エラー

状況: アプリケーションがデータベースに接続できない

# 依存関係の問題確認
systemctl list-dependencies webapp --failed
# postgresql.service が failed状態

# PostgreSQL起動
sudo systemctl start postgresql
sudo systemctl enable postgresql

# 依存関係を明示的に設定
sudo nano /etc/systemd/system/webapp.service
[Unit]
Requires=postgresql.service
After=postgresql.service

[Service]
# 接続確認後に起動
ExecStartPre=/opt/scripts/db-wait.sh

データベース待機スクリプト:

#!/bin/bash
# /opt/scripts/db-wait.sh
timeout=60
interval=2

for ((i=0; i<timeout; i+=interval)); do
    if pg_isready -h localhost -p 5432 -U webapp; then
        echo "Database is ready"
        exit 0
    fi
    sleep $interval
done

echo "Database connection timeout"
exit 1

ケーススタディ4: ログローテーション問題

状況: ログファイルが巨大になりディスク容量を圧迫

# ディスク使用量確認
sudo journalctl --disk-usage
# Archived and active journals take up 2.1G in the file system.

# 設定確認
sudo nano /etc/systemd/journald.conf
[Journal]
# ログサイズ制限設定
SystemMaxUse=500M
SystemMaxFileSize=50M
RuntimeMaxUse=100M
MaxRetentionSec=1week
MaxFileSec=1day
# 設定適用
sudo systemctl restart systemd-journald

# 古いログ削除
sudo journalctl --vacuum-size=500M
sudo journalctl --vacuum-time=7d

systemd管理コマンドチートシート

基本操作

操作コマンド説明
起動sudo systemctl start サービス名サービスを開始
停止sudo systemctl stop サービス名サービスを停止
再起動sudo systemctl restart サービス名サービスを再起動
状態確認systemctl status サービス名詳細状態を表示
有効化sudo systemctl enable サービス名自動起動を有効
無効化sudo systemctl disable サービス名自動起動を無効
設定再読込sudo systemctl daemon-reloadsystemd設定を再読込

ログ確認

用途コマンド説明
基本ログsudo journalctl -u サービス名サービスのログ表示
リアルタイムsudo journalctl -u サービス名 -fログをリアルタイム監視
時間指定sudo journalctl -u サービス名 --since "1 hour ago"1時間前からのログ
エラーのみsudo journalctl -u サービス名 -p errエラーレベル以上のログ
行数制限sudo journalctl -u サービス名 -n 50最新50行のログ

トラブルシューティング

目的コマンド説明
構文確認sudo systemd-analyze verify ファイル.serviceサービスファイル構文チェック
依存関係systemctl list-dependencies サービス名依存関係ツリー表示
失敗サービスsystemctl --failed失敗したサービス一覧
起動時間sudo systemd-analyze blame起動時間の長いサービス
クリティカルsudo systemd-analyze critical-chain起動の重要パス分析

設定確認

確認内容コマンド説明
有効サービスsystemctl list-unit-files --type=service --state=enabled有効化されたサービス
全サービスsystemctl list-units --type=service --all全サービスの状態
設定場所systemctl show サービス名 -p FragmentPathサービスファイルパス
環境変数systemctl show サービス名 -p Environment設定された環境変数
リソースsystemctl status サービス名 | grep -E "(Memory|CPU|Tasks)"リソース使用量

まとめ

systemdサービス管理をマスターすることで、Linuxサーバー運用の効率性と安定性が大幅に向上します。基本的なサービスファイル作成から高度な設定、トラブルシューティングまで、段階的に学習することで実践的なスキルが身につきます。

重要なポイント

  1. セキュリティ第一: 専用ユーザーでの実行と適切な権限設定
  2. 監視の重要性: journalctlを活用したログ監視とアラート設定
  3. 段階的導入: 基本設定から始めて、必要に応じて高度な機能を追加
  4. 文書化: サービス設定の変更履歴と運用手順の記録

継続的な学習と実践により、systemdを活用したプロフェッショナルなサーバー運用が可能になります。

次に読む(関連記事)