Systemdサービスファイルのマスター:包括的なガイド

正しいユニットセクション、再起動動作、ログ、セキュリティ、タイマーを備えた信頼性の高いsystemdサービスファイルを作成します。

Systemdサービスファイルの習得:包括的ガイド

Systemdサービスファイルは、Linuxがアプリケーションを起動、停止、再起動、監視する方法を指示します。手動で起動したサービスが起動時に失敗したり、過剰に再起動したり、ログが間違った場所に書き込まれたりする場合、通常はユニットファイルを確認する必要があります。

このガイドでは、systemdサービスユニットファイルをゼロから作成および構成することに焦点を当てます。コアセクション、動作するPythonサービスの例、一般的なトラブルシューティングコマンド、および本番環境で使用する価値のあるセキュリティとリソース制御について説明します。

Systemdユニットファイルの理解

Systemdはユニットファイルを使用して、サービス、ソケット、デバイス、マウントポイントなどのさまざまなシステムリソースを記述します。通常.service拡張子で終わるサービスユニットファイルは、systemdが特定のデーモンまたはアプリケーションを管理する方法を定義します。

これらのファイルはセクションに編成され、各セクションには構成ディレクティブを表すキーと値のペアが含まれています。主に焦点を当てるセクションは、[Unit][Service]、および[Install]です。

Systemdサービスファイルの構造

典型的なsystemdサービスファイルは次の構造を持っています:

[Unit]
Description=サービスの簡単な説明。
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/my_application --config /etc/my_app.conf
Restart=on-failure
User=myuser
Group=mygroup

[Install]
WantedBy=multi-user.target

各セクションとその一般的なディレクティブを詳しく見ていきましょう:

[Unit] セクション

このセクションはユニットに関するメタデータを提供し、他のユニットとの関係を定義します。依存関係と順序付けに使用されます。

  • Description=: サービスの人間が読める名前。systemctl statusの出力で表示されます。
  • Documentation=: サービスのドキュメントへのURLまたはパス。
  • Requires=: 強い依存関係を定義します。ここにリストされたユニットの起動に失敗した場合、このユニットも起動に失敗します。
  • Wants=: 弱い依存関係を定義します。ここにリストされたユニットの起動に失敗しても、このユニットは起動を試みます。
  • Before=: このユニットがリストされたユニットの前に起動することを保証します。
  • After=: 順序のみを制御します。例えば、After=network.targetは基本的なネットワークターゲットの後にこのユニットを起動しますが、外部接続性を保証するものではありません。ネットワーク依存のサービスは、After=network-online.targetとディストリビューションのwait-onlineサービスが必要な場合があります。
  • Conflicts=: ここにリストされたユニットが起動されると、このユニットは停止され、その逆も同様です。

[Service] セクション

このセクションはサービス自体の動作を構成します。プロセスの起動、停止、管理方法を定義します。

  • Type=: プロセスの起動タイプを指定します。一般的な値は次のとおりです:

    • simple(デフォルト):メインプロセスはExecStart=で指定されたものです。SystemdはExecStart=プロセスがフォークされた直後にサービスが開始されたと見なします。
    • forking: ExecStart=プロセスが子プロセスをフォークし、親プロセスが終了します。Systemdは親プロセスが終了したときにサービスが開始されたと見なします。このタイプではPIDFile=を指定する必要があることがよくあります。
    • oneshot: simpleと似ていますが、プロセスは作業が完了した後に終了することが期待されます。セットアップスクリプトに便利です。
    • notify: デーモンが正常に起動したときにsystemdに通知メッセージを送信します。これをサポートする最新のデーモンに推奨されるタイプです。
    • dbus: サービスがD-Bus名を取得します。
  • ExecStart=: サービスを起動するために実行するコマンド。ほとんどのサービスタイプでは、1つのExecStart=コマンドを使用します。Type=oneshotでは複数のExecStart=行が有効で、順次実行されます。

  • ExecStop=: サービスを停止するために実行するコマンド。

  • ExecReload=: 再起動せずにサービスの構成をリロードするために実行するコマンド。

  • Restart=: サービスを自動的に再起動するタイミングを定義します。一般的な値:

    • no(デフォルト):再起動しない。
    • on-success: サービスが正常に終了した場合のみ再起動(終了コード0)。
    • on-failure: サービスがゼロ以外の終了コードで終了した場合、シグナルで終了された場合、またはタイムアウトした場合に再起動。
    • on-abnormal: シグナルで終了された場合またはタイムアウトした場合に再起動。
    • on-abort: シグナルによって不潔に終了された場合のみ再起動。
    • always: 終了ステータスに関係なく常に再起動。
  • RestartSec=: サービスを再起動する前に待機する時間(デフォルトは100ms)。

  • User=: サービスを実行するユーザー。

  • Group=: サービスを実行するグループ。

  • WorkingDirectory=: コマンドを実行する前に変更するディレクトリ。

  • Environment=: サービスの環境変数を設定します。

  • EnvironmentFile=: ファイルから環境変数を読み取ります。

  • PIDFile=: PIDファイルへのパス(Type=forkingでよく使用されます)。

  • StandardOutput= / StandardError=: stdoutとstderrの出力先を制御します(例:journalnullinherit)。一般的なsystemdベースのディストリビューションでは、マネージャーのデフォルトが変更されていない限り、サービスの出力は通常ジャーナルに送られます。

[Install] セクション

このセクションは、通常はシンボリックリンクを作成することにより、ユニットを有効または無効にする方法を定義します。

  • WantedBy=: 有効にしたときにこのサービスを「必要とする」ターゲットを指定します。一般的な値:
    • multi-user.target: システムがマルチユーザーコマンドライン状態に達したときに起動するサービス用。
    • graphical.target: システムがグラフィカルログイン状態に達したときに起動するサービス用。

最初のSystemdサービスファイルの作成

/opt/my_app/my_app.pyにある架空のPythonスクリプトmy_app.pyの簡単なサービスファイルを作成しましょう。

1. サービスファイルを作成する:

カスタムアプリケーションのサービスファイルは通常/etc/systemd/system/に配置されます。ファイル名をmy_app.serviceとしましょう。

# ディレクトリが存在しない場合は作成
sudo mkdir -p /etc/systemd/system/

# テキストエディタを使用してサービスファイルを作成
sudo nano /etc/systemd/system/my_app.service

2. my_app.serviceに次の内容を追加します:

[Unit]
Description=My Custom Python Application
After=network.target

[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/my_app/
ExecStart=/usr/bin/python3 /opt/my_app/my_app.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

例の説明:

  • Description: アプリケーションを明確に識別します。
  • After=network.target: 起動前にネットワークが利用可能であることを保証します。
  • Type=simple: my_app.pyがメインプロセスであり、フォークしないことを前提としています。
  • User=appuserGroup=appgroup: アプリケーションを実行するユーザーとグループを指定します。これらのユーザーとグループがシステムに存在し、適切な権限を持っていることを確認してください。 必要に応じて作成する必要がある場合があります:
    sudo groupadd appgroup
    sudo useradd -r -g appgroup appuser
    sudo chown -R appuser:appgroup /opt/my_app/
    
  • WorkingDirectory: スクリプトのコンテキストを設定します。
  • ExecStart: Pythonスクリプトを実行するコマンド。/usr/bin/python3がPythonインタプリタへの正しいパスであり、スクリプトが実行可能であることを確認してください。
  • Restart=on-failure: スクリプトがクラッシュした場合、systemdが再起動を試みます。
  • WantedBy=multi-user.target: このサービスは、システムがマルチユーザー環境で起動するときに自動的に開始されます。

3. systemdマネージャー構成をリロードします:

サービスファイルを作成または変更した後、systemdに構成をリロードするように指示する必要があります。

sudo systemctl daemon-reload

4. サービスを有効にして開始します:

  • 有効化: これにより、起動時にサービスが自動的に開始されます。
    sudo systemctl enable my_app.service
    
  • 開始: これにより、サービスがすぐに開始されます。
    sudo systemctl start my_app.service
    

5. サービスのステータスを確認します:

サービスが実行されているかどうか、および潜在的なエラーを確認するには:

sudo systemctl status my_app.service

問題がある場合、statusコマンドは多くの場合、エラーメッセージまたはjournaldからのログを表示します。

6. ログの表示:

Systemdはログ記録のためにjournaldと統合されています。サービスのログは次を使用して表示できます:

sudo journalctl -u my_app.service

リアルタイムでログを追跡することもできます:

sudo journalctl -f -u my_app.service

その他の便利なコマンド

  • サービスの停止: sudo systemctl stop my_app.service
  • サービスの再起動: sudo systemctl restart my_app.service
  • 構成のリロード(アプリがサポートしている場合): sudo systemctl reload my_app.service
  • 起動時の自動開始を無効にする: sudo systemctl disable my_app.service

高度な構成とベストプラクティス

セキュリティに関する考慮事項

  • 非rootユーザーとしてサービスを実行する: 絶対に必要な場合を除き、常にUser=Group=を指定します。これは最小権限の原則に従います。
  • サービスを分離する: PrivateTmp=trueProtectSystem=strictProtectHome=trueNoNewPrivileges=trueなどのサンドボックス機能を検討してください。アプリケーションでテストしてください。これらは正当なファイル書き込みをブロックする可能性があります。
    • PrivateTmp=true: サービスに独自のプライベートな/tmpおよび/var/tmpディレクトリを提供します。
    • ProtectSystem=strict: サービスのファイルシステムの大部分を読み取り専用にします。サービスが書き込む必要があるディレクトリにはReadWritePaths=を使用します。
    • NoNewPrivileges=true: サービスが新しい特権を取得するのを防ぎます。

複雑な起動の処理

  • Type=forkingPIDFile=: フォークする古いアプリケーションの場合、PIDFile=が正しいファイルを指していることを確認します。
  • Type=notify: アプリケーションがサポートしている場合、systemdがいつ本当に準備ができたかを知るための最も堅牢な方法です。
  • ExecStartPre=ExecStartPost=: ExecStart=の前後に実行するコマンド。セットアップまたはクリーンアップタスクに便利です。

リソース制御

Systemdを使用すると、リソース使用量を制限できます:

  • CPUWeight=: サービスの相対的なCPUウェイト。
  • MemoryMax=: サービスが使用できる最大メモリ。
  • IOWeight=: カーネルとcgroupセットアップでサポートされている場合の相対的なI/Oウェイト。

例:

[Service]
# ... その他のディレクティブ ...
MemoryMax=512M
CPUWeight=50

タイマーとCronの比較

Systemdタイマーは、従来のcronジョブに代わる最新の方法を提供します。より柔軟で、systemdのログ記録および依存関係管理とより適切に統合されます。

  • Cron: crontabファイルで定義されたスケジュールされたタスク。
  • Systemdタイマー(.timerユニット): これらのユニットは.serviceユニットをスケジュールします。対応する.serviceファイルをいつ実行するかを指定する.timerファイルを定義します。

例:

毎日午前3時にスクリプトを実行するには:

  1. my_script.service: 実行するサービス。

    [Unit]
    Description=My daily script
    
    [Service]
    Type=oneshot
    ExecStart=/opt/my_scripts/run_daily.sh
    User=scriptuser
    
  2. my_script.timer: サービスをスケジュールするタイマー。

    [Unit]
    Description=Run my daily script once a day
    
    [Timer]
    # 毎日03:00に実行
    OnCalendar=*-*-* 03:00:00
    # マシンがオフの間にスケジュールされた時間を逃した場合、起動後すぐに実行します。
    Persistent=true
    
    [Install]
    WantedBy=timers.target
    

これを使用するには:

  • 両方のファイルを/etc/systemd/system/に配置します。
  • sudo systemctl daemon-reloadを実行します。
  • タイマーを有効にして開始します:sudo systemctl enable my_script.timerおよびsudo systemctl start my_script.timer

タイマーは、Persistent=true(起動時に実行されなかったジョブを実行)、カレンダーイベント(hourlydailyweeklyなど)、およびjournalctlとのより良い統合などの利点を提供します。

一般的な問題のトラブルシューティング

  • サービスが起動しない: systemctl status <サービス名>journalctl -u <サービス名>を確認します。タイプミス、誤ったパス、欠落している依存関係、または権限エラーを探します。
  • 誤ったType=: サービスがすぐに失敗するかハングする場合、Type=が間違っている可能性があります。simpleまたはforkingを試し、forkingを使用する場合はPIDFileが正しいことを確認します。
  • 権限が拒否されました: 指定されたUser=Group=が必要なファイルとディレクトリに対する読み取り/書き込みアクセス権を持っていることを確認します。
  • 環境変数: アプリケーションが特定の環境変数に依存している場合、Environment=またはEnvironmentFile=を使用して正しく設定されていることを確認します。
  • 依存関係: After=Wants=、およびRequires=が意図したものと一致していることを確認します。After=は起動の順序を指定します。それ自体では別のユニットを引き込みません。

本番ホストで新しいユニットを有効にする前に、次を実行します:

sudo systemd-analyze verify /etc/systemd/system/my_app.service

これにより、起動時にサービスに依存する前に、多くの構文およびディレクティブの誤りをキャッチできます。

重要なポイント

アプリケーションを正確に記述する最小のサービスファイルを作成し、その後、再起動ポリシー、ログ記録、セキュリティ制限、およびリソース制限を意図的に追加します。変更のたびに、systemctl daemon-reloadを実行し、ユニットを検証し、本番環境で信頼する前にsystemctl statusjournalctl -uを確認します。