失敗したSystemdサービスのトラブルシューティング:システム管理者のための実践ガイド

Systemdサービスは現代のLinuxシステムの基盤ですが、失敗することがあります。この実践ガイドは、システム管理者が一般的なsystemdサービスの障害を体系的にトラブルシューティングし解決する力を与えます。`journalctl`を使ったログ分析、依存関係の問題の診断、終了コードの解釈、Webサーバーやデータベースなどに対する具体的な修正を学び、サービス機能を迅速に復元します。

失敗したSystemdサービスのトラブルシューティング:システム管理者のための実践ガイド

失敗したsystemdサービスは、通常、最初に見たときよりも謎めいていません。有用な証拠はすでにマシン上にあります。ユニット定義、systemdが実行しようとした正確なコマンド、終了ステータス、そして障害周辺のジャーナル行です。秘訣は、サービスを10回再起動してメッセージが変わることを期待するのではなく、正しい順序でそれらを読むことです。

私は通常、3つの質問から始めます。systemdはユニットを見つけましたか、プロセスは開始しましたか、アプリケーション自体が設定や環境を拒否しましたか?以下のコマンドは、その調査を具体的に保ちます。

Systemdサービスの障害を理解する

systemdサービスが起動に失敗したり、予期せずクラッシュしたりする場合、その原因は多岐にわたります。単純な設定ミス、依存関係の欠如、リソースの制限、サービス自体のバグなどが考えられます。Systemdは、これらの障害の正確な原因を特定するための堅牢なメカニズムを提供します。

サービス障害の一般的な原因:

  • 設定エラー: サービスの.serviceユニットファイルまたは関連する設定ファイルの設定が正しくない。
  • 依存関係の欠如: サービスが他のシステムリソース(ネットワーク、他のサービス、特定のファイルシステムなど)に依存しているが、それらが利用できないか、まだ起動していない。
  • リソースの枯渇: サービスが必要とするメモリ、CPU、またはディスクI/Oをシステムが提供できない。
  • 権限の問題: サービスプロセスに、必要なファイル、ディレクトリ、またはネットワークポートにアクセスするための適切な権限がない。
  • サービスのバグ: アプリケーション自体に、起動時または動作中にクラッシュを引き起こすバグがある。
  • データの破損: サービスが使用する重要なデータファイルが破損している。
  • ネットワークの問題: ネットワークインターフェース、DNS、またはファイアウォールルールに問題があり、サービスがポートにバインドしたり通信したりできない。

ステップ1:サービスの状態を確認する

失敗したサービスのトラブルシューティングの最初のステップは、現在の状態を確認することです。systemdのsystemctlコマンドがこれに最適なツールです。

systemctl statusの使用

systemctl status <サービス名>.serviceコマンドは、サービスの現在の状態、最近のログエントリ、プロセス情報の簡潔な概要を提供します。

sudo systemctl status nginx.service

出力例(失敗したサービス):

● nginx.service - A high performance web server and reverse proxy
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: failed (result=exit-code) since Tue 2023-10-27 10:30:00 UTC; 1min ago
       Docs: man:nginx(8)
    Process: 1234 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=1/FAILURE)
   Main PID: 1234 (code=exited, status=1/FAILURE)

Oct 27 10:30:00 your-server systemd[1]: Starting A high performance web server and reverse proxy...
Oct 27 10:30:00 your-server nginx[1234]: nginx: [emerg] bind() to port 80 failed (98: Address already in use)
Oct 27 10:30:00 your-server systemd[1]: nginx.service: Main process exited, code=exited, status=1/FAILURE
Oct 27 10:30:00 your-server systemd[1]: Failed to start A high performance web server and reverse proxy.

systemctl statusの出力で注目すべき重要な情報:

  • Active:: この行は現在の状態を示します。failedが注目すべき状態です。failed (result=exit-code)failed (result=oom-kill)と表示されることもあります。resultは手がかりを提供することがよくあります。
  • Process:: systemdが実行しようとしたプロセスの詳細。code=exited, status=...と表示されている場合、これは重要です。
  • ログエントリ: 最新のログ行には、サービスからの直接のエラーメッセージが含まれていることがよくあります。

ステップ2:journalctlでログを分析する

journalctlコマンドは、systemdジャーナルからログを照会および表示するための強力なツールです。サービスが失敗した理由についての詳細な洞察を得るために不可欠です。

サービス向けの基本的なjournalctlの使用法

特定のサービスのログを表示するには、-uフラグを使用します:

sudo journalctl -u <サービス名>.service

リアルタイムでログを追跡するには:

sudo journalctl -f -u <サービス名>.service

最後の起動からのログを表示するには(起動中に失敗したサービスに便利):

sudo journalctl -b -u <サービス名>.service

特定の時刻以降のログを表示するには:

sudo journalctl --since "2023-10-27 10:00:00" -u <サービス名>.service

journalctlの出力を解釈する

アプリケーションまたはsystemd自体によって報告されたエラーメッセージ、スタックトレース、または特定のエラーコードを探します。systemctl statusの出力例では、すでに重要なエラーが示されていました:bind() to port 80 failed (98: Address already in use)。これは、別のプロセスがすでにポート80を使用しており、Nginxの起動を妨げていることを明確に示しています。

ヒント: サービスが非常に冗長な場合は、出力を制限できます:

sudo journalctl -n 50 -u <サービス名>.service  # 最後の50行を表示

ステップ3:サービスの依存関係と要件を確認する

Systemdサービスは、多くの場合、他のサービスやシステムリソースが利用可能であることに依存しています。依存関係が満たされない場合、サービスは起動しません。

依存関係の表示

systemctl catを使用してサービスの依存関係を調べ、Requires=Wants=After=Before=PartOf=などのディレクティブを確認できます。

systemctl cat <サービス名>.service

例えば、特定のアドレスにバインドするサービスは、ネットワークが設定された後に順序付けられる必要があるかもしれません。After=network-online.targetは順序のみを制御します。それ自体では、そのターゲットをトランザクションにプルしません。サービスが本当にそれを必要とする場合、多くの場合、両方が表示されます:

Wants=network-online.target
After=network-online.target

Requires=は控えめに使用してください。これはより強力な関係を作成し、必要なユニットが停止したときにサービスを停止させる可能性があります。多くのアプリケーションサービスは、Wants=After=のみを必要とします。

欠落している依存関係の確認

systemctl statusは依存関係の問題を示すことがよくありますが、必要なサービスがアクティブであるかどうかを明示的に確認すると役立ちます。

systemctl is-active <依存サービス名>.service

必要なサービスがマスクされているか停止されている場合、対象のサービスが起動しなくなる可能性があります。

systemctl list-dependencies <サービス名>.service

このコマンドは、完全な依存関係ツリーを表示します。

ステップ4:終了コードを理解する

サービスが失敗すると、特定の終了コードで終了します。このコードは、障害の性質に関する貴重な情報を提供します。

  • 終了コード0: 成功。
  • 終了コード1: 多くのプログラムにおける一般的な失敗。具体的な意味はアプリケーションによって異なります。
  • 終了コード127: コマンドが見つかりません(多くの場合、ExecStartパスが正しくないか、実行可能ファイルが見つからないため)。
  • 終了コード137: SIGKILLによって強制終了されました。これは多くの場合、メモリプレッシャーに関連していますが、常にそうとは限りません。
  • 終了コード139: SIGSEGV(セグメンテーションフォールト)によって強制終了されました。

systemctl statusの出力から、status=1/FAILUREが確認できました。これは一般的な失敗であり、なぜステータス1で失敗したかを理解するには、先行するログメッセージが不可欠です。

OOMキルの特定

systemctl statusfailed (result=oom-kill)を示している場合、LinuxのOut-Of-Memory(OOM)キラーが、システムのメモリが極端に不足していたためにサービスのプロセスを終了したことを意味します。

これを確認するには、journalctlまたはdmesgで関連するメッセージを見つけることができます:

dmesg | grep -i oom

OOMエラーのトラブルシューティング

  • システムRAMを増やす: 可能であれば。
  • メモリ使用量を減らす: サービスまたは他の実行中のプロセスを最適化します。
  • スワップを設定する: 十分なスワップスペースが利用可能であることを確認します。
  • サービスのメモリ制限を確認する: MemoryMax=設定により、ホストにまだ空きメモリがある場合でも、サービス固有のOOMが発生する可能性があります。
  • 最近のデプロイを確認する: メモリ障害は、多くの場合、設定変更、トラフィック変更、またはバージョン変更に続いて発生します。

ステップ5:Systemdが実際に使用しているユニットファイルを確認する

エディタで開いているファイルが完全なユニットであると想定しないでください。パッケージ、ドロップイン、オーバーライドが組み合わさって最終的な定義になることがあります:

systemctl cat <サービス名>.service
systemctl show <サービス名>.service -p FragmentPath -p DropInPaths

これにより、一般的な問題をキャッチできます。誰かが/usr/lib/systemd/system/app.serviceを編集したが、/etc/systemd/system/app.service.d/override.confのオーバーライドがまだEnvironment=ExecStart=を変更している場合です。

ユニットファイルまたはドロップインを編集した後は、systemdをリロードします:

sudo systemctl daemon-reload

この手順を忘れると、systemctl restartが古いユニット定義を使い続ける可能性があります。

ステップ6:一般的なサービス固有の問題と修正

上記の手順は一般的ですが、特定のサービスには一般的な障害モードがあります。

Webサーバー(Nginx、Apache)

  • ポートがすでに使用中: 例で見たように、別のプロセスがポート80または443をリッスンしている可能性があります。sudo ss -tulnp | grep :80を使用して、問題のプロセスを見つけます。
  • 設定の構文エラー: Webサーバーの設定テストを実行します(例:sudo nginx -tまたはsudo apachectl configtest)。
  • SSL証明書の欠落: 証明書ファイルが存在し、読み取り可能であることを確認します。

データベース(MySQL、PostgreSQL)

  • データディレクトリの権限: データベースユーザーがデータディレクトリに対して正しい読み取り/書き込みアクセス権を持っていることを確認します。
  • データファイルの破損: バックアップからの復元や、データベース固有のリカバリツールの使用が必要になる場合があります。
  • ディスク容量不足: データベースはかなりのディスク容量を消費する可能性があります。

ネットワークサービス

  • 不正なIPアドレスまたはホスト名: ネットワーク設定を確認します。
  • ファイアウォールルール: 必要なポートが開いていることを確認します。
  • DNS解決の問題: /etc/resolv.confとネットワーク接続を確認します。

ステップ7:高度なトラブルシューティングテクニック

サービスの再有効化と再起動

変更を行った後、必要に応じてユニットをリロードし、サービスを再起動します。起動時の動作を変更しない限り、毎回enableを実行する必要はありません。

sudo systemctl daemon-reload # systemdマネージャー設定をリロード
sudo systemctl restart <サービス名>.service

systemctl --failedの使用

このコマンドは、現在失敗状態にあるすべてのユニットを一覧表示します。

systemctl --failed

リソース制限の確認(ulimit

一部のサービスは、オペレーティングシステムレベルのリソース制限に達した場合に失敗することがあります。サービスが実行されるユーザーとしてulimit -aで制限を確認するか、ユニットファイル内のsystemd独自のリソース制御ディレクティブを確認します。

systemd管理のサービスの場合、インタラクティブシェルのulimitよりもユニットプロパティの方が関連性が高いことがよくあります:

systemctl show <サービス名>.service -p LimitNOFILE -p User -p Group -p MemoryMax -p TasksMax

アプリケーションがtoo many open filesと表示する場合は、LimitNOFILEをアプリケーションの接続数とファイル使用量と比較します。サービスがスレッドや子プロセスを作成できない場合は、TasksMaxを確認します。

デバッグフラグ

多くのアプリケーションには、.serviceファイルのExecStart行でコマンドライン引数を介して有効にできるデバッグモードや詳細ログがあります。アプリケーションのドキュメントを参照してください。

簡単な例:サービスは手動で動作するが、起動時に失敗する

これは最も一般的なsystemdの不満の1つです。開発者が手動でコマンドを実行すると動作します。同じコマンドがサービスとして失敗します。通常の違いは環境です。

サービスのユーザーと作業ディレクトリを確認します:

systemctl show myapp.service -p User -p Group -p WorkingDirectory
systemctl cat myapp.service

次に、アプリケーション内の前提条件を探します:相対パス、ホームディレクトリ内のファイル、.bashrcからの環境変数、またはインタラクティブシェルによってロードされた資格情報。systemdはサービスのためにシェルのスタートアップファイルを読み取りません。アプリケーションがAPP_ENV=productionDATABASE_URL=...を必要とする場合は、その設定をEnvironment=EnvironmentFile=、または通常のシークレット管理パスを使用してユニットに配置します。

起動時のみの障害は、順序の問題である可能性もあります。サービスは、DNS、ネットワークアドレス、またはマウントされたファイルシステムの準備ができる前に起動する場合があります。アプリケーションで盲目的なスリープを使用してこれを修正しないでください。ユニットで依存関係を表現します:

Wants=network-online.target
After=network-online.target
RequiresMountsFor=/srv/myapp

RequiresMountsFor=は、サービスが特定のパスを必要とする場合、特にそのパスが別のディスクやネットワークマウントからのものである場合に便利です。これは、広範なターゲットがたまたま先に終了することを期待するよりも明確です。

失敗状態のリセット

サービスが失敗した後、systemdはリセットされるかユニットが成功するまで失敗状態を記憶します。これは可視性のために役立ちますが、問題を修正した後にステータスチェックを混乱させる可能性があります:

sudo systemctl reset-failed myapp.service
sudo systemctl restart myapp.service
systemctl status myapp.service

必要な証拠を取得した後にreset-failedを使用します。インシデント中は、失敗状態とジャーナルのタイムスタンプが有用な手がかりとなります。

もう1つ、ノイズの多い障害の後に役立つ小さな習慣があります。何かを編集する前に、ユニットが再起動ループしていないか確認します。

systemctl show myapp.service -p NRestarts -p RestartUSec

再起動回数が急速に増加している場合は、調査中にユニットを停止します。これにより、依存関係が繰り返しの不良接続から保護され、ジャーナルが読みやすく保たれます。

信頼できるパターンは次のとおりです:statusを読み、ジャーナルを読み、systemctl catで有効なユニットを検査し、依存関係とパスを確認し、何が変更されたかを把握した後にのみ再起動します。これにより、systemdのトラブルシューティングが退屈なものになり、それはまさに障害時に望むことです。