systemdサービス管理完全ガイド:カスタムサービス作成から運用まで¶
この記事の対象者
- Linuxサーバー運用を行っている中級者(基本的なコマンドライン操作ができる方)
この記事のポイント¶
- カスタムsystemdサービスを作成・登録できる
- サービスの自動起動とログ監視を設定できる
- 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.target | 0 | システム停止 |
| rescue.target | 1 | シングルユーザーモード |
| multi-user.target | 3 | マルチユーザー(CUI) |
| graphical.target | 5 | グラフィカルインターフェース |
| reboot.target | 6 | 再起動 |
ログ監視と分析¶
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
[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-reload | systemd設定を再読込 |
ログ確認¶
| 用途 | コマンド | 説明 |
|---|---|---|
| 基本ログ | 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サーバー運用の効率性と安定性が大幅に向上します。基本的なサービスファイル作成から高度な設定、トラブルシューティングまで、段階的に学習することで実践的なスキルが身につきます。
重要なポイント¶
- セキュリティ第一: 専用ユーザーでの実行と適切な権限設定
- 監視の重要性: journalctlを活用したログ監視とアラート設定
- 段階的導入: 基本設定から始めて、必要に応じて高度な機能を追加
- 文書化: サービス設定の変更履歴と運用手順の記録
継続的な学習と実践により、systemdを活用したプロフェッショナルなサーバー運用が可能になります。