一般的なSystemdサービスの障害を効果的にトラブルシューティングする方法

systemctl、journalctl、終了コード、ユニットチェック、実践的な修復手順を用いて、一般的なsystemdサービスの障害を診断します。

一般的なSystemdサービスの障害を効果的にトラブルシューティングする方法

ほとんどのsystemdサービスの障害は、3つの質問を分けて考えることで謎ではなくなります。systemdはユニットファイルを正しく読み取ったか、コマンドを実行できたか、アプリケーションは起動後に正常に動作し続けているか。これらは異なる障害ポイントであり、それぞれ異なる手がかりを残します。

私が最もよく見かける間違いは、すぐにユニットファイルを編集し始めることです。まずステータスとログを読みましょう。失敗したサービスは通常、実行ファイルの欠落、ユーザーの誤り、権限の問題、依存関係の順序の問題、アプリケーションのクラッシュのいずれかを教えてくれます。正確な文言が重要です。


必須の診断ツールキット

効果的なトラブルシューティングは、サービスの状態と運用ログに関する即時フィードバックを提供する2つの主要なsystemdツールに依存しています。

1. サービスのステータスを確認する

systemctl status コマンドは、ユニットの状態、最近のログ、プロセスID(PID)や終了コードなどの重要なメタデータを含む、ユニットの状態の即時スナップショットを提供します。

$ systemctl status myapp.service

確認すべき重要な情報:

  • Load: ユニットファイルが正しく読み取られたことを確認します。loaded は良好です。not found と表示された場合、サービスファイルの場所が間違っているか、スペルミスがあります。
  • Active: これがコアステータスです。failed と表示された場合、サービスは起動を試みましたが、予期せず終了しました。
  • Exit Code: この数値コードは、多くの場合 Active: failed と一緒に表示され、非常に重要です。プロセスが終了した理由を示します(例:0は正常終了、1または2は一般的なアプリケーションエラー、203は実行パスエラー)。
  • 最近のログ: systemdはサービスのログ出力の最後の数行を含めることが多く、エラーが即座に明らかになる場合があります。

2. Journalctlを使用したログの詳細調査

systemctl status が概要を提供する一方で、journalctl は標準出力と標準エラーストリームを含む、サービスの実行履歴の完全なコンテキストを提供します。

以下のコマンドを使用して、障害が発生しているサービスのジャーナルを表示します。-x フラグで説明を追加し、-e フラグで最後(最新のエントリ)にジャンプします。

$ journalctl -xeu myapp.service

ヒント: 障害が数時間前または数日前に発生した場合は、journalctl -u myapp.service --since "2 hours ago" などの時間フィルタリングオプションを使用します。


一般的な障害の段階的な診断

systemdの障害は通常、いくつかの予測可能なカテゴリに分類されます。ステータスとログを調べることで、問題を迅速に分類し、適切な解決策を適用できます。

障害タイプ1: 実行エラー(終了コード203)

終了コード 203/EXEC は、systemdが ExecStart ディレクティブで指定されたファイルを実行できなかったことを意味します。これは最も一般的な設定ミスの1つです。

原因と解決策:

  1. パスが間違っている: 実行ファイルへのパスが間違っているか、絶対パスではありません。

    • 解決策: ExecStart では常に完全な絶対パスを使用します。実行ファイルがその正確な場所に存在することを確認します。
    # 誤り
    ExecStart=myapp
    
    # 正しい
    ExecStart=/usr/local/bin/myapp
    
  2. 権限がない: ファイルにサービスを実行するユーザーの実行権限がありません。

    • 解決策: 実行権限を確認して適用します: chmod +x /path/to/executable
  3. インタプリタがない(シバン): ExecStart がスクリプト(PythonやBashなど)を指している場合、シバン行(#!/usr/bin/env python)がないか間違っている可能性があり、実行が妨げられます。

    • 解決策: スクリプトに有効なシバン行があることを確認します。

障害タイプ2: アプリケーションのクラッシュ(終了コード1または2)

サービスが正常に起動し(systemdが実行ファイルを見つけた)、その後すぐに一般的なアプリケーションエラーコード(通常は1または2)で failed 状態になった場合、問題はアプリケーションのロジックまたは環境内にあります。

原因と解決策:

  1. 設定ファイルのエラー: アプリケーションが必要な設定ファイルを読み取れなかったか、ファイルに無効な構文が含まれています。

    • 解決策: journalctl の出力を注意深く確認します。アプリケーションは通常、設定ファイルのパスや構文に関する特定のエラーメッセージを出力します。設定ファイルが相対パスの場合は、WorkingDirectory= ディレクティブを使用します。
  2. リソース競合/アクセス拒否: 権限制限により、アプリケーションが必要なポートを開いたり、データベースにアクセスしたり、ログファイルに書き込んだりできませんでした。

    • 解決策: サービスファイルの User= ディレクティブを確認し、そのユーザーが必要なすべてのリソースとディレクトリに対する読み取り/書き込みアクセス権を持っていることを確認します。

障害タイプ3: 依存関係の障害

データベース、ネットワークインターフェース、マウントされたファイルシステムなど、必要な依存関係の準備が整う前にサービスが起動するために失敗する場合があります。

原因と解決策:

  1. ネットワークの準備ができていない: ネットワーク接続を必要とするサービス(Webサーバー、プロキシなど)は、ネットワークスタックが初期化される前に起動すると失敗することがよくあります。

    • 解決策: サービスが起動時にアドレスまたはルートを必要とする場合は、network-online.target の順序付けを追加し、ディストリビューションのwait-onlineサービスがネットワークマネージャーに対して有効になっていることを確認します。
    [Unit]
    Description=My Web Service
    After=network-online.target
    Wants=network-online.target
    
  2. ファイルシステムがマウントされていない: サービスは、まだマウントされていないボリューム上のファイルにアクセスしようとします(特にセカンダリストレージやネットワークマウントで重要)。

    • 解決策: RequiresMountsFor= を使用して、起動前にどのパスが利用可能でなければならないかをsystemdに明示的に伝えます。
    [Unit]
    RequiresMountsFor=/mnt/data/storage
    

障害タイプ4: ユーザーと環境の問題(終了コード217)

終了コード 217/USER は、多くの場合、ユーザーまたはグループのディレクティブ、または環境変数が利用できないことに関連する障害を示します。

原因と解決策:

  1. 無効なユーザー/グループ: User= または Group= ディレクティブで指定されたユーザーがシステムに存在しません。

    • 解決策: id <username> でユーザー名が存在することを確認します。
  2. 環境変数がない: systemdサービスはクリーンな環境で実行されるため、シェル変数(PATH やカスタムAPIキーなど)は継承されません。

    • 解決策: 必要な変数をサービスファイルに直接定義するか、環境ファイルを介して定義します。
    [Service]
    # 直接定義
    Environment="API_KEY=ABCDEFG"
    
    # 外部ファイルの使用(例:/etc/sysconfig/myapp)
    EnvironmentFile=/etc/sysconfig/myapp
    

トラブルシューティングのワークフローとベストプラクティス

サービスファイルを変更するときは、常にこの3ステップのサイクルに従って、変更が正しく認識されテストされるようにします。

1. 設定の構文を検証する

systemd-analyze verify を使用して、サービスを起動する前にサービスユニットファイルをチェックします。これにより、単純な構文エラーをキャッチできます。

$ systemd-analyze verify /etc/systemd/system/myapp.service

2. デーモンをリロードする

Systemdは設定ファイルをキャッシュします。ユニットファイルを変更した後は、systemdに設定をリロードするように指示する必要があります。

$ systemctl daemon-reload

3. 再起動してステータスを確認する

サービスを再起動し、すぐにステータスとログを確認します。

$ systemctl restart myapp.service
$ systemctl status myapp.service

即時再起動とタイムアウトの処理

サービスが restarting ループに入ったり、明確なログメッセージなしですぐに失敗したりする場合は、[Service] セクションでこれらのディレクティブを調整することを検討します。

ディレクティブ 目的 ベストプラクティス
Type= systemdがプロセスを管理する方法(例:simpleforking)。 アプリケーションが明示的にデーモン化しない限り、simple を使用します。
TimeoutStartSec= systemdがメインプロセスからの成功シグナルを待機する時間。 アプリケーションの起動に時間がかかる場合(大規模なデータベース初期化など)、この値を増やします。
Restart= サービスを自動的に再起動するタイミングを定義します(例:alwayson-failure)。 本番アプリケーションでは on-failure を使用して、設定エラーが繰り返された場合の無限再起動ループを防ぎます。

障害状態をより注意深く読み取る

failed だけが悪い状態ではありません。ユニットは正常終了後に inactive (dead) になることがあり、これは Type=oneshot ジョブでは正常ですが、実行し続けることが期待されるデーモンでは疑わしいです。ユニットは TimeoutStartSec= が期限切れになるまで activating 状態になることがあります。ユニットは、コマンドが終了し、systemdがそれが許容可能であると判断した場合に active (exited) になることがあります。再起動ポリシーを変更する前に、サービスタイプがプログラムと一致していることを確認します。

通常のフォアグラウンドプロセスの場合は、以下から始めます。

[Service]
Type=simple
ExecStart=/usr/local/bin/myapp

1回実行して終了するスクリプトの場合:

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/rotate-reports

バックグラウンドでフォークする古いデーモンの場合、Type=forking が必要になる場合がありますが、習慣的に使用しないでください。多くの最新アプリケーションは、systemdで実行されるときにフォアグラウンドに留まります。systemdにフォークを期待するように指示し、プロセスがsystemdの期待する方法でフォークしない場合、誤解を招く起動障害が発生する可能性があります。

プレッシャー下で機能するトリアージチェックリスト

サービスがダウンしていて人々が待っているときは、固定されたシーケンスを使用します。

systemctl status myapp.service --no-pager
journalctl -u myapp.service -b --no-pager
systemctl cat myapp.service
systemctl show myapp.service -p FragmentPath -p User -p Group -p WorkingDirectory -p ExecStart

最初の実際のエラーを探します。最後の行ではありません。最後のジャーナルエントリは、systemdがユニットを失敗としてマークしたことだけを示している場合があります。有用な行はその上にあることがよくあります: Permission deniedNo such file or directoryAddress already in useFailed at step USER、またはアプリケーション固有の例外。

サービスが最近編集された場合は、構文とリロード状態を確認します。

sudo systemd-analyze verify /etc/systemd/system/myapp.service
sudo systemctl daemon-reload

systemctl status がユニットファイルがディスク上で変更されたと表示した場合、systemdはマネージャーが新しい定義をリロードしていないことを警告しています。daemon-reload の前にサービスを再起動すると、古い設定が引き続き使用される可能性があります。

権限の問題のように見えない権限の問題

サービスはシェルからは完全に実行できるが、systemdでは失敗することがあります。これは、あなたとして実行されていないためです。User=Group=WorkingDirectory=、および ProtectSystem=ReadWritePaths=PrivateTmp=NoNewPrivileges= などのハードニングオプションを確認します。

例:

[Service]
User=webapp
WorkingDirectory=/srv/webapp
ExecStart=/srv/webapp/bin/server
ReadWritePaths=/srv/webapp/var
ProtectSystem=strict

ProtectSystem=strict を使用すると、ファイルシステムの大部分がサービスに対して読み取り専用になります。これは優れたハードニング設定ですが、アプリケーションが明示的に許可したパスにのみ書き込む必要があることを意味します。ジャーナルに、アプリケーションがPIDファイル、キャッシュファイル、SQLiteデータベース、アップロードディレクトリを作成できないと表示されている場合、ユニットのサンドボックス化が原因である可能性があります。

また、親ディレクトリの権限も確認します。実行ファイルのモードが 755 であっても、/srv/webapp がサービスユーザーによって検索可能でない場合、systemdは依然として実行に失敗します。以下を使用します。

namei -l /srv/webapp/bin/server
sudo -u webapp /srv/webapp/bin/server --check-config

サービスユーザーとして安全な設定チェックを実行すると、フルデーモンを起動せずに多くの問題をキャッチできます。

再起動ループとレート制限

Restart=on-failure は便利ですが、繰り返される起動の洪水の中で元のエラーを隠してしまう可能性があります。Systemdは起動レート制限も適用します。サービスが短い時間内に何度も失敗すると、start-limit-hit が表示される場合があります。

便利なコマンド:

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

reset-failed は原因を修正しません。systemdの失敗状態とレート制限メモリをクリアして、変更後に再度テストできるようにするだけです。それを頻繁に使用する必要がある場合は、速度を落として、ジャーナルの最初の失敗を修正します。

永続的な問題のデバッグ

標準ログで問題が明らかにならない場合、アプリケーションが出力をリダイレクトしている可能性があります。

  • StandardOutputStandardError を確認する: デフォルトでは、これらはジャーナルに送られます。/dev/null またはファイルに設定されている場合は、エラーメッセージについてそれらの場所を直接確認する必要があります。
  • 一時的な冗長性: 可能であれば、アプリケーション(または ExecStart のコマンドライン引数)を一時的に最大の冗長性(例:--debug または -v)で実行するように設定して、障害時に詳細なログ出力を生成します。

適切な停止点

サービスが起動したら、もう1つ確認します。それが実際に機能するかどうかです。systemctl status は、systemdの観点からのプロセス状態のみを通知できます。Webサービスは、500を返している間もアクティブである可能性があります。ワーカーは、すべてのジョブに失敗している間もアクティブである可能性があります。ユニットレベルの問題を修正した後、アプリケーション独自のヘルスチェックを実行し、アプリケーションログを確認し、通信する依存関係に到達可能であることを確認します。

ほとんどのインシデントでは、有用なパスは短いです。systemctl status、次に journalctl -u、次に systemctl cat でユニットを検査し、設定されたサービスユーザーとしてコマンドをテストします。これにより、証拠に近づき、ランダムなユニットファイルの変更から遠ざかります。

最終的な原因を、記憶が新しいうちにサービスのランブックまたはデプロイメントノートに書き留めてください。「systemdを修正した」は後で役に立ちません。「デプロイによって /opt/app/current/bin/server が実行権限なしで作成されたため、サービスは203/EXECで失敗した」は役に立ちます。次のインシデントは通常、前のインシデントと似ています。