Systemd のトラブルシューティング:サービス依存関係と順序付けディレクティブの理解
Linux の最新のシステムおよびサービスマネージャーである Systemd は、システムサービスを管理するための強力で柔軟な方法を提供します。Systemd の設定でよくある課題は、サービスが正しい順序で開始され、依存関係が適切に満たされていることを確認することです。依存関係の設定ミスは競合状態を引き起こす可能性があり、サービスが前提条件の準備が整う前に起動しようとして失敗したり、予期しない動作を引き起こしたりします。この記事では、サービスの依存関係と順序付けを制御する重要な Systemd ユニットファイルディレクティブである Requires、Wants、After、および Before について詳しく説明します。これらのディレクティブを理解し、正しく実装することは、堅牢で信頼性の高いシステム構成を構築するために不可欠です。
サービスの依存関係を適切に管理することは、起動の失敗を防ぐだけでなく、予測可能で安定したオペレーティング環境を作成することでもあります。サービスがお互いに依存している場合、Systemd はそれらの起動とシャットダウンをどのようにオーケストレーションするかについての明示的な指示を必要とします。これらの指示を提供しないと、特定のスループット条件下やシステム再起動時にのみ現れる、追跡が困難な微妙なバグとして現れる可能性があります。依存関係と順序付けディレクティブを習得することで、サービスライフサイクルをきめ細かく制御し、重要なアプリケーションとシステムコンポーネントが意図したとおりに機能することを保証できます。
主要な依存関係ディレクティブ:Requires と Wants
Systemd は、ユニット間の直接的な依存関係を定義するために、Requires と Wants の 2 つの主要なディレクティブを使用します。これらのディレクティブは、ユニットファイル(例:.service ファイル)の [Unit] セクションに配置されます。
Requires=
Requires= ディレクティブは、強力な依存関係を確立します。ユニット A がユニット B を Requires= している場合、ユニット A が正常にアクティブと見なされるためには、ユニット B がアクティブである 必要があります。ユニット B がアクティブ化に失敗したり停止されたりすると、ユニット A も停止されるか、起動が阻止されます。これは、必要とされるユニットの失敗が依存ユニットに直接影響を与える重要な関係です。
例:
データベースサービス(mariadb.service)に厳密に依存する Web アプリケーションサービス(myapp.service)を考えてみましょう。myapp.service ユニットファイルには次のように記述されている可能性があります。
[Unit]
Description=My Web Application
Requires=mariadb.service
[Service]
ExecStart=/usr/bin/myapp
[Install]
WantedBy=multi-user.target
このシナリオでは、mariadb.service が起動に失敗したり、手動で停止されたりすると、Systemd は myapp.service も停止します。myapp.service を起動しようとして mariadb.service が実行されていない場合、Systemd はまず mariadb.service の起動を試みます。mariadb.service が失敗すると、myapp.service は起動しません。
Wants=
Wants= ディレクティブは、弱い、オプションの依存関係を定義します。ユニット A がユニット B を Wants= している場合、Systemd はユニット A を起動するときにユニット B の起動を試みますが、ユニット B が起動に失敗したり実行されていなくても、ユニット A はアクティブ化されます。これは、別のサービスから利益を得るが、独立して機能できる(おそらく機能が削減されたり警告が表示されたりする)サービスに役立ちます。
例:
特定のロギングサービス(app-logger.service)なしでも実行できる監視エージェント(monitoring-agent.service)があると仮定しますが、理想的にはそれが利用可能であるとします。monitoring-agent.service ユニットファイルは次のようになる可能性があります。
[Unit]
Description=Monitoring Agent
Wants=app-logger.service
[Service]
ExecStart=/usr/bin/monitoring-agent
[Install]
WantedBy=multi-user.target
ここで、Systemd は monitoring-agent.service がアクティブ化されたときに app-logger.service の起動を試みます。ただし、app-logger.service が起動に失敗しても、monitoring-agent.service は正常に起動を続行します。
Requires= vs. Wants=
Requires=: 強力な依存関係。必要とされるユニットが失敗すると、依存ユニットも失敗または停止します。Wants=: 弱い依存関係。依存ユニットは、必要とされるユニットの起動を試みますが、失敗した場合でも続行します。
Requires= は Wants= を含意することに注意することが重要です。ユニットが別のユニットを必要とする場合、それは暗黙的にそれを望んでもいます。
順序付けディレクティブ:After と Before
Requires と Wants が何が実行されている必要があるかを定義するのに対し、After と Before は、ユニットがいつ互いに相対して開始されるべきかを定義します。これらのディレクティブは、システム起動プロセス中またはオンデマンドでユニットがアクティブ化されるときの操作のシーケンスを制御します。これらは、依存関係ディレクティブと組み合わせて使用されることがよくあります。
After=
After= ディレクティブは、現在のユニットが After= でリストされたユニットが正常にアクティブ化された後にのみ開始されるべきであることを指定します。これにより、依存サービスが独自の起動シーケンスを開始する前に、前提条件サービスが稼働していることが保証されます。
例:
ネットワークに依存するサービス(custom-network-app.service)は、ネットワークが完全に構成された後にのみ開始されるべきです。これは通常、ネットワークターゲット(network.target)の後に開始されることを確認することで処理されます。
[Unit]
Description=Custom Network Application
Requires=network.target
After=network.target
[Service]
ExecStart=/usr/bin/custom-network-app
[Install]
WantedBy=multi-user.target
この構成では、Systemd は custom-network-app.service の起動を試みる前に network.target がアクティブであることを保証します。network.target がまだ準備できていない場合、custom-network-app.service は遅延します。
Before=
Before= ディレクティブは、現在のユニットが Before= でリストされたユニットの前に開始されるべきであることを指定します。これは、シャットダウン中に他のサービスよりも後に停止する必要があるサービス、またはそれらのための環境を提供するために特定のサービスの前に開始する必要があるサービスに役立ちます。
例:
メールサーバー(postfix.service)が、メールを送信する可能性のあるユーザー向けのサービスよりも前に実行されている必要があるシナリオを想像してください。postfix.service が早期に開始されることを確認するために Before= を使用する場合があります。
[Unit]
Description=Postfix Mail Transfer Agent
# ... Conflicts=などの他のディレクティブ
Before=user-session.target
[Service]
ExecStart=/usr/lib/postfix/master
[Install]
WantedBy=multi-user.target
この設定は、user-session.target の一部が起動を開始する前に postfix.service を開始しようとします。同様に、シャットダウン中、postfix.service は、対応する After=user-session.target を持つ場合、停止される最後のものの一つになります。
After= vs. Before=
After=: リストされたユニットが現在のユニットの開始前にアクティブであることを保証します。Before=: 現在のユニットがリストされたユニットの前に開始されることを保証します。
After= と Before= は補完的であることを理解することが重要です。ユニット A がユニット B の後に開始され、ユニット B がユニット A の後に開始されるようにしたい場合は、通常、ユニット A で After=B を使用し、ユニット A で Before=B を使用します。これにより厳密な順序付けが作成されます:A が開始され、次に B が開始されます。逆の場合は、B が開始され、次に A が開始されます。順序付けを行う場合、現在のユニットの前に何が起こるかを指定するよりも、現在のユニットの後に何が起こるかを指定する方が一般的に直感的です。たとえば、サービスがネットワークの後に開始されることを確認するには、サービスに After=network.target を追加します。サービスをシャットダウンターゲットの前に開始したい場合は、Before=shutdown.target を使用します。
組み合わせディレクティブによる堅牢な構成
実際のシナリオでは、これらのディレクティブを組み合わせて複雑な依存関係グラフを作成することがよくあります。multi-user.target は、システムがマルチユーザー操作の準備ができていることを示す一般的なターゲットです。多くのサービスは WantedBy=multi-user.target および After=multi-user.target(またはより正確には、multi-user.target が依存する basic.target や getty.target など)として構成されています。
一般的なパターン:
データベースを必要とし、ネットワーク構成後に開始されるべきサービスは次のようになります。
[Unit]
Description=My Application Service
Requires=mariadb.service
Wants=other-optional-service.service
After=network.target mariadb.service
[Service]
ExecStart=/usr/local/bin/my_app
[Install]
WantedBy=multi-user.target
パターンの説明:
Requires=mariadb.service:my_app.serviceが機能するためにはmariadb.serviceが実行されている必要があることを保証します。mariadb.serviceが失敗すると、my_app.serviceも停止します。Wants=other-optional-service.service:other-optional-service.serviceの起動を試みますが、失敗した場合でもmy_app.serviceは続行します。After=network.target mariadb.service:my_app.serviceは、network.targetとmariadb.serviceが正常にアクティブ化された後にのみ開始されることを保証します。これは、データベースがアクセス可能でネットワークが準備できていることを確認するために重要です。WantedBy=multi-user.target: 有効化されると(systemctl enable my_app.service)、このディレクティブはシンボリックリンクを追加するため、システムがmulti-user.target状態に達したときにmy_app.serviceが開始されます。
高度な考慮事項とベストプラクティス
WantedByvs.RequiredBy:Wantsvs.Requiresと同様に、WantedByは弱い順序付けであり、RequiredByは強い順序付けです。ほとんどのサービスはWantedBy=multi-user.targetを使用します。Conflicts=: このディレクティブは、現在のユニットと同時に実行されるべきではないユニットを指定します。現在のユニットが開始されると、競合するユニットは停止され、その逆も同様です。- 推移的依存関係: 依存関係は推移的です。A が B を必要とし、B が C を必要とする場合、A は間接的に C を必要とします。Systemd はこれらのチェーンを自動的に処理します。
Condition*=ディレクティブ:ConditionPathExists=、ConditionFileNotEmpty=、ConditionVirtualization=などを使用して、システム状態に基づいてユニットのアクティブ化を条件付きにし、堅牢性をさらに向上させます。systemctl list-dependencies <unit>を使用する: このコマンドは、直接的および間接的な依存関係を含む、ユニットの依存関係ツリーを視覚化するのに非常に役立ちます。systemctl status <unit>を使用する: 設定変更後は必ずサービスの状態を確認してください。多くの場合、依存関係の問題を含む、失敗の理由が表示されます。- 循環依存関係を避ける: Systemd はそれらを解決しようとしますが、直接的な循環依存関係(
A Requires B、B Requires A)は、起動ループや失敗につながる可能性があります。これを避けるために依存関係を慎重に設計してください。
結論
Systemd の依存関係と順序付けディレクティブを習得することは、Linux サービスを管理する人にとって不可欠です。Requires、Wants、After、および Before を正しく使用することで、確実に起動し、競合状態のような一般的な落とし穴を回避する回復力のあるシステムを構築できます。強力な依存関係と弱い依存関係、そして「何」と「いつ」のニュアンスを理解することで、サービスライフサイクルを正確に制御でき、より安定した予測可能なシステム動作につながります。systemctl status および systemctl list-dependencies を使用して構成を徹底的にテストし、サービスが意図したとおりにオーケストレーションされていることを常に確認してください。