systemd-analyzeでLinuxの起動時間を最適化:サーバーブートのプロファイリングと高速化

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

背景と目的

クラウドインスタンスの予期せぬ再起動や、50台規模のノードに対するカーネルパッチ適用時など、SSH接続が可能になるまでの待ち時間は直接的なコストにつながります。ダウンタイムにおける1秒の遅れも無視できません。特にオートスケーリンググループやフェイルオーバークラスターでは、サーバーの初期化が遅れるとヘルスチェックが遅延し、流入するトラフィックが滞留してしまいます。

現代の主要なLinuxディストリビューションの多くは、バックグラウンドでsystemdを採用しています。従来のSysVinitから大幅に進化し、サービスを並行して起動できるようになりましたが、デフォルト構成のまま運用を続けると不要な処理が蓄積しがちです。不要なネットワークターゲットの待機やストレージのタイムアウト、不要なバックグラウンドデーモンによって、本来8秒で完了するはずの再起動が90秒以上もかかってしまうケースは珍しくありません。

どのサービスが起動を遅らせているのか、推測に頼るのはやめましょう。正確な数値データを活用することが重要です。標準搭載されているsystemd-analyzeツールを使用すれば、ユニットの依存関係をプロファイリングし、ボトルネックとなっているクリティカルチェーンを特定して、ミリ秒単位でリソースを消費している要因を可視化できます。

インストール

朗報として、systemd-analyzeはUbuntu、Debian、AlmaLinux、Rocky Linux、Fedoraなど、ほぼすべての主要なディストリビューションの基本systemdパッケージに同梱されています。別途追加ソフトウェアをインストールする必要はほとんどありません。

環境でツールが利用可能かどうか確認します:

systemd-analyze --version

補助ユーティリティが省かれた最小構成のクラウドイメージや軽量コンテナを使用している場合は、パッケージマネージャーからコアパッケージをインストールしてください。

DebianおよびUbuntuの場合:

sudo apt update
sudo apt install -y systemd

RHEL、AlmaLinux、Rocky Linuxの場合:

sudo dnf install -y systemd

設定とプロファイリング

ブート時間のチューニングは、「ベースラインの計測」「真のボトルネックの特定」「不要なものの無効化」「検証」という4つのステップで行います。

1. 全体の起動時間の確認

まずは、直近の起動におけるベースラインメトリクスを取得します:

systemd-analyze time

以下のように3つのステージに分かれた出力が表示されます:

Startup finished in 5.120s (kernel) + 2.840s (initrd) + 21.430s (userspace) = 29.390s
graphical.target reached after 21.390s in userspace

userspaceの数値は、カーネルから制御が引き渡された後に読み込まれたすべてのサービス、デーモン、スクリプトの所要時間を表しています。この例では、全体の起動時間29.4秒のうちuserspaceが21.4秒を占めています。この領域こそが管理者が直接制御できる部分であり、最も改善効果を得やすいポイントです。

2. blameによる起動の遅いユニットの特定

次に、初期化に時間がかかっているアクティブなユニットをランキング形式で一覧表示します:

systemd-analyze blame | head -n 15

一般的なサーバーでは、次のようなユニットが上位に現れる傾向があります:

12.421s NetworkManager-wait-online.service
 8.114s cloud-init.service
 3.650s snapd.service
 2.102s apt-daily.service
 1.218s polkit.service

ここで慌てる必要はありません。あるサービスが12秒かかっているからといって、全体の起動が必ず12秒遅延しているとは限らないためです。

3. クリティカルチェーンの追跡

systemdはサービスを並行して起動するため、独立したバックグラウンドタスクはユーザーログインを妨げることなくバックグラウンドで動作します。サーバーが実際にmulti-user.targetに到達するのを妨げている一連の直列チェーン(クリティカルパス)を特定するには、次のように実行します:

systemd-analyze critical-chain multi-user.target

表示されるツリーによって、依存関係にある各ユニットがいつ起動し、ターゲット到達をどれだけ遅延させたかが判明します:

multi-user.target @21.390s
└─nginx.service @20.120s +1.250s
  └─network-online.target @20.100s
    └─NetworkManager-wait-online.service @7.680s +12.420s
      └─NetworkManager.service @6.100s +1.580s

NetworkManager-wait-online.serviceの+12.420sという項目に注目してください。このユニット1つがnetwork-online.targetをブロックし、結果としてNginxの起動を遅らせ、起動時間全体を12秒以上も引き延ばしていたことがわかります。

4. インタラクティブなブートチャートの生成

視覚的に把握したい場合は、起動シーケンス全体をベクターグラフィック(SVG)として出力できます:

systemd-analyze plot > /tmp/boot_analysis.svg

生成されたSVGファイルをブラウザで開くと、CPUスパイク、ディスクI/Oアクティビティ、各実行中サービスの開始から終了までのバーがウォーターフォール形式のタイムラインで視覚的に綺麗にレンダリングされます。

5. 不要なサービスの削減とマスク

ボトルネックとなっている候補を特定したら、計画的に排除していきます。

NetworkManager-wait-onlineの無効化:
多くの本番サーバーがNetworkManager-wait-online.serviceで待たされています。デフォルトでは、すべてのネットワークインターフェースがキャリア状態になるまで処理をブロックします。サーバーが固定IPやeth0での一般的なクラウドDHCPを使用している場合は、このユニットをマスクすることで起動時間を即座に10〜15秒短縮できます:

sudo systemctl disable NetworkManager-wait-online.service
sudo systemctl mask NetworkManager-wait-online.service

プロビジョニング済みインスタンスでのCloud-Initの無効化:
cloud-initは初回起動時にSSHキーの配置やドライブのパーティション分割を行うために不可欠です。しかし、それ以降の再起動では、HTTP経由でクラウドのメタデータエンドポイントに問い合わせを行うことで、6〜10秒もの無駄な待機時間が発生します。プロビジョニングが完了したら、不要なチェックを無効化しましょう:

# 以降の再起動でcloud-initをスキップ
sudo touch /etc/cloud/cloud-init.disabled

Ubuntu 22.04のクラウドノード(2 vCPU、4GB RAM)での検証では、不要なネットワーク待機をマスクし、cloud-initを無効化することで、userspaceの合計起動時間が21.4秒からわずか4.8秒へと短縮されました。

周辺機器およびデスクトップ向けサービスのマスク:
ヘッドレスなクラウドインスタンスやラックマウントサーバーでは、Bluetoothやスマートカード、モバイル通信モデムのサービスは不要です。これらを完全にマスクして、間接的な依存関係としてもsystemdが起動しないようにします:

sudo systemctl mask ModemManager.service
sudo systemctl mask bluetooth.service
sudo systemctl mask cups.service

6. ハングしたサービスのタイムアウト短縮

デフォルトでは、systemdは起動時やシャットダウン時に応答のないサービスに対して90秒の猶予期間を設けています。バックグラウンドワーカーがハングした場合、再起動プロセス全体が1分半も停止してしまいます。/etc/systemd/system.confで制限時間を15秒に短縮します:

sudo sed -i 's/#DefaultTimeoutStartSec=90s/DefaultTimeoutStartSec=15s/' /etc/systemd/system.conf
sudo sed -i 's/#DefaultTimeoutStopSec=90s/DefaultTimeoutStopSec=15s/' /etc/systemd/system.conf
sudo systemctl daemon-reload

検証とモニタリング

サーバーを再起動し、実際の稼働環境で改善効果を測定します:

sudo reboot

ホストに再接続できたら、再度systemd-analyzeを実行してベースラインの数値と比較します:

systemd-analyze time

意図しない副作用が発生していないか確認します。マスクした依存関係の影響で失敗したサービスがないことを確認してください:

systemctl --failed

システムジャーナルを確認し、不要なサービスの整理によって警告や循環依存が発生していないかチェックします:

journalctl -b -p warning..err

大規模なカーネルやディストリビューションのアップグレードを適用する前に、新しいブートプロット(SVG)のコピーを保存しておきましょう。過去のSVGチャートと比較することで、起動時間を遅延させる問題のあるデーモンを本番環境で顕在化する前に検知できます。

Share: