Systemdサービスの障害トラブルシューティング:ステップバイステップガイド

ステータスチェック、ジャーナルログ、ユニットファイルのレビュー、依存関係の修正、環境デバッグを通じて、systemdサービスの障害を診断します。

Systemdサービスの障害トラブルシューティング:ステップバイステップガイド

Systemdサービスの障害は、落ち着いて証拠を追跡すれば、デバッグが容易になります。失敗したユニットは通常、systemdが記録した状態、実行しようとしたコマンド、systemdまたはアプリケーションによって書き込まれたログの3つの有益な手がかりを残します。これらを順番に読むことで、問題がユニット、アプリケーション、依存関係、ホストのいずれにあるのかを把握する前にユニットファイルを編集するというよくある落とし穴を回避できます。

以下の例では架空の mywebapp.service を使用していますが、同じワークフローはデータベースヘルパー、キューコンシューマー、バックアップジョブ、エクスポーター、内部デーモンにも適用できます。

最初の防御線:systemctl status

サービスが起動に失敗した場合、最初に実行すべきコマンドは systemctl status <サービス名> です。このコマンドは、サービスの現在の状態(アクティブかどうか、ロードされているかどうか、そして重要なことに、最近のログのスニペット)のスナップショットを提供します。これにより、問題を迅速に特定するのに十分な情報が得られることがよくあります。

Webアプリケーションサービス mywebapp.service が起動しないとします。

systemctl status mywebapp.service

出力例の解釈:

● mywebapp.service - My Web Application
     Loaded: loaded (/etc/systemd/system/mywebapp.service; enabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Mon 2023-10-26 10:30:05 UTC; 10s ago
    Process: 12345 ExecStart=/usr/local/bin/mywebapp-start.sh (code=exited, status=1/FAILURE)
   Main PID: 12345 (code=exited, status=1/FAILURE)
        CPU: 10ms

Oct 26 10:30:05 hostname systemd[1]: Started My Web Application.
Oct 26 10:30:05 hostname mywebapp-start.sh[12345]: Error: Port 8080 already in use
Oct 26 10:30:05 hostname systemd[1]: mywebapp.service: Main process exited, code=exited, status=1/FAILURE
Oct 26 10:30:05 hostname systemd[1]: mywebapp.service: Failed with result 'exit-code'.

この出力から、すぐに以下のことがわかります。

  • サービス mywebapp.servicefailed 状態です。
  • Result: exit-code で失敗しており、ExecStart コマンドがゼロ以外のステータスで終了したことを意味します。
  • Process 行は、コマンド mywebapp-start.shstatus=1/FAILURE で失敗したことを示しています。
  • 重要なことに、ログ行は Error: Port 8080 already in use を示しています。これは問題の明確な兆候です。

このコマンドは最初の診断ツールであり、多くの場合、原因を直接指し示すか、次にどこを調べるべきかを絞り込みます。

journalctl で深く掘り下げる

systemctl status が簡単な概要を提供する一方で、journalctl は詳細なログを取得するための頼りになるコマンドです。これは、システムのすべての部分(サービスを含む)からのログを収集する systemd ジャーナルを照会します。

基本的なログレビュー

特定のサービスのすべてのログ(履歴エントリを含む)を表示するには:

journalctl -u mywebapp.service

これにより、mywebapp.service に関連するすべてのログエントリが表示されます。サービスが繰り返し失敗する場合、各失敗試行のエントリが表示されます。

フィルタリングと時間ベースのクエリ

特に最近の障害の後で結果を絞り込むには、--since--priority などのフラグを使用できます。

  • 特定の時間以降のログを表示:
    journalctl -u mywebapp.service --since "10 minutes ago"
    journalctl -u mywebapp.service --since "2023-10-26 10:00:00"
    
  • エラーレベル以上のメッセージのみを表示:
    journalctl -u mywebapp.service -p err
    
  • -xe と組み合わせて拡張説明と詳細出力を表示:
    journalctl -u mywebapp.service -xe --since "5 minutes ago"
    
    -x は一部の systemd メッセージに説明テキストを追加できます。これらの説明はヒントとして扱い、ユニット固有のログの代わりとして扱わないでください。

ログメッセージの理解

ErrorFailedWarning などのキーワードや、何が問題だったかを示すアプリケーション固有のメッセージを探します。タイムスタンプに注意して、障害に至るまでの一連のイベントを理解します。

ヒント: サービスの ExecStart スクリプトが標準出力または標準エラーに出力する場合、それらのメッセージは通常 journalctl によってキャプチャされます。スクリプトが説明的なエラーメッセージをログに記録するようにしてください。

ユニットファイルの検査:サービスの設計図

すべての systemd サービスはユニットファイル(例:mywebapp.service)によって定義されます。このファイルの設定ミスは、起動失敗の一般的な原因です。サービスが 何をしようとしているのか を理解する必要があります。

ユニットファイルの取得

サービスのアクティブなユニットファイルを表示するには:

systemctl cat mywebapp.service

このコマンドは、オーバーライドを含め、systemd が使用している正確なユニットファイルを表示します。

確認すべき主要なディレクティブ

実行関連の問題については [Service] セクション、依存関係については [Unit] セクションに焦点を当てます。

  • ExecStart:これは、systemd がサービスを起動するために実行するコマンドです。パスが正しいこと、コマンド自体が実行可能であり、(指定された User として)手動で呼び出したときに正常に実行されることを確認します。
    ExecStart=/usr/local/bin/mywebapp-start.sh
    
  • Type:プロセスの起動タイプを定義します。一般的なタイプは次のとおりです。
    • simple(デフォルト):ExecStart がメインプロセスです。
    • forkingExecStart が子プロセスをフォークし、親プロセスは終了します。systemd は親プロセスの終了を待ちます。
    • oneshotExecStart が実行され終了します。systemd は、コマンドが実行されている間、サービスをアクティブと見なします。
    • notify:サービスは、準備ができたときに systemd に通知を送信します。
    • 誤った Type は、systemd が実際に起動したサービスを失敗と見なしたり、その逆が発生したりする可能性があります。
  • User / Group:サービスが実行されるユーザーとグループです。権限の問題は、多くの場合、サービスがこのユーザーの下で権限を持たないファイルやリソースにアクセスしようとすることに起因します。
    User=mywebappuser
    Group=mywebappgroup
    
  • WorkingDirectory:サービスが実行されるディレクトリです。ExecStart やその他のコマンド内の相対パスはこれに依存します。
  • Restart:サービスを再起動するタイミングを定義します。on-failure または always に設定されている場合、失敗したサービスが常に再起動し、最初の障害を捕捉するのが難しくなる可能性があります。
  • TimeoutStartSec / TimeoutStopSec:systemd がサービスの起動または停止を待機する時間です。サービスの初期化に TimeoutStartSec よりも時間がかかる場合、systemd はそれを強制終了し、失敗を報告します。

一般的なユニットファイルの問題

  • パスの誤りExecStart またはその他のファイルパスのタイプミス。
  • Environment 変数の欠落:サービスは、systemd のクリーンな環境には存在しない可能性のある特定の環境変数(例:PATH)を必要とすることがよくあります(以下を参照)。
  • 権限:指定された User に、スクリプトの実行権限や、必要なデータファイルの読み取り/書き込み権限がない。
  • 構文エラー:ユニットファイル自体の単純なタイプミス。

ExecStart を手動でテストするには:

サービスのユーザーに切り替えて、コマンドを直接実行してみます。

sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh

これにより、journalctl で表示されるエラーがターミナルに直接再現されることが多く、デバッグが容易になります。

依存関係管理:サービスが単独で起動できない場合

サービスは、自身が起動する前に、他のサービスやシステムコンポーネントがアクティブであることに依存していることがよくあります。systemd は WantsRequiresAfterBefore ディレクティブを使用してこれらの依存関係を管理します。

依存関係の特定

systemctl list-dependencies <サービス名> を使用して、サービスが明示的に必要とする、または実行を希望するものを確認します。

systemctl list-dependencies mywebapp.service

[Unit] セクションの一般的なディレクティブ:

  • After=:このサービスがリストされたユニットの 後に起動する ことを指定します。リストされたユニットが失敗した場合、このサービスは引き続き起動を試みます(Requires= も使用されていない限り)。
  • Requires=:このサービスがリストされたユニットを 必要とする ことを指定します。必要なユニットのいずれかが起動に失敗した場合、このサービスは起動しません。
  • Wants=Requires= のより弱い形式です。必要なユニットが失敗した場合でも、このサービスは引き続き起動を試みます。

例:

[Unit]
Description=My Web Application
After=network.target mysql.service
Requires=mysql.service

ここで、mywebapp.servicenetwork.targetmysql.service の後に起動するように順序付けられており、mysql.service が正常に起動することを必要とします。mysql.service が失敗した場合、mywebapp.service は起動しません。

依存関係の競合の解決

依存関係の問題が原因でサービスが失敗した場合、journalctl は通常、どの依存関係が満たされなかったかを示します。たとえば、Dependency failed for My Web Application と表示され、その後に mysql.service の障害に関する詳細が続く場合があります。

解決手順:

  1. 依存サービスを確認する: systemctl status <依存サービス>(例:systemctl status mysql.service)および journalctl -u <依存サービス> を実行して、その 障害を最初にトラブルシューティングします。
  2. After= および Requires= ディレクティブを確認する: それらが目的の起動順序と厳格性を正しく反映していることを確認します。場合によっては、サービスは別のユニットの起動ジョブが完了するのを待つだけでなく、特定のポートが開くのを待つ必要があります。狭いチェックには、ExecStartPre= が役立ちます。ネットワークデーモンの場合、ソケットアクティベーションまたはアプリケーションレベルの再試行ロジックの方が信頼性が高いことがよくあります。

環境変数とパス:隠れた落とし穴

Systemd サービスは、非常にクリーンで最小限の環境で実行されます。これにより、ユーザーのシェルで完全に機能するコマンドが、重要な環境変数(PATH など)が欠落しているために systemd によって実行されると失敗するという問題が頻繁に発生します。

Systemd のクリーンな環境

systemd がサービスを開始するとき、systemctl start を開始したユーザーの完全な環境を継承しません。たとえば、PATH 変数はしばしば削減されるため、pythonnode などのコマンドが /usr/bin/bin などの標準的な場所にない場合、見つからない可能性があります。

症状: ExecStart=/usr/local/bin/myscript.shpython: command not foundnode: command not found、ライブラリが見つからないエラー、または必要な設定が空であるというアプリケーションメッセージで失敗する。

修正: サービス環境を明示的にします。

[Service]
WorkingDirectory=/opt/mywebapp
Environment="APP_ENV=production"
Environment="PATH=/opt/mywebapp/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
ExecStart=/opt/mywebapp/venv/bin/gunicorn app:app

多くの変数については、環境ファイルを使用します。

[Service]
EnvironmentFile=/etc/mywebapp/mywebapp.env
ExecStart=/opt/mywebapp/bin/server

そのファイルはシンプルに保ちます。EnvironmentFile= は Bash スクリプトではありません。export KEY=value、コマンド置換、シェルの条件文ではなく、KEY=value 行を使用します。ファイルに機密情報が含まれている場合は、制限的な権限も設定します。

sudo chown root:mywebapp /etc/mywebapp/mywebapp.env
sudo chmod 0640 /etc/mywebapp/mywebapp.env

権限:サービスユーザーとして障害を再現する

手動テストは多くの場合 root またはログインユーザーとして行われますが、ユニットは専用のサービスアカウントとして実行されるため、権限の問題は一般的です。

設定されたユーザーを確認します。

systemctl show mywebapp.service -p User -p Group

次に、同じコマンドをそのユーザーとして実行します。

sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh

アプリケーションが作業ディレクトリを必要とする場合は、それを含めます。

sudo -u mywebappuser bash -lc 'cd /opt/mywebapp && /usr/local/bin/mywebapp-start.sh'

実行可能ファイル以外にも注意してください。サービスユーザーは、/etc/mywebapp/config.yml への読み取りアクセス、/var/lib/mywebapp への書き込みアクセス、すべての親ディレクトリへの実行アクセス、または /run/mywebapp の下に Unix ソケットを作成する権限を必要とする場合があります。簡単なチェックで、多くの推測を省くことができます。

sudo -u mywebappuser test -r /etc/mywebapp/config.yml
sudo -u mywebappuser test -w /var/lib/mywebapp
namei -l /var/lib/mywebapp/uploads

サービスが 80 や 443 などの低いポートにバインドする場合にのみ失敗する場合は、すぐに root として実行しないでください。リバースプロキシ、ソケットアクティベーション、または対象を絞ったケイパビリティが、サービスによってはより安全な場合があります。

起動制限と再起動ループ

繰り返しクラッシュするサービスは、start request repeated too quickly のようなメッセージで停止する場合があります。これは、systemd のレート制限が作動したことを意味します。元の障害は以前に発生しているため、レート制限メッセージだけに焦点を当てないでください。

以下を使用します。

journalctl -u mywebapp.service --since "30 minutes ago"
systemctl show mywebapp.service -p NRestarts -p Restart -p StartLimitBurst -p StartLimitIntervalUSec

根本原因を修正した後、失敗した状態をクリアします。

sudo systemctl reset-failed mywebapp.service
sudo systemctl start mywebapp.service

Restart=always には注意してください。これは回復力のあるデーモンには便利ですが、デバッグ中はジャーナルをあふれさせ、最初の明確なエラーを隠す可能性があります。ユニットを一時的に停止し、ログを確認し、何かを変更したら手動で起動することができます。

リロード前にユニットを検証する

ユニットファイルを編集した後、サービスを再起動する前に、ファイルを検証し、systemd をリロードします。

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

サービスにドロップインオーバーライドがある場合は、マージされたバージョンを検査します。

systemctl cat mywebapp.service
systemctl show mywebapp.service -p FragmentPath -p DropInPaths -p ExecStart

これにより、厄介なケースを捕捉できます。/usr/lib/systemd/system の下のファイルを編集したが、/etc/systemd/system/mywebapp.service.d/override.conf の下のドロップインがまだ ExecStart を変更している場合や、systemd がロードしたものではないコピーされたユニットファイルを修正した場合などです。

実践的な操作手順

本番サービスがダウンしている場合は、短く繰り返し可能なループを使用します。

  1. systemctl status mywebapp.service --no-pager を実行します。
  2. journalctl -u mywebapp.service --since "15 minutes ago" を読みます。
  3. systemctl cat mywebapp.service を検査します。
  4. コマンド、ユーザー、作業ディレクトリ、環境、依存関係を確認します。
  5. サービスユーザーとしてコマンドを再現します。
  6. 1 つの変更を行います。
  7. ユニットが変更された場合は systemctl daemon-reload を実行します。
  8. 再起動し、ジャーナルを再度確認します。

この順序により、調査の根拠が維持されます。ジャーナルに Permission denied と表示された場合は、権限を修正します。No such file or directory と表示された場合は、systemd の観点からパスを確認します。Dependency failed と表示された場合は、最初に依存関係をデバッグします。プロセスがステータス 0/SUCCESS で終了したがサービスが失敗したと表示された場合は、Type= と、アプリケーションがデーモン化するかすぐに終了するかを確認します。

目標は、すべての systemd ディレクティブを暗記することではありません。障害メッセージを、それを生成したレイヤーに一致させ続けることです。