Systemdマスターガイド:初めてのカスタムサービスユニットファイル作成
カスタムUnit Fileを作成して、Systemdサービス管理の基礎を学びましょう。このチュートリアルでは、`[Unit]`、`[Service]`、`[Install]`の各セクションを分解し、`systemctl`を使用してLinux上で基本的なバックグラウンドサービスを定義、有効化、起動、確認する手順を段階的に解説します。
Systemdマスターガイド:初めてのカスタムサービスユニットファイル作成
スクリプトや小規模なアプリケーションがターミナルセッション、screenウィンドウ、あるいは脆弱なcronの回避策を超えたとき、カスタムsystemdサービスユニットが役立ちます。クラッシュ後に再起動すべきワーカーがあるかもしれません。ネットワークが準備できた後に起動する必要がある小さな内部APIがあるかもしれません。バックアップスクリプトを制御されたサービスとして実行し、そのログをジャーナルに保存し、オペレーターが他のすべてに使用するのと同じsystemctlコマンドを使用できるようにしたいかもしれません。
systemdの便利な点は、ユニットファイルが複雑であることではありません。ユニットファイルがプロセスを明確にする点にあります。何が実行されるか、誰として実行されるか、いつ起動するか、どのように停止するか、ログの保存場所、そして失敗した場合にsystemdが何をすべきか。これらの決定が文書化されると、サービスの運用がはるかに容易になります。
このチュートリアルでは、ゼロから小さなサービスを構築します。例は意図的にシンプルですが、パターンはバックグラウンドワーカー、キューコンシューマー、メトリクスエクスポーター、内部デーモンで使用するものと同じです。
ユニットファイルではなく、実際のコマンドから始める
優れたサービスユニットは、手動で既に動作しているコマンドから始まります。systemd設定を書く前に、プログラムを直接実行し、フォアグラウンドでの動作を理解できることを確認してください。
この例では、小さなレポータースクリプトを作成します。
sudo install -d -o root -g root -m 0755 /opt/my-custom-service
sudo nano /opt/my-custom-service/reporter.sh
次の内容を追加します。
#!/usr/bin/env bash
set -euo pipefail
while true; do
echo "$(date --iso-8601=seconds) reporter heartbeat"
sleep 10
done
実行可能にしてテストします。
sudo chmod 0755 /opt/my-custom-service/reporter.sh
/opt/my-custom-service/reporter.sh
数行表示されたらCtrl-Cで停止します。スクリプトが/var/log/reporter.logに直接追記するのではなく、標準出力に書き込んでいることに注目してください。これは意図的です。ほとんどのカスタムサービスでは、systemdにstdoutとstderrをジャーナルにキャプチャさせる方が、すべてのスクリプトに独自のログファイルのパーミッション、ローテーション、障害動作を管理させるよりもクリーンです。
専用のサービスユーザーを作成する
アプリケーションサービスは、本当にroot権限が必要でない限り、rootとして実行しないでください。ハートビートスクリプトには必要ありません。Webアプリにも通常は必要ありません。キューから読み取り、データベースに書き込むワーカーにも通常は必要ありません。
ロックダウンされたシステムユーザーを作成します。
sudo useradd --system --no-create-home --shell /usr/sbin/nologin reporter
ディストリビューションが異なるnologinパスを使用している場合は、以下で確認します。
command -v nologin || command -v false
サービスユーザーは、書き込みが必要なファイルのみを所有する必要があります。この例では、スクリプトはsystemdを通じてジャーナルに書き込むため、/opt/my-custom-serviceの所有権は必要ありません。
サービスユニットを書く
カスタムの管理者管理システムユニットは、通常/etc/systemd/system/に配置します。ベンダーパッケージユニットは、ディストリビューションに応じて/usr/lib/systemd/system/または/lib/systemd/system/に配置されることが一般的です。可能な限りベンダーユニットファイルを直接編集せず、独自のユニットには/etc/systemd/system/を、オーバーライドにはドロップインを使用してください。
ユニットを作成します。
sudo nano /etc/systemd/system/my-reporter.service
実用的な最初のバージョンとして、次を使用します。
[Unit]
Description=My Custom Reporter Service
Documentation=man:systemd.service(5)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=reporter
Group=reporter
ExecStart=/opt/my-custom-service/reporter.sh
Restart=on-failure
RestartSec=5s
WorkingDirectory=/opt/my-custom-service
StandardOutput=journal
StandardError=journal
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
[Unit]セクションは関係性を記述します。After=network-online.targetは順序を制御します。それ自体ではnetwork-onlineターゲットを引き込みません。Wants=network-online.targetはsystemdにそのターゲットも起動するよう要求します。サービスがネットワークを必要としない場合は、両方の行を削除してユニットをよりシンプルに保ちます。
[Service]セクションはプロセスを記述します。Type=simpleは、バックグラウンドにフォークしないフォアグラウンドプロセスに適しています。これは現代のサービスでは一般的なケースです。レガシーデーモンがフォークし、PIDファイルを書き込み、シェルに制御を戻す場合は、Type=forkingが必要になるかもしれませんが、単に「デーモンらしい」という理由で使用しないでください。
ExecStartは絶対パスである必要があります。パイプ、リダイレクト、&&などのシェル機能は、明示的にシェルを実行しない限り解釈されません(例:ExecStart=/bin/bash -lc 'command one && command two')。コマンドにシェルロジックが必要な場合は、スクリプトを優先してください。テストが容易で、読みやすくなります。
Restart=on-failureは、異常終了後にsystemdにサービスを再起動するよう指示します。クリーンなsystemctl stop後は再起動しません。RestartSec=5sは、タイトな再起動ループがマシンに負荷をかけるのを防ぎます。
ここでのハードニングオプションは控えめですが有用です。NoNewPrivileges=trueは、プロセスとその子プロセスがsetuidバイナリやファイルケーパビリティを通じて新しい権限を取得するのを防ぎます。PrivateTmp=trueは、サービスにプライベートな/tmpビューを提供します。これらは通常、シンプルなサービスでは安全ですが、一部のソフトウェアは共有一時パスを期待するため、実際のアプリケーションでテストしてください。
ユニットをロードして起動する
ユニットファイルを追加または変更した後、systemdのマネージャー設定をリロードします。
sudo systemctl daemon-reload
今すぐサービスを起動します。
sudo systemctl start my-reporter.service
状態を確認します。
systemctl status my-reporter.service
Active: active (running)と表示されることを確認します。失敗した場合は、推測せずにログを読みます。
journalctl -u my-reporter.service -n 50 --no-pager
テスト中はライブログを追跡します。
journalctl -u my-reporter.service -f
スクリプトのパスが間違っている、パーミッションがない、ユーザーが存在しない、またはコマンドがすぐに終了する場合、systemdは通常、ジャーナルで明確にその旨を伝えます。
起動時に有効化する
サービスの起動と有効化は異なるアクションです。startは今すぐ実行します。enableはブートターゲットに配線し、将来の起動時に開始されるようにします。
sudo systemctl enable my-reporter.service
ユニットをテストした後は、1つのコマンドで両方を実行できます。
sudo systemctl enable --now my-reporter.service
有効化されているかどうかを確認します。
systemctl is-enabled my-reporter.service
障害の診断を容易にする
最も一般的な初めてのサービスの障害は、systemdの衣をまとった通常のLinuxの問題です。
status=203/EXECが表示された場合、systemdはコマンドを実行できませんでした。パス、実行可能ビット、シバン行、および行末を確認してください。WindowsからCRLF行末でコピーされたスクリプトは、エディタでは正常に見えても失敗する可能性があります。
パーミッションエラーが表示された場合、サービスがシェルユーザーではなくreporterとして実行されていることを思い出してください。以下でテストします。
sudo -u reporter /opt/my-custom-service/reporter.sh
サービスが起動してすぐに停止する場合、プロセスがおそらく終了しています。Type=simpleはコマンドが実行し続けることを期待します。ワンショットのセットアップコマンドには、simpleではなくType=oneshotを使用する必要があります。
ログがない場合、アプリケーションがstdout/stderrではなくファイルに書き込んでいるか、内部でユーザーを変更していないか確認してください。ほとんどの小規模サービスでは、stdoutに書き込むことが最も驚きの少ないオプションです。
便利な管理コマンド
ユニットが配置されれば、日常の運用は簡単です。
sudo systemctl start my-reporter.service
sudo systemctl stop my-reporter.service
sudo systemctl restart my-reporter.service
sudo systemctl reload my-reporter.service
systemctl status my-reporter.service
journalctl -u my-reporter.service --since "1 hour ago"
systemctl cat my-reporter.service
systemctl show my-reporter.service -p User -p Restart -p ExecStart
systemctl catは、ドロップインオーバーライドがあるマシンで特に便利です。systemdが読み取っている有効なユニットフラグメントを表示するためです。
カスタムユニットファイルは賢い必要はありません。退屈で、明示的で、テスト可能である必要があります。コマンドを手動で動作させ、専用ユーザーとして実行し、サービスを正確に記述する最小のユニットを書き、systemdをリロードし、何かが失敗したらジャーナルを使用します。このワークフローは、おもちゃのレポータースクリプトから実際の本番デーモンまでスケールします。
環境と設定をクリーンに追加する
遅かれ早かれ、サービスには設定が必要になります。ポート、データベースURL、フィーチャーフラグ、パスなどです。環境によって異なる値をユニットファイル内に埋め込むのは避けてください。一般的なパターンは環境ファイルです。
sudo nano /etc/my-reporter.env
例:
REPORT_INTERVAL=10
REPORT_LABEL=production
機密情報が含まれている場合は、ファイルをロックダウンします。
sudo chown root:reporter /etc/my-reporter.env
sudo chmod 0640 /etc/my-reporter.env
次に、ユニットから参照します。
[Service]
EnvironmentFile=/etc/my-reporter.env
ExecStart=/opt/my-custom-service/reporter.sh
スクリプトでは、デフォルト値で変数を読み取ります。
interval="${REPORT_INTERVAL:-10}"
label="${REPORT_LABEL:-default}"
シークレットについては注意が必要です。環境変数は、システム設定とパーミッションに応じて、プロセス検査やサービスメタデータを通じて公開される可能性があります。機密性の高い値には、適切なシークレットマネージャー、厳格なパーミッションを持つクレデンシャルファイル、またはディストリビューションがサポートしている場合はsystemdの新しいクレデンシャル機能を優先してください。重要な習慣は、便利だからといってパスワードをユニットファイルに散りばめるのではなく、意図的に決定することです。
ローカル変更にはドロップインオーバーライドを使用する
パッケージがユニットをインストールし、1つの設定を変更する必要がある場合、ベンダーファイルを編集しないでください。ドロップインを使用します。
sudo systemctl edit my-reporter.service
これにより、/etc/systemd/system/my-reporter.service.d/の下にオーバーライドファイルが開きます。例:
[Service]
RestartSec=15s
保存後、リロードして再起動します。
sudo systemctl daemon-reload
sudo systemctl restart my-reporter.service
マージされた結果を確認します。
systemctl cat my-reporter.service
ドロップインが重要なのは、パッケージの更新によってベンダーユニットが置き換えられる可能性があるためです。/etc内のオーバーライドは、可視性を保ち、意図的であることが明確です。
シャットダウン動作について考える
起動はライフサイクルの半分にすぎません。サービスはクリーンに停止する必要もあります。デフォルトでは、systemdはSIGTERMを送信し、待機し、プロセスが終了しない場合はSIGKILLを送信する可能性があります。多くのシンプルなサービスではこれで問題ありません。キューイングワーカー、アップロードプロセッサー、データベースライターの場合、プロセスが現在の作業を安全に完了または放棄できるように、終了処理を処理する必要があるかもしれません。
タイムアウトを調整できます。
[Service]
TimeoutStopSec=30s
KillSignal=SIGTERM
理由がない限り、非常に長い停止タイムアウトを設定しないでください。長時間のシャットダウンは、デプロイメント、再起動、インシデント復旧を遅らせます。ワーカーは通常、新しい作業の受け入れを停止し、処理中のアイテムを完了し、制限時間内に終了する必要があります。
ノイズの多い再起動ループを防ぐ
Restart=on-failureは便利ですが、壊れたサービスが繰り返し再起動する可能性があります。障害モードがノイズが多い可能性がある場合は、制限を追加します。
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
これにより、systemdはインターバル内で失敗が多すぎると試行を停止します。問題を修正したら、失敗状態をリセットします。
sudo systemctl reset-failed my-reporter.service
sudo systemctl start my-reporter.service
このコマンドはテスト中にも便利です。スクリプトやパーミッションを修正した後でも、サービスは失敗状態のままになる可能性があります。
ユニットを信頼する前に検証する
Systemdには便利な検証ツールがあります。
systemd-analyze verify /etc/systemd/system/my-reporter.service
すべてのアプリケーションの問題をキャッチできるわけではありませんが、構文エラー、使用しているsystemdバージョンで不明な設定、および一部の順序の問題をキャッチできます。大きな編集後や、ディストリビューション間でユニットをコピーするときに実行してください。Systemdの機能はバージョンによって異なるため、新しいFedoraサーバーで動作するハードニングオプションが、古いエンタープライズディストリビューションには存在しない可能性があります。
また、起動順序が混乱した場合は、ユニットの依存関係ビューを確認します。
systemctl list-dependencies my-reporter.service
systemctl list-dependencies --reverse my-reporter.service
最初のコマンドは、サービスが引き込む、または依存するものを表示します。逆ビューは、それに依存するものを表示します。これは、サービスが予期せず起動したり、1つのユニットを無効にすることが別のユニットに影響を与える場合に便利です。
サービスがシステムレベルかユーザーレベルかを判断する
この記事では、/etc/systemd/system/の下のシステムサービスを使用しています。これは、ブート時に起動し、ログインセッションとは独立して実行されるマシンサービスに適した選択です。Systemdはユーザーサービスもサポートしており、通常はsystemctl --userで管理され、ユーザーごとのバックグラウンドプロセスに使用されます。
sudoを避けるためだけに、インフラストラクチャデーモンにユーザーサービスを使用しないでください。ユーザーサービスは、ライフサイクルルール、環境処理、ログイン動作が異なります。アプリケーションワーカー、エクスポーター、ホストレベルのエージェントには、専用の最小権限ユーザーを持つシステムサービスの方が通常、推論が容易です。
最初のユニットは退屈に保つ
オンラインで見つけたすべてのハードニングディレクティブ(プライベートデバイス、読み取り専用パス、システムコールフィルター、ケーパビリティバウンディング、名前空間制限など)を追加したくなるものです。これらは貴重なツールですが、サービスが動作した後で1つずつ追加してください。厳重に制限されたサービスが起動に失敗した場合、初心者はユニットが間違っているのか、アプリケーションが間違っているのか、サンドボックス設定が必要なファイルをブロックしたのかを判断できないことがよくあります。
優れた本番パスは段階的です。動作するサービス、専用ユーザー、信頼性の高いロギング、再起動ポリシー、基本的なハードニング、そしてアプリケーションが正しく動作することを証明するテストを行った後、より強力なサンドボックス化を行います。