<p>本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">systemd-analyze を用いたLinux起動解析の完全ガイド</h1>
<p>Linuxシステム起動時間の最適化は、DevOpsエンジニアにとって重要な課題の一つです。<code>systemd-analyze</code>コマンドは、<code>systemd</code>を採用するLinuxシステムにおいて、起動プロセスの詳細なメトリクスを提供し、パフォーマンスボトルネックを特定するための強力なツールです。本記事では、<code>systemd-analyze</code>の基本的な使い方から、安全なBashスクリプトによる自動化、<code>jq</code>や<code>curl</code>といったツールとの連携、さらに<code>systemd</code>ユニット/タイマーを用いた最適化と運用・トラブルシューティングまでを網羅的に解説します。</p>
<h2 class="wp-block-heading">1. 要件と前提</h2>
<h3 class="wp-block-heading">要件</h3>
<ul class="wp-block-list">
<li><p><code>systemd</code>を採用したLinux環境(Ubuntu 18.04+, CentOS 7+, RHEL 7+など)。</p></li>
<li><p>Bashシェルが利用可能であること。</p></li>
<li><p><code>jq</code>および<code>curl</code>コマンドがインストールされていること。</p></li>
<li><p>root権限(<code>sudo</code>)でコマンドを実行できること。</p></li>
</ul>
<h3 class="wp-block-heading">前提</h3>
<ul class="wp-block-list">
<li><p>Linuxシステムの基本的な操作知識。</p></li>
<li><p><code>systemd</code>ユニットファイルに関する基本的な理解。</p></li>
<li><p>パフォーマンス最適化の一般的な概念。</p></li>
</ul>
<h2 class="wp-block-heading">2. 実装</h2>
<h3 class="wp-block-heading">2.1. systemd-analyzeによる起動時間の分析</h3>
<p><code>systemd-analyze</code>コマンドは、システムの起動時間や各ユニットの起動時間に関する詳細な情報を提供します。</p>
<h4 class="wp-block-heading">2.1.1. 概要時間の確認</h4>
<p>システム全体の起動時間を表示します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">systemd-analyze time
</pre>
</div>
<p><strong>出力例:</strong></p>
<pre data-enlighter-language="generic">Startup finished in 3.456s (kernel) + 12.345s (userspace) = 15.801s
</pre>
<ul class="wp-block-list">
<li><p><code>kernel</code>: カーネルの初期化にかかった時間。</p></li>
<li><p><code>userspace</code>: <code>systemd</code>および各サービスが起動にかかった時間。</p></li>
</ul>
<h4 class="wp-block-heading">2.1.2. 各ユニットの起動時間(<code>blame</code>)</h4>
<p>起動に最も時間のかかっているユニットをリストアップします。これにより、ボトルネックを特定しやすくなります。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">systemd-analyze blame | head -n 10
</pre>
</div>
<p><strong>出力例:</strong></p>
<pre data-enlighter-language="generic">1.234s networkd-dispatcher.service
1.012s snapd.service
987ms dev-sda1.device
876ms systemd-journal-flush.service
765ms systemd-udev-trigger.service
654ms accounts-daemon.service
543ms systemd-logind.service
432ms systemd-resolved.service
321ms plymouth-start.service
210ms systemd-tmpfiles-setup.service
</pre>
<h4 class="wp-block-heading">2.1.3. 起動クリティカルパス(<code>critical-chain</code>)</h4>
<p>起動時間全体に影響を与える可能性のある依存関係のチェーンを表示します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">systemd-analyze critical-chain
</pre>
</div>
<p><strong>出力例:</strong></p>
<pre data-enlighter-language="generic">The time when unit became active or started is printed after the "@" character.
The time the unit took to start up is printed after the "+" character.
graphical.target @12.345s
└─multi-user.target @12.345s
└─snapd.service @10.123s +1.012s
└─basic.target @9.876s
└─sockets.target @9.876s
└─snapd.socket @9.876s
└─systemd-udevd.service @9.765s +123ms
└─systemd-journald.service @9.654s +111ms
└─systemd-remount-fs.service @9.543s +100ms
└─systemd-fsck-root.service @9.432s +99ms
└─systemd-readahead-replay.service @9.321s +88ms
└─systemd-readahead-collect.service @9.210s +77ms
</pre>
<h4 class="wp-block-heading">2.1.4. 起動プロセスの可視化(<code>plot</code>)</h4>
<p>起動プロセスをSVG形式の画像として出力します。これにより、時間の流れと依存関係を視覚的に把握できます。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">systemd-analyze plot > boot_analysis_20240725.svg
</pre>
</div>
<p>生成されたSVGファイルはWebブラウザで開くことができます。</p>
<h3 class="wp-block-heading">2.2. 安全なBashスクリプトの作成例</h3>
<p><code>systemd-analyze</code>コマンドの結果を自動的に収集し、レポートを作成するスクリプトの例です。root権限が必要な操作は<code>sudo</code>を明示します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">#!/bin/bash
# systemd-analyzeの結果を収集し、レポートを生成するスクリプト
# 厳格なエラーハンドリング:
# -e: コマンドが失敗した場合に即座に終了
# -u: 未定義の変数を使用した場合にエラー
# -o pipefail: パイプライン中のコマンドが失敗した場合に終了
set -euo pipefail
# 一時ディレクトリの作成とクリーンアップ
# trap: スクリプト終了時(EXIT)、中断時(INT)、エラー時(ERR)に実行
tmpdir=$(mktemp -d -t systemd-analysis-XXXXXXXXXX)
trap 'rm -rf "$tmpdir"; echo "Temporary directory $tmpdir removed." >&2' EXIT INT TERM
echo "Starting systemd-analyze report generation at $(date -u +%Y-%m-%dT%H:%M:%SZ)..."
# 実行ユーザーの確認
if [ "$(id -u)" -eq 0 ]; then
echo "Running as root."
SUDO=""
else
echo "Running as non-root, using sudo for privileged commands."
SUDO="sudo"
fi
report_file="$tmpdir/systemd_boot_report_$(date +%Y%m%d%H%M%S).txt"
plot_file="$tmpdir/systemd_boot_plot_$(date +%Y%m%d%H%M%S).svg"
json_dump_file="$tmpdir/systemd_dump_$(date +%Y%m%d%H%M%S).json"
# レポートヘッダ
echo "--- systemd Boot Analysis Report (Generated on {{jst_today}}) ---" > "$report_file"
echo "" >> "$report_file"
# 1. 起動時間の概要
echo "### 1. Boot Time Summary" >> "$report_file"
"$SUDO" systemd-analyze time >> "$report_file" 2>&1
echo "" >> "$report_file"
# 2. 起動に時間のかかっているユニット上位10件
echo "### 2. Top 10 Slowest Units (systemd-analyze blame)" >> "$report_file"
"$SUDO" systemd-analyze blame | head -n 10 >> "$report_file" 2>&1
echo "" >> "$report_file"
# 3. クリティカルチェーン
echo "### 3. Critical Boot Chain (systemd-analyze critical-chain)" >> "$report_file"
"$SUDO" systemd-analyze critical-chain >> "$report_file" 2>&1
echo "" >> "$report_file"
# 4. 起動プロットの生成 (SVG)
echo "### 4. Boot Plot Generation" >> "$report_file"
echo "Generating boot plot SVG: $plot_file" >> "$report_file"
"$SUDO" systemd-analyze plot > "$plot_file" 2>&1
echo "Boot plot generated successfully." >> "$report_file"
echo "" >> "$report_file"
# 5. systemdの状態ダンプ (JSON形式)
echo "### 5. systemd State Dump (JSON)" >> "$report_file"
echo "Dumping systemd state to JSON: $json_dump_file (requires sudo)" >> "$report_file"
# systemd-analyze dump --json は非常に大量の出力を生成するため、必要に応じて使用を検討
# "$SUDO" systemd-analyze dump --json > "$json_dump_file" 2>&1
echo "To get full JSON dump, run: '$SUDO systemd-analyze dump --json > $json_dump_file'" >> "$report_file"
echo "" >> "$report_file"
echo "Report generation completed. Outputs are in: $tmpdir"
echo "Report: $report_file"
echo "Plot: $plot_file"
echo "To view plot: xdg-open $plot_file (or open in web browser)"
# スクリプトが正常終了した場合、一時ディレクトリの削除はtrapで実行される
exit 0
</pre>
</div>
<p><strong>実行方法:</strong></p>
<ol class="wp-block-list">
<li><p>上記のコードを<code>analyze_boot.sh</code>として保存します。</p></li>
<li><p><code>chmod +x analyze_boot.sh</code> で実行権限を付与します。</p></li>
<li><p><code>./analyze_boot.sh</code> で実行します。必要に応じて<code>sudo</code>プロンプトが表示されます。</p></li>
</ol>
<h3 class="wp-block-heading">2.3. jq を用いたJSON処理</h3>
<p><code>systemd-analyze</code>コマンドの中には、<code>systemd-analyze dump --json</code>のようにJSON形式の出力をサポートするものがあります。これを<code>jq</code>で処理する例を示します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">#!/bin/bash
# systemd-analyze dumpのJSON出力をjqで解析するスクリプト
set -euo pipefail
# systemd-analyze dump --json はroot権限が必要
if [ "$(id -u)" -ne 0 ]; then
echo "This script requires root privileges. Please run with sudo." >&2
exit 1
fi
echo "Fetching systemd dump in JSON format..."
# systemdの状態をJSONで取得
# 実際の環境では出力が非常に大きい可能性があるため、ここでは最初の数行のみを例示
# 全ての出力を解析する場合は、`head -n 10`を削除してください
# NOTE: systemd-analyze dump --jsonはすべてのユニット情報を出力するため、非常に巨大なJSONとなる。
# テスト目的では `systemd-analyze unit --json NetworkManager.service` のように特定のユニットを使う方が適切。
# ここではdumpの例として扱いつつ、注意喚起を行う。
systemd_dump_json=$(sudo systemd-analyze dump --json | head -n 50) # 例として一部のみ取得
echo "Parsing JSON output with jq..."
# jqを使って、units配列の最初の2つのユニット名と状態を抽出する例
# 実際の `systemd-analyze dump --json` の出力構造は複雑なため、
# ここでは概念的な例として、一般的なsystemdユニット情報の抽出を示す。
# より実用的な例として、`systemd-analyze unit <unit_name> --json` を推奨する。
# 例: systemd-analyze unit systemd-journald.service --json
# ここでは、簡略化のため、`systemd-analyze unit <unit_name> --json` の出力形式を想定してjqコマンドを記述します。
# 実際には、`systemd-analyze dump --json` の出力はトップレベルに 'version', 'units' などを含む辞書形式です。
# 例として、`systemd-analyze unit systemd-journald.service --json` を直接実行し、その結果を処理します。
echo "--- Analyzing systemd-journald.service with jq ---"
sudo systemd-analyze unit systemd-journald.service --json | jq '.[] | {Name: .name, Description: .description, LoadState: .load_state, ActiveState: .active_state, SubState: .sub_state}'
echo ""
echo "--- Analyzing networkd-dispatcher.service with jq ---"
sudo systemd-analyze unit networkd-dispatcher.service --json | jq '.[] | {Name: .name, Description: .description, LoadState: .load_state, ActiveState: .active_state, SubState: .sub_state, StateChangeTimestamp: (.state_change_timestamp | strftime("%Y-%m-%d %H:%M:%S %Z"))}'
echo ""
echo "jq examples completed."
</pre>
</div>
<p><strong>jqコマンド解説:</strong></p>
<ul class="wp-block-list">
<li><p><code>.[]</code>: JSON配列の各要素をイテレートします。</p></li>
<li><p><code>{Name: .name, ...}</code>: 特定のフィールドを選び、新しいJSONオブジェクトとして整形します。</p></li>
<li><p><code>(.state_change_timestamp | strftime("%Y-%m-%d %H:%M:%S %Z"))</code>: タイムスタンプを読みやすい形式に変換します。</p></li>
</ul>
<h3 class="wp-block-heading">2.4. curlによる安全なリモート操作の例</h3>
<p><code>systemd-analyze</code>自体はリモート操作を行いませんが、DevOpsの文脈では、スクリプトや設定ファイルをリモートから安全に取得する際に<code>curl</code>を使用することがよくあります。ここでは、HTTPS通信での検証、再試行、バックオフを含む堅牢な<code>curl</code>コマンドの例を示します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">#!/bin/bash
# curl を用いてリモートからファイルを安全にダウンロードする例
set -euo pipefail
DOWNLOAD_URL="https://example.com/path/to/my_systemd_config.service" # 取得するファイルのURLを仮定
OUTPUT_FILE="./downloaded_config.service"
CA_BUNDLE_PATH="/etc/ssl/certs/ca-certificates.crt" # システムのCA証明書バンドル
echo "Attempting to download $DOWNLOAD_URL securely..."
# curlコマンドのオプション:
# -f, --fail-with-body: HTTPエラーコード (4xx, 5xx) を受け取った場合、エラーとして終了し、
# 可能であればエラーレスポンスボディを表示 (curl 7.76.0以降)。
# -s, --silent: 進行状況メーターやエラーメッセージを表示しない。
# -S, --show-error: --silent と組み合わせた場合にエラーメッセージを表示する。
# -L, --location: サーバーがHTTP Locationヘッダーでリダイレクトを指示した場合、その場所へ移動する。
# --retry 5: 失敗した場合、最大5回再試行する。
# --retry-delay 5: 各再試行の前に5秒間待機する。
# --retry-max-time 60: 再試行を含めた合計時間を60秒に制限する。
# --connect-timeout 10: 接続確立に10秒以上かかった場合、タイムアウトする。
# --max-time 30: 全体の操作を30秒に制限する。
# --tlsv1.2: TLSv1.2以降のみを使用する。
# --cacert "$CA_BUNDLE_PATH": 指定されたCA証明書バンドルを使用してTLSピアを検証する。
# -o "$OUTPUT_FILE": 出力を指定したファイルに保存する。
if ! curl -fSsL \
--retry 5 \
--retry-delay 5 \
--retry-max-time 60 \
--connect-timeout 10 \
--max-time 30 \
--tlsv1.2 \
--cacert "$CA_BUNDLE_PATH" \
-o "$OUTPUT_FILE" \
"$DOWNLOAD_URL"; then
echo "Error: Failed to download $DOWNLOAD_URL" >&2
exit 1
fi
echo "Successfully downloaded $OUTPUT_FILE"
echo "File content preview:"
head -n 5 "$OUTPUT_FILE"
exit 0
</pre>
</div>
<p><strong>注記</strong>: <code>example.com</code>は存在しないため、このスクリプトは成功しません。実際のURLに置き換えて使用してください。</p>
<h3 class="wp-block-heading">2.5. systemd unit/timer の例</h3>
<p>ここでは、簡単なPythonスクリプトを定期的に実行する<code>systemd</code>サービスとタイマーの例を示します。</p>
<h4 class="wp-block-heading">2.5.1. 実行するPythonスクリプト</h4>
<p><code>~/bin/my_script.py</code>として保存します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">#!/usr/bin/env python3
import datetime
import os
import sys
# ログファイルパス。ホームディレクトリのサブディレクトリに保存
log_dir = os.path.expanduser("~/logs")
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, "my_script.log")
with open(log_file, "a") as f:
f.write(f"[{datetime.datetime.now()}] My script ran successfully! User: {os.getenv('USER')} UID: {os.getuid()}\n")
print(f"[{datetime.datetime.now()}] Logged message to {log_file}")
# 実行ユーザーがrootでないことを確認
if os.getuid() == 0:
sys.exit("Error: Script run as root. This script is intended for non-root users.")
sys.exit(0)
</pre>
</div>
<p>実行権限を付与します: <code>chmod +x ~/bin/my_script.py</code></p>
<h4 class="wp-block-heading">2.5.2. systemdサービスユニット (<code>.service</code>)</h4>
<p><code>~/.config/systemd/user/my-custom-script.service</code>として保存します。これはユーザーサービスであり、root権限なしで管理できます。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">[Unit]
Description=My Custom Script Service
Documentation=https://example.com/my-script-docs
[Service]
ExecStart=/usr/bin/env python3 /home/%u/bin/my_script.py
StandardOutput=journal
StandardError=journal
Restart=on-failure
RestartSec=5s
# ユーザーサービスとして実行するため、User=を設定しない (デフォルトで実行ユーザー)
# Systemサービスとしてroot権限で実行したい場合は、User=root を設定
[Install]
WantedBy=default.target
</pre>
</div>
<ul class="wp-block-list">
<li><p><code>%u</code>: サービスを管理するユーザー名に自動的に置き換えられます。</p></li>
<li><p><code>StandardOutput=journal</code>: 標準出力をsystemdジャーナルに送ります。</p></li>
<li><p><code>WantedBy=default.target</code>: ユーザーログイン時に自動起動するよう設定します。</p></li>
</ul>
<h4 class="wp-block-heading">2.5.3. systemdタイマーユニット (<code>.timer</code>)</h4>
<p><code>~/.config/systemd/user/my-custom-script.timer</code>として保存します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">[Unit]
Description=Run My Custom Script every 5 minutes
[Timer]
OnBootSec=1min # 起動後1分で一度実行
OnUnitActiveSec=5min # サービスがアクティブになってから5分ごとに実行
Unit=my-custom-script.service # このタイマーが起動するサービス
[Install]
WantedBy=timers.target
</pre>
</div>
<ul class="wp-block-list">
<li><p><code>OnBootSec</code>: システム起動後、指定された時間後にタイマーを一度起動します。</p></li>
<li><p><code>OnUnitActiveSec</code>: 関連するサービスユニットが最後にアクティブになった後、指定された間隔でタイマーを定期的に起動します。</p></li>
<li><p><code>WantedBy=timers.target</code>: ユーザーログイン時にタイマーを自動起動するよう設定します。</p></li>
</ul>
<h4 class="wp-block-heading">2.5.4. ユニットの有効化と起動(ユーザーサービス)</h4>
<p>ユーザーサービスはroot権限なしで管理できます。</p>
<ol class="wp-block-list">
<li><p><strong>systemd設定のリロード:</strong></p>
<div class="codehilite">
<pre data-enlighter-language="generic">systemctl --user daemon-reload
</pre>
</div></li>
<li><p><strong>タイマーの有効化と起動:</strong></p>
<div class="codehilite">
<pre data-enlighter-language="generic">systemctl --user enable my-custom-script.timer
systemctl --user start my-custom-script.timer
</pre>
</div></li>
<li><p><strong>状態の確認:</strong></p>
<div class="codehilite">
<pre data-enlighter-language="generic">systemctl --user status my-custom-script.timer
systemctl --user status my-custom-script.service
</pre>
</div></li>
<li><p><strong>ログの確認:</strong></p>
<div class="codehilite">
<pre data-enlighter-language="generic">journalctl --user -u my-custom-script.service
</pre>
</div></li>
</ol>
<p>これで、起動後1分、その後5分ごとにスクリプトが自動的に実行され、ログが<code>~/logs/my_script.log</code>とジャーナルに出力されるようになります。</p>
<h2 class="wp-block-heading">3. 検証</h2>
<p>起動解析と最適化の結果は、定期的な検証によって効果を測定することが重要です。</p>
<ol class="wp-block-list">
<li><p><strong>繰り返し分析:</strong> 最適化を適用した後、<code>systemd-analyze time</code>、<code>blame</code>、<code>critical-chain</code>を再度実行し、数値が改善されたかを確認します。</p></li>
<li><p><strong>プロットの比較:</strong> <code>systemd-analyze plot</code>で生成されたSVGファイルを比較し、視覚的にボトルネックが解消されたかを確認します。</p></li>
<li><p><strong>ログの確認:</strong> <code>journalctl -f</code>などで、サービスやタイマーが期待通りに動作し、エラーがないことを確認します。</p></li>
<li><p><strong>システムリソース監視:</strong> <code>top</code>、<code>htop</code>、<code>sar</code>などのツールを使用して、最適化がCPUやメモリ使用量に予期せぬ悪影響を与えていないか監視します。</p></li>
</ol>
<h2 class="wp-block-heading">4. 運用</h2>
<p>起動解析は一度行えば終わりではありません。システムの変更やアップデートによってパフォーマンス特性は変化する可能性があります。</p>
<ul class="wp-block-list">
<li><p><strong>定期的な自動分析:</strong> 上記のBashスクリプトをCI/CDパイプラインやcronジョブとして設定し、定期的に起動時間を監視します。</p></li>
<li><p><strong>閾値監視とアラート:</strong> 起動時間が特定の閾値を超えた場合にアラートを発するよう設定し、異常を早期に検知します。</p></li>
<li><p><strong>構成管理:</strong> <code>systemd</code>ユニットファイルやスクリプトは、Gitなどのバージョン管理システムで管理し、変更履歴を追跡できるようにします。</p></li>
<li><p><strong>最小権限の原則:</strong> <code>systemd</code>ユニットやスクリプトを実行する際は、必要最小限の権限(<code>User=</code>オプションなど)を与えることで、セキュリティリスクを低減します。特に、ユーザーサービスはroot権限を必要とせず、ユーザーのホームディレクトリで隔離された環境で実行されるため、安全性が高まります。</p></li>
</ul>
<h2 class="wp-block-heading">5. トラブルシュート</h2>
<p>起動時間が遅い、またはサービスが起動しない場合の一般的なトラブルシューティング手順です。</p>
<ol class="wp-block-list">
<li><p><strong>ログの確認:</strong> <code>journalctl -xe</code> または <code>journalctl -b</code> を実行し、起動時のエラーや失敗したサービスユニットのログを確認します。特定のサービスの問題であれば <code>journalctl -u <service_name></code> を使用します。</p></li>
<li><p><strong><code>systemd-analyze blame</code> の再実行:</strong> 再度<code>blame</code>を実行し、起動が遅いユニットが変化していないか、新たなボトルネックが発生していないかを確認します。</p></li>
<li><p><strong><code>systemd-analyze critical-chain</code> の分析:</strong> 依存関係のループや、予期せぬ依存関係によって起動がブロックされていないかを確認します。</p></li>
<li><p><strong>ユニット定義の確認:</strong> <code>systemctl status <service_name></code> でユニットの状態を確認し、<code>systemctl cat <service_name></code> でユニットファイルの内容が正しいかを確認します。</p></li>
<li><p><strong>依存関係の確認:</strong> <code>systemctl list-dependencies <service_name></code> で、そのサービスが依存している他のサービスを確認します。</p></li>
<li><p><strong>ネットワークの問題:</strong> ネットワーク関連のサービスが遅い場合、DNS解決の遅延やNICドライバの問題が原因であることがあります。<code>ping</code>や<code>nslookup</code>で基本的なネットワーク接続を確認します。</p></li>
</ol>
<h3 class="wp-block-heading">起動解析フローチャート</h3>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["システム起動が遅いと感じる"] --> B{"systemd-analyze time"};
B --> C["起動時間を確認"];
C --> D{"原因を特定する"};
D -- blameでボトルネック特定 --> E["systemd-analyze blame"];
D -- critical-chainで依存関係確認 --> F["systemd-analyze critical-chain"];
D -- plotで視覚化 --> G["systemd-analyze plot"];
E --> H{"遅いユニットを特定"};
F --> I{"クリティカルパスの依存関係を特定"};
G --> J{"視覚的にボトルネックを把握"};
H & I & J --> K["最適化策の検討"];
K --> L["ユニット設定の変更"];
K --> M["スクリプトの最適化"];
L & M --> N["変更を適用"];
N --> O["システム再起動"];
O --> P{"systemd-analyze timeで改善確認"};
P -- 改善あり --> Q["最適化完了/次のボトルネックへ"];
P -- 改善なし --> D;
</pre></div>
<h2 class="wp-block-heading">6. まとめ</h2>
<p><code>systemd-analyze</code>は、Linuxシステムの起動パフォーマンスを理解し、最適化するために不可欠なツールです。<code>time</code>、<code>blame</code>、<code>critical-chain</code>、<code>plot</code>といったサブコマンドを使いこなすことで、起動プロセスの詳細を深く掘り下げ、ボトルネックを正確に特定できます。
、安全なBashスクリプトによる自動分析、<code>jq</code>を用いたJSON出力の処理、<code>curl</code>によるセキュアなリモートリソース取得の例、さらに<code>systemd</code>ユニットとタイマーを用いたサービスの管理方法までを網羅しました。これらの知識と技術を組み合わせることで、DevOpsエンジニアはより堅牢でパフォーマンスの高いLinuxシステムを構築・運用できるようになるでしょう。継続的な監視と改善のサイクルを通じて、システムの起動パフォーマンスを常に最適な状態に保つことが重要です。</p>
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
systemd-analyze を用いたLinux起動解析の完全ガイド
Linuxシステム起動時間の最適化は、DevOpsエンジニアにとって重要な課題の一つです。systemd-analyzeコマンドは、systemdを採用するLinuxシステムにおいて、起動プロセスの詳細なメトリクスを提供し、パフォーマンスボトルネックを特定するための強力なツールです。本記事では、systemd-analyzeの基本的な使い方から、安全なBashスクリプトによる自動化、jqやcurlといったツールとの連携、さらにsystemdユニット/タイマーを用いた最適化と運用・トラブルシューティングまでを網羅的に解説します。
1. 要件と前提
要件
systemdを採用したLinux環境(Ubuntu 18.04+, CentOS 7+, RHEL 7+など)。
Bashシェルが利用可能であること。
jqおよびcurlコマンドがインストールされていること。
root権限(sudo)でコマンドを実行できること。
前提
2. 実装
2.1. systemd-analyzeによる起動時間の分析
systemd-analyzeコマンドは、システムの起動時間や各ユニットの起動時間に関する詳細な情報を提供します。
2.1.1. 概要時間の確認
システム全体の起動時間を表示します。
出力例:
Startup finished in 3.456s (kernel) + 12.345s (userspace) = 15.801s
2.1.2. 各ユニットの起動時間(blame)
起動に最も時間のかかっているユニットをリストアップします。これにより、ボトルネックを特定しやすくなります。
systemd-analyze blame | head -n 10
出力例:
1.234s networkd-dispatcher.service
1.012s snapd.service
987ms dev-sda1.device
876ms systemd-journal-flush.service
765ms systemd-udev-trigger.service
654ms accounts-daemon.service
543ms systemd-logind.service
432ms systemd-resolved.service
321ms plymouth-start.service
210ms systemd-tmpfiles-setup.service
2.1.3. 起動クリティカルパス(critical-chain)
起動時間全体に影響を与える可能性のある依存関係のチェーンを表示します。
systemd-analyze critical-chain
出力例:
The time when unit became active or started is printed after the "@" character.
The time the unit took to start up is printed after the "+" character.
graphical.target @12.345s
└─multi-user.target @12.345s
└─snapd.service @10.123s +1.012s
└─basic.target @9.876s
└─sockets.target @9.876s
└─snapd.socket @9.876s
└─systemd-udevd.service @9.765s +123ms
└─systemd-journald.service @9.654s +111ms
└─systemd-remount-fs.service @9.543s +100ms
└─systemd-fsck-root.service @9.432s +99ms
└─systemd-readahead-replay.service @9.321s +88ms
└─systemd-readahead-collect.service @9.210s +77ms
2.1.4. 起動プロセスの可視化(plot)
起動プロセスをSVG形式の画像として出力します。これにより、時間の流れと依存関係を視覚的に把握できます。
systemd-analyze plot > boot_analysis_20240725.svg
生成されたSVGファイルはWebブラウザで開くことができます。
2.2. 安全なBashスクリプトの作成例
systemd-analyzeコマンドの結果を自動的に収集し、レポートを作成するスクリプトの例です。root権限が必要な操作はsudoを明示します。
#!/bin/bash
# systemd-analyzeの結果を収集し、レポートを生成するスクリプト
# 厳格なエラーハンドリング:
# -e: コマンドが失敗した場合に即座に終了
# -u: 未定義の変数を使用した場合にエラー
# -o pipefail: パイプライン中のコマンドが失敗した場合に終了
set -euo pipefail
# 一時ディレクトリの作成とクリーンアップ
# trap: スクリプト終了時(EXIT)、中断時(INT)、エラー時(ERR)に実行
tmpdir=$(mktemp -d -t systemd-analysis-XXXXXXXXXX)
trap 'rm -rf "$tmpdir"; echo "Temporary directory $tmpdir removed." >&2' EXIT INT TERM
echo "Starting systemd-analyze report generation at $(date -u +%Y-%m-%dT%H:%M:%SZ)..."
# 実行ユーザーの確認
if [ "$(id -u)" -eq 0 ]; then
echo "Running as root."
SUDO=""
else
echo "Running as non-root, using sudo for privileged commands."
SUDO="sudo"
fi
report_file="$tmpdir/systemd_boot_report_$(date +%Y%m%d%H%M%S).txt"
plot_file="$tmpdir/systemd_boot_plot_$(date +%Y%m%d%H%M%S).svg"
json_dump_file="$tmpdir/systemd_dump_$(date +%Y%m%d%H%M%S).json"
# レポートヘッダ
echo "--- systemd Boot Analysis Report (Generated on {{jst_today}}) ---" > "$report_file"
echo "" >> "$report_file"
# 1. 起動時間の概要
echo "### 1. Boot Time Summary" >> "$report_file"
"$SUDO" systemd-analyze time >> "$report_file" 2>&1
echo "" >> "$report_file"
# 2. 起動に時間のかかっているユニット上位10件
echo "### 2. Top 10 Slowest Units (systemd-analyze blame)" >> "$report_file"
"$SUDO" systemd-analyze blame | head -n 10 >> "$report_file" 2>&1
echo "" >> "$report_file"
# 3. クリティカルチェーン
echo "### 3. Critical Boot Chain (systemd-analyze critical-chain)" >> "$report_file"
"$SUDO" systemd-analyze critical-chain >> "$report_file" 2>&1
echo "" >> "$report_file"
# 4. 起動プロットの生成 (SVG)
echo "### 4. Boot Plot Generation" >> "$report_file"
echo "Generating boot plot SVG: $plot_file" >> "$report_file"
"$SUDO" systemd-analyze plot > "$plot_file" 2>&1
echo "Boot plot generated successfully." >> "$report_file"
echo "" >> "$report_file"
# 5. systemdの状態ダンプ (JSON形式)
echo "### 5. systemd State Dump (JSON)" >> "$report_file"
echo "Dumping systemd state to JSON: $json_dump_file (requires sudo)" >> "$report_file"
# systemd-analyze dump --json は非常に大量の出力を生成するため、必要に応じて使用を検討
# "$SUDO" systemd-analyze dump --json > "$json_dump_file" 2>&1
echo "To get full JSON dump, run: '$SUDO systemd-analyze dump --json > $json_dump_file'" >> "$report_file"
echo "" >> "$report_file"
echo "Report generation completed. Outputs are in: $tmpdir"
echo "Report: $report_file"
echo "Plot: $plot_file"
echo "To view plot: xdg-open $plot_file (or open in web browser)"
# スクリプトが正常終了した場合、一時ディレクトリの削除はtrapで実行される
exit 0
実行方法:
上記のコードをanalyze_boot.shとして保存します。
chmod +x analyze_boot.sh で実行権限を付与します。
./analyze_boot.sh で実行します。必要に応じてsudoプロンプトが表示されます。
2.3. jq を用いたJSON処理
systemd-analyzeコマンドの中には、systemd-analyze dump --jsonのようにJSON形式の出力をサポートするものがあります。これをjqで処理する例を示します。
#!/bin/bash
# systemd-analyze dumpのJSON出力をjqで解析するスクリプト
set -euo pipefail
# systemd-analyze dump --json はroot権限が必要
if [ "$(id -u)" -ne 0 ]; then
echo "This script requires root privileges. Please run with sudo." >&2
exit 1
fi
echo "Fetching systemd dump in JSON format..."
# systemdの状態をJSONで取得
# 実際の環境では出力が非常に大きい可能性があるため、ここでは最初の数行のみを例示
# 全ての出力を解析する場合は、`head -n 10`を削除してください
# NOTE: systemd-analyze dump --jsonはすべてのユニット情報を出力するため、非常に巨大なJSONとなる。
# テスト目的では `systemd-analyze unit --json NetworkManager.service` のように特定のユニットを使う方が適切。
# ここではdumpの例として扱いつつ、注意喚起を行う。
systemd_dump_json=$(sudo systemd-analyze dump --json | head -n 50) # 例として一部のみ取得
echo "Parsing JSON output with jq..."
# jqを使って、units配列の最初の2つのユニット名と状態を抽出する例
# 実際の `systemd-analyze dump --json` の出力構造は複雑なため、
# ここでは概念的な例として、一般的なsystemdユニット情報の抽出を示す。
# より実用的な例として、`systemd-analyze unit <unit_name> --json` を推奨する。
# 例: systemd-analyze unit systemd-journald.service --json
# ここでは、簡略化のため、`systemd-analyze unit <unit_name> --json` の出力形式を想定してjqコマンドを記述します。
# 実際には、`systemd-analyze dump --json` の出力はトップレベルに 'version', 'units' などを含む辞書形式です。
# 例として、`systemd-analyze unit systemd-journald.service --json` を直接実行し、その結果を処理します。
echo "--- Analyzing systemd-journald.service with jq ---"
sudo systemd-analyze unit systemd-journald.service --json | jq '.[] | {Name: .name, Description: .description, LoadState: .load_state, ActiveState: .active_state, SubState: .sub_state}'
echo ""
echo "--- Analyzing networkd-dispatcher.service with jq ---"
sudo systemd-analyze unit networkd-dispatcher.service --json | jq '.[] | {Name: .name, Description: .description, LoadState: .load_state, ActiveState: .active_state, SubState: .sub_state, StateChangeTimestamp: (.state_change_timestamp | strftime("%Y-%m-%d %H:%M:%S %Z"))}'
echo ""
echo "jq examples completed."
jqコマンド解説:
.[]: JSON配列の各要素をイテレートします。
{Name: .name, ...}: 特定のフィールドを選び、新しいJSONオブジェクトとして整形します。
(.state_change_timestamp | strftime("%Y-%m-%d %H:%M:%S %Z")): タイムスタンプを読みやすい形式に変換します。
2.4. curlによる安全なリモート操作の例
systemd-analyze自体はリモート操作を行いませんが、DevOpsの文脈では、スクリプトや設定ファイルをリモートから安全に取得する際にcurlを使用することがよくあります。ここでは、HTTPS通信での検証、再試行、バックオフを含む堅牢なcurlコマンドの例を示します。
#!/bin/bash
# curl を用いてリモートからファイルを安全にダウンロードする例
set -euo pipefail
DOWNLOAD_URL="https://example.com/path/to/my_systemd_config.service" # 取得するファイルのURLを仮定
OUTPUT_FILE="./downloaded_config.service"
CA_BUNDLE_PATH="/etc/ssl/certs/ca-certificates.crt" # システムのCA証明書バンドル
echo "Attempting to download $DOWNLOAD_URL securely..."
# curlコマンドのオプション:
# -f, --fail-with-body: HTTPエラーコード (4xx, 5xx) を受け取った場合、エラーとして終了し、
# 可能であればエラーレスポンスボディを表示 (curl 7.76.0以降)。
# -s, --silent: 進行状況メーターやエラーメッセージを表示しない。
# -S, --show-error: --silent と組み合わせた場合にエラーメッセージを表示する。
# -L, --location: サーバーがHTTP Locationヘッダーでリダイレクトを指示した場合、その場所へ移動する。
# --retry 5: 失敗した場合、最大5回再試行する。
# --retry-delay 5: 各再試行の前に5秒間待機する。
# --retry-max-time 60: 再試行を含めた合計時間を60秒に制限する。
# --connect-timeout 10: 接続確立に10秒以上かかった場合、タイムアウトする。
# --max-time 30: 全体の操作を30秒に制限する。
# --tlsv1.2: TLSv1.2以降のみを使用する。
# --cacert "$CA_BUNDLE_PATH": 指定されたCA証明書バンドルを使用してTLSピアを検証する。
# -o "$OUTPUT_FILE": 出力を指定したファイルに保存する。
if ! curl -fSsL \
--retry 5 \
--retry-delay 5 \
--retry-max-time 60 \
--connect-timeout 10 \
--max-time 30 \
--tlsv1.2 \
--cacert "$CA_BUNDLE_PATH" \
-o "$OUTPUT_FILE" \
"$DOWNLOAD_URL"; then
echo "Error: Failed to download $DOWNLOAD_URL" >&2
exit 1
fi
echo "Successfully downloaded $OUTPUT_FILE"
echo "File content preview:"
head -n 5 "$OUTPUT_FILE"
exit 0
注記: example.comは存在しないため、このスクリプトは成功しません。実際のURLに置き換えて使用してください。
2.5. systemd unit/timer の例
ここでは、簡単なPythonスクリプトを定期的に実行するsystemdサービスとタイマーの例を示します。
2.5.1. 実行するPythonスクリプト
~/bin/my_script.pyとして保存します。
#!/usr/bin/env python3
import datetime
import os
import sys
# ログファイルパス。ホームディレクトリのサブディレクトリに保存
log_dir = os.path.expanduser("~/logs")
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, "my_script.log")
with open(log_file, "a") as f:
f.write(f"[{datetime.datetime.now()}] My script ran successfully! User: {os.getenv('USER')} UID: {os.getuid()}\n")
print(f"[{datetime.datetime.now()}] Logged message to {log_file}")
# 実行ユーザーがrootでないことを確認
if os.getuid() == 0:
sys.exit("Error: Script run as root. This script is intended for non-root users.")
sys.exit(0)
実行権限を付与します: chmod +x ~/bin/my_script.py
2.5.2. systemdサービスユニット (.service)
~/.config/systemd/user/my-custom-script.serviceとして保存します。これはユーザーサービスであり、root権限なしで管理できます。
[Unit]
Description=My Custom Script Service
Documentation=https://example.com/my-script-docs
[Service]
ExecStart=/usr/bin/env python3 /home/%u/bin/my_script.py
StandardOutput=journal
StandardError=journal
Restart=on-failure
RestartSec=5s
# ユーザーサービスとして実行するため、User=を設定しない (デフォルトで実行ユーザー)
# Systemサービスとしてroot権限で実行したい場合は、User=root を設定
[Install]
WantedBy=default.target
%u: サービスを管理するユーザー名に自動的に置き換えられます。
StandardOutput=journal: 標準出力をsystemdジャーナルに送ります。
WantedBy=default.target: ユーザーログイン時に自動起動するよう設定します。
2.5.3. systemdタイマーユニット (.timer)
~/.config/systemd/user/my-custom-script.timerとして保存します。
[Unit]
Description=Run My Custom Script every 5 minutes
[Timer]
OnBootSec=1min # 起動後1分で一度実行
OnUnitActiveSec=5min # サービスがアクティブになってから5分ごとに実行
Unit=my-custom-script.service # このタイマーが起動するサービス
[Install]
WantedBy=timers.target
OnBootSec: システム起動後、指定された時間後にタイマーを一度起動します。
OnUnitActiveSec: 関連するサービスユニットが最後にアクティブになった後、指定された間隔でタイマーを定期的に起動します。
WantedBy=timers.target: ユーザーログイン時にタイマーを自動起動するよう設定します。
2.5.4. ユニットの有効化と起動(ユーザーサービス)
ユーザーサービスはroot権限なしで管理できます。
systemd設定のリロード:
systemctl --user daemon-reload
タイマーの有効化と起動:
systemctl --user enable my-custom-script.timer
systemctl --user start my-custom-script.timer
状態の確認:
systemctl --user status my-custom-script.timer
systemctl --user status my-custom-script.service
ログの確認:
journalctl --user -u my-custom-script.service
これで、起動後1分、その後5分ごとにスクリプトが自動的に実行され、ログが~/logs/my_script.logとジャーナルに出力されるようになります。
3. 検証
起動解析と最適化の結果は、定期的な検証によって効果を測定することが重要です。
繰り返し分析: 最適化を適用した後、systemd-analyze time、blame、critical-chainを再度実行し、数値が改善されたかを確認します。
プロットの比較: systemd-analyze plotで生成されたSVGファイルを比較し、視覚的にボトルネックが解消されたかを確認します。
ログの確認: journalctl -fなどで、サービスやタイマーが期待通りに動作し、エラーがないことを確認します。
システムリソース監視: top、htop、sarなどのツールを使用して、最適化がCPUやメモリ使用量に予期せぬ悪影響を与えていないか監視します。
4. 運用
起動解析は一度行えば終わりではありません。システムの変更やアップデートによってパフォーマンス特性は変化する可能性があります。
定期的な自動分析: 上記のBashスクリプトをCI/CDパイプラインやcronジョブとして設定し、定期的に起動時間を監視します。
閾値監視とアラート: 起動時間が特定の閾値を超えた場合にアラートを発するよう設定し、異常を早期に検知します。
構成管理: systemdユニットファイルやスクリプトは、Gitなどのバージョン管理システムで管理し、変更履歴を追跡できるようにします。
最小権限の原則: systemdユニットやスクリプトを実行する際は、必要最小限の権限(User=オプションなど)を与えることで、セキュリティリスクを低減します。特に、ユーザーサービスはroot権限を必要とせず、ユーザーのホームディレクトリで隔離された環境で実行されるため、安全性が高まります。
5. トラブルシュート
起動時間が遅い、またはサービスが起動しない場合の一般的なトラブルシューティング手順です。
ログの確認: journalctl -xe または journalctl -b を実行し、起動時のエラーや失敗したサービスユニットのログを確認します。特定のサービスの問題であれば journalctl -u <service_name> を使用します。
systemd-analyze blame の再実行: 再度blameを実行し、起動が遅いユニットが変化していないか、新たなボトルネックが発生していないかを確認します。
systemd-analyze critical-chain の分析: 依存関係のループや、予期せぬ依存関係によって起動がブロックされていないかを確認します。
ユニット定義の確認: systemctl status <service_name> でユニットの状態を確認し、systemctl cat <service_name> でユニットファイルの内容が正しいかを確認します。
依存関係の確認: systemctl list-dependencies <service_name> で、そのサービスが依存している他のサービスを確認します。
ネットワークの問題: ネットワーク関連のサービスが遅い場合、DNS解決の遅延やNICドライバの問題が原因であることがあります。pingやnslookupで基本的なネットワーク接続を確認します。
起動解析フローチャート
graph TD
A["システム起動が遅いと感じる"] --> B{"systemd-analyze time"};
B --> C["起動時間を確認"];
C --> D{"原因を特定する"};
D -- blameでボトルネック特定 --> E["systemd-analyze blame"];
D -- critical-chainで依存関係確認 --> F["systemd-analyze critical-chain"];
D -- plotで視覚化 --> G["systemd-analyze plot"];
E --> H{"遅いユニットを特定"};
F --> I{"クリティカルパスの依存関係を特定"};
G --> J{"視覚的にボトルネックを把握"};
H & I & J --> K["最適化策の検討"];
K --> L["ユニット設定の変更"];
K --> M["スクリプトの最適化"];
L & M --> N["変更を適用"];
N --> O["システム再起動"];
O --> P{"systemd-analyze timeで改善確認"};
P -- 改善あり --> Q["最適化完了/次のボトルネックへ"];
P -- 改善なし --> D;
6. まとめ
systemd-analyzeは、Linuxシステムの起動パフォーマンスを理解し、最適化するために不可欠なツールです。time、blame、critical-chain、plotといったサブコマンドを使いこなすことで、起動プロセスの詳細を深く掘り下げ、ボトルネックを正確に特定できます。
、安全なBashスクリプトによる自動分析、jqを用いたJSON出力の処理、curlによるセキュアなリモートリソース取得の例、さらにsystemdユニットとタイマーを用いたサービスの管理方法までを網羅しました。これらの知識と技術を組み合わせることで、DevOpsエンジニアはより堅牢でパフォーマンスの高いLinuxシステムを構築・運用できるようになるでしょう。継続的な監視と改善のサイクルを通じて、システムの起動パフォーマンスを常に最適な状態に保つことが重要です。
コメント