カスタムSystemdユニットファイルの効率的な記述と管理方法
最新のLinuxディストリビューションでは、initシステムおよびサービスマネージャーとしてsystemdが主に採用されています。Linuxのシステム管理者や開発者がアプリケーションを確実に展開・管理するためには、systemdを理解することが不可欠です。多くのアプリケーションにはプリビルドされたsystemdユニットファイルが付属していますが、カスタムユニットファイルを作成する能力があれば、独自のアプリケーション、スクリプト、またはカスタムプロセスを起動、シャットダウン、およびライフサイクル全体を標準化できます。
この記事では、カスタムのsystemd .service ユニットファイルの作成、設定、および管理のプロセスをガイドします。アプリケーションの実行方法、依存関係の確立、堅牢な動作の確保を定義する主要なディレクティブを探ります。最終的に、カスタムサービスをLinuxオペレーティングシステムにシームレスに統合し、ブート時の自動起動、失敗時の再起動、systemctl を使用した容易な管理を実現できるようになります。
カスタムsystemdユニットファイルを習得することは、サービスに対するきめ細かな制御を提供し、システムの安定性を向上させ、管理タスクを簡素化します。プロのようにアプリケーションを管理するために必要なコアコンポーネントと実践的な手順を見ていきましょう。
Systemdユニットファイルの理解
Systemdは、設定ファイルによって定義されるユニットと呼ばれるさまざまなシステムリソースを管理します。これらのユニットには、サービス(.service)、マウントポイント(.mount)、デバイス(.device)、ソケット(.socket)などがあります。アプリケーションやバックグラウンドプロセスの管理に関して、.service ユニットタイプが最も一般的で関連性が高いです。
Systemdユニットファイルは、通常特定のディレクトリに保存されるプレーンテキストファイルです。優先順位の高い順に、主要な場所は次のとおりです。
/etc/systemd/system/: これはカスタムユニットファイルとオーバーライドのための推奨される場所です。これらはシステムデフォルトよりも優先され、システム更新後も保持されます。/run/systemd/system/: 実行時に生成されたユニットファイルに使用されます。/usr/lib/systemd/system/: インストールされたパッケージによって提供されるユニットファイルが含まれます。このディレクトリ内のファイルを直接変更しないでください。
カスタムユニットファイルを /etc/systemd/system/ に配置することで、systemdによって適切に認識・管理されることが保証されます。
.service ユニットファイルの構造
A systemd .service ユニットファイルは、いくつかのセクションに構造化されており、それぞれが [セクション名] で示され、さまざまなディレクティブ(キーと値のペア)を含んでいます。サービスユニットの3つの主要なセクションは [Unit]、[Service]、および [Install] です。
使用する最も重要なディレクティブを分解してみましょう。
[Unit] セクション
このセクションには、ユニット、その説明、および依存関係に関する一般的なオプションが含まれます。
Description: サービスを人間が読める文字列で説明します。これはsystemctl statusの出力に表示されます。
ini Description=私のカスタムPython WebアプリケーションDocumentation: サービスに関するドキュメントを指すURL(オプション)。
ini Documentation=https://example.com/docs/my-appAfter: このユニットがリストされたユニットの後に開始されるべきことを指定します。これは起動順序の管理に役立ちます。Webアプリケーションの場合、ネットワークが稼働していることを確認したい場合があります。
ini After=network.targetRequires:Afterに似ていますが、より強い依存関係を示します。必要なユニットが失敗した場合、このユニットは開始されないか、停止されます。
ini Requires=docker.serviceWants:Requiresの弱い形式です。必要なユニットが失敗したり見つからなかったりしても、このユニットは引き続き開始を試みます。これは、クリティカルではない依存関係の場合、通常Requiresよりも好まれます。
ini Wants=syslog.target
[Service] セクション
このセクションでは、サービスの起動方法、停止方法、および動作方法を含む、実行パラメータを定義します。
-
Type: プロセス起動のタイプを定義します。systemdがサービスを監視する方法にとって極めて重要です。simple(デフォルト):ExecStartコマンドがサービスのメインプロセスです。systemdはExecStartが呼び出された直後にサービスが開始されたと見なします。プロセスがフォアグラウンドで永続的に実行されることを期待します。forking:ExecStartコマンドが子プロセスをフォークし、親プロセスが終了します。systemdは、親プロセスが終了した時点でサービスが開始されたと見なします。アプリケーションがデーモン化する場合に使用します。oneshot:ExecStartコマンドは、完了すると終了する一度限りのプロセスです。タスクを実行して終了するスクリプト(例:バックアップスクリプト)に役立ちます。notify:simpleに似ていますが、サービスが準備完了になったときにsystemdに通知を送信します。libsystemd-devとアプリケーション内の特定のコードが必要です。idle:ExecStartコマンドは、すべてのジョブが終了した場合にのみ実行され、システムがほとんどアイドル状態になるまで実行が遅延されます。
ini Type=simple -
ExecStart: サービス開始時に実行されるコマンドです。これはこのセクションで最も重要なディレクティブです。実行可能ファイルまたはスクリプトへの絶対パスを常に使用してください。
ini ExecStart=/usr/bin/python3 /opt/my_app/app.py ExecStop: サービス停止時に実行されるコマンド(オプション)。指定しない場合、systemdはプロセスにSIGTERMを送信します。
ini ExecStop=/usr/bin/pkill -f 'my_app/app.py'ExecReload: サービスの構成をリロードするために実行されるコマンド(オプション)。
ini ExecReload=/bin/kill -HUP $MAINPIDUser: サービスのプロセスが実行されるユーザーアカウント。セキュリティ上不可欠です。絶対に必要な場合を除き、rootは避けてください。
ini User=myappuserGroup: サービスのプロセスが実行されるグループアカウント。
ini Group=myappgroupWorkingDirectory: 実行されるコマンドの作業ディレクトリ。
ini WorkingDirectory=/opt/my_appRestart: サービスが自動的に再起動されるタイミングを定義します。no(デフォルト): 再起動しない。on-success: サービスが正常に終了した場合にのみ再起動する。on-failure: サービスがゼロ以外のステータスコードで終了した場合、またはシグナルによってキルされた場合にのみ再起動する。always: 終了ステータスに関係なく、常にサービスを再起動する。
ini Restart=on-failure
RestartSec: サービスを再起動するまでの待機時間(例:5秒の場合は5s)。
ini RestartSec=5sEnvironment: 実行されるコマンドの環境変数を設定します。
ini Environment="APP_ENV=production" "DEBUG=false"EnvironmentFile: ファイルから環境変数を読み取ります。各行はKEY=VALUEである必要があります。
ini EnvironmentFile=/etc/default/my_appLimitNOFILE: サービスに許可されるオープンファイルディスクリプタの最大数を設定します(例:100000)。高並行性アプリケーションにとって重要です。
ini LimitNOFILE=65536
[Install] セクション
このセクションでは、サービスがブート時に自動的に開始されるように有効化される方法を定義します。
WantedBy: このサービスを「欲する」ターゲットユニットを指定します。ターゲットユニットが有効化されると、このサービスはその.wantsディレクトリにシンボリックリンクされ、ターゲットと共に開始されるようになります。multi-user.target: ほとんどのサーバーサービスにとって標準的なターゲットであり、非グラフィカルなマルチユーザーログインを持つシステムを示します。graphical.target: グラフィカル環境を必要とするサービス用。
ini WantedBy=multi-user.target
RequiredBy:WantedByに似ていますが、より強い依存関係があります。ターゲットが有効になると、このユニットも有効になり、このユニットが失敗するとターゲットも失敗します。
ヒント: サーバー上でバックグラウンドで実行することを意図したほとんどのカスタムサービスでは、
Type=simpleとWantedBy=multi-user.targetが最も一般的で適切な選択肢です。
ステップバイステップ:カスタムSystemdサービスの作成と管理
具体例として、指定されたディレクトリからファイルを配信するシンプルなPython HTTPサーバーを作成しましょう。これをsystemdサービスとしてセットアップします。
ステップ 1: アプリケーション/スクリプトの準備
まず、アプリケーションスクリプトを作成します。この例では、シンプルなPython HTTPサーバーを使用します。アプリケーション用のディレクトリ(例:/opt/my_app)を作成し、その中に app.py を配置します。
# /opt/my_app/app.py
import http.server
import socketserver
import os
PORT = int(os.environ.get("PORT", 8000))
DIRECTORY = os.environ.get("DIRECTORY", os.getcwd())
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=DIRECTORY, **kwargs)
print(f"Serving directory {DIRECTORY} on port {PORT}")
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("Server started.")
httpd.serve_forever()
ディレクトリとファイルを作成します。
sudo mkdir -p /opt/my_app
sudo nano /opt/my_app/app.py
(Pythonコードを貼り付け)
スクリプトが実行可能であることを確認します(python3 コマンドではオプションですが、良い習慣です)。
sudo chmod +x /opt/my_app/app.py
セキュリティ上の理由から、サービス専用の専用ユーザーを作成することを検討してください。
sudo useradd --system --no-create-home myappuser
アプリケーションディレクトリに対して適切な所有権を設定します。
sudo chown -R myappuser:myappuser /opt/my_app
ステップ 2: ユニットファイルの作成
次に、Pythonアプリケーションのsystemdユニットファイルを作成します。ファイル名は my_app.service とします。
sudo nano /etc/systemd/system/my_app.service
以下の内容を貼り付けます。
# /etc/systemd/system/my_app.service
[Unit]
Description=私のカスタムPython HTTPサーバー
Documentation=https://github.com/example/my_app
After=network.target
[Service]
Type=simple
User=myappuser
Group=myappuser
WorkingDirectory=/opt/my_app
Environment="PORT=8080" "DIRECTORY=/var/www/html"
ExecStart=/usr/bin/python3 /opt/my_app/app.py
Restart=on-failure
RestartSec=10s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
注: サービスの出力がsystemdジャーナルに送信されるように
StandardOutput=journalとStandardError=journalを設定しました。これにより、journalctlを使用したログの表示が容易になります。
ステップ 3: ユニットファイルの配置
指示どおり、ユニットファイルを /etc/systemd/system/ に配置しました。カスタムユニットファイルはここに配置する必要があります。
ステップ 4: Systemdデーモンのリロード
ユニットファイルを作成または変更した後、systemdに変更を通知する必要があります。これはsystemdデーモンをリロードすることで行われます。
sudo systemctl daemon-reload
ステップ 5: サービスの開始
これでサービスを開始できます。
sudo systemctl start my_app.service
ステップ 6: サービスの状態とログの確認
サービスが正しく実行されていることを確認します。
systemctl status my_app.service
出力例(一部省略):
● my_app.service - 私のカスタムPython HTTPサーバー
Loaded: loaded (/etc/systemd/system/my_app.service; disabled; vendor preset: enabled)
Active: active (running) since Tue 2023-10-26 10:30:00 UTC; 5s ago
Docs: https://github.com/example/my_app
Main PID: 12345 (python3)
Tasks: 1 (limit: 1100)
Memory: 6.5M
CPU: 45ms
CGroup: /system.slice/my_app.service
└─12345 /usr/bin/python3 /opt/my_app/app.py
Oct 26 10:30:00 yourhostname python3[12345]: Serving directory /var/www/html on port 8080
Oct 26 10:30:00 yourhostname python3[12345]: Server started.
サービスのログを表示するには、journalctl を使用します。
journalctl -u my_app.service -f
このコマンドは my_app.service のログを表示し、-f (follow) は新しいログをリアルタイムで表示します。
ブラウザまたは curl から http://localhost:8080 をテストすることもできます(/var/www/html が存在し、いくつかのファイルが含まれていると仮定します)。
ステップ 7: 自動起動のためのサービスの有効化
システムが起動するたびにサービスが自動的に開始されるようにするには、有効化する必要があります。
sudo systemctl enable my_app.service
このコマンドは、/etc/systemd/system/multi-user.target.wants/my_app.service から /etc/systemd/system/my_app.service へのシンボリックリンクを作成します。
ステップ 8: サービスの停止と無効化
実行中のサービスを停止するには:
sudo systemctl stop my_app.service
サービスがブート時に自動的に開始されないようにするには(手動で開始できるように残しておく場合):
sudo systemctl disable my_app.service
サービスを完全に削除したい場合は、まず disable してから stop し、最後に /etc/systemd/system/ から .service ファイルを削除し、sudo systemctl daemon-reload を実行します。
ステップ 9: サービスの更新
app.py スクリプトまたは my_app.service ユニットファイルを変更した場合、systemdを更新し、サービスを再起動する必要があります。
/opt/my_app/app.pyまたは/etc/systemd/system/my_app.serviceを編集します。- ユニットファイルを変更した場合は、
sudo systemctl daemon-reloadを実行します。 - サービスを再起動します:
sudo systemctl restart my_app.service。
ベストプラクティスとトラブルシューティング
- 絶対パス:
ExecStart、WorkingDirectory、およびユニットファイル内のその他のファイルパスには、必ず絶対パスを使用してください。相対パスは予期しない動作を引き起こす可能性があります。 - 専用ユーザー: サービスは、特権のない専用のユーザーアカウント(例:
myappuser)で実行し、侵害された場合の潜在的な損害を制限するためにセキュリティを強化します。 - 明確なロギング: サービスの出力をsystemdジャーナルに送るために
StandardOutput=journalおよびStandardError=journalを活用します。ログを表示するにはjournalctl -u <サービス名>を使用します。 - 依存関係: 起動順序(例:ネットワーキング、データベース)に関してサービスが正しく開始されることを保証するために、
After、Wants、Requiresを慎重に検討してください。 - 変更のテスト: サービスをブート時に開始するように有効化する前に、手動で開始および停止して徹底的にテストします。ステータスとログを確認してください。
- リソース制限: 暴走するサービスがすべてのシステムリソースを消費するのを防ぐために、
LimitNOFILE、LimitNPROC、MemoryLimitなどのディレクティブを使用します。 - 環境変数: ユニットファイルやスクリプトにハードコーディングするのではなく、変更されたり環境によって異なったりする可能性のある構成値には
Environment=またはEnvironmentFile=を使用します。 - スクリプト内のエラー処理: アプリケーションスクリプトがエラーを優雅に処理することを確認してください。ゼロ以外の終了コードは
Restart=on-failureをトリガーします。
警告:
/usr/lib/systemd/system/内のユニットファイルを直接変更しないでください。変更はパッケージの更新によって上書きされる可能性が高いです。カスタムユニットまたはオーバーライドには/etc/systemd/system/を使用してください。
結論
Systemdユニットファイルは、Linuxシステム上のプロセスやアプリケーションを管理するための強力で柔軟なメカニズムです。その構造と主要なディレクティブを理解することで、カスタムサービスの起動、シャットダウン、および監視を効果的に標準化し、システムの安定性を高め、管理を簡素化できます。ExecStart を使用した起動コマンドの定義から、After を使用した依存関係の管理、WantedBy を使用した自動起動の有効化まで、カスタムアプリケーションをsystemdエコシステムにシームレスに統合するためのツールが揃いました。この基本的なスキルは、堅牢で信頼性の高いLinuxデプロイメントを維持するために非常に貴重です。
より高度なサービス管理シナリオのために、タイマー(.timer)、ソケットアクティベーション(.socket)、cgroupといった高度なsystemd機能をさらに探求し続けてください。