Linuxでsystemdサービスユニットを作成する方法
systemdは多くの論争の対象となっており、一部のディストリビューションはそれを排除するためにフォークされたほどです(例えば、DebianのフォークであるDevuanは、デフォルトでsystemdをsysvinitに置き換えています)。最終的に、systemdはLinuxの世界における事実上の標準のinitシステムとなっています。
このチュートリアルでは、systemdサービスの構造を見て、作成方法を学びます。
このチュートリアルでは、あなたは学びます:
- サービスユニットとは何ですか?
- サービスユニットのセクションは何ですか?
- 各セクションで使用できる最も一般的なオプションは何ですか。
- 定義できるサービスの異なる種類は何ですか。
ソフトウェアの要件と使用される規約
systemd イニットシステム
RHEL、CentOS、Fedora、Ubuntu、Debian、Archlinuxなど、主要なディストリビューションはすべてsystemdを初期化システムとして採用しています。実際、systemdは単なる初期化システム以上のものであり、これが一部の人々がその設計に強く反対する理由の一つです。それは、確立されたUnixのモットー「一つのことをしっかりとやる」に反しています。他の初期化システムがサービスを管理するためにシンプルなシェルスクリプトを使用するのに対し、systemdは独自の.serviceファイル(.serviceサフィックスを持つユニット)を使用します。このチュートリアルでは、それらがどのように構成され、作成およびインストールする方法を見ていきます。
サービスユニットの構造
サービスユニットとは何ですか?.serviceのサフィックスを持つファイルは、systemdによって管理されるプロセスに関する情報を含んでいます。それは主に三つのセクションで構成されています:
- [ユニット]: このセクションには、ユニットのタイプとは具体的に関係のない情報が含まれています。サービスの説明など。
- [サービス]: この場合、単位の特定のタイプに関する情報を含んでいます
- [インストール]: このセクションでは、ユニットのインストールに関する情報が含まれています。
それぞれを詳しく分析しましょう。
[ユニット] セクション
[Unit] セクションは、.service ファイル内のユニットの説明と、その動作や依存関係に関する情報を含んでいます:(正しく動作するためには、サービスは別のサービスに依存することがあります)。ここでは、このセクションで使用できるいくつかの最も関連性の高いオプションについて説明します。
「説明」オプション
まず最初に、Description オプションがあります。このオプションを使用することで、ユニットの説明を提供できます。この説明は、例えば、systemd の状態の概要を返す systemctl コマンドを呼び出すときに表示されます。ここでは、例として Fedora システムでの httpd サービスの説明がどのように定義されているかを示します:
[Unit]
Description=The Apache HTTP Server
「後」オプション
「After」オプションを使用することで、スペースで区切られたリストの形式で提供するユニットの後に自分のユニットを開始する必要があることを示すことができます。例えば、Apacheウェブサービスが定義されているサービスファイルを再度確認すると、次のようになります:
After=network.target remote-fs.target nss-lookup.target httpd-init.service上の行は、systemdに対して、network、remove-fs、nss-lookupターゲットとhttpd-initサービスの後にのみ、サービスユニットhttpd.serviceを起動するよう指示しています。
「Requires」を使用してハード依存関係を指定すること
先に簡単に述べたように、ユニット(私たちのケースではサービス)は、正しく機能するために他のユニット(必ずしも「サービス」ユニットではない)に依存することがあります。このような依存関係は、Requiresオプションを使用して宣言することができます。
サービスが依存するユニットのいずれかが起動しない場合、そのサービスのアクティベーションは停止します。これが、これらを「ハード依存関係」と呼ぶ理由です。この行は、avahi-daemonのサービスファイルから抽出されたもので、avahi-daemon.socketユニットに依存していることがどのように宣言されているかを見ることができます。
Requires=avahi-daemon.socket「Wants」を使用して「ソフト」依存関係を宣言する
私たちは、Requiresオプションを使用してサービスのいわゆる「ハード」依存関係を宣言する方法を見ましたが、Wantsオプションを使用して「ソフト」依存関係もリストすることができます。
違いは何ですか?上で述べたように、いかなる「ハード」依存関係が失敗した場合、サービスも失敗します。しかし、いかなる「ソフト」依存関係の失敗は、依存するユニットに何が起こるかには影響しません。提供された例では、docker.serviceユニットがdocker-storage-setup.serviceに対してソフト依存関係を持っている様子が見て取れます。
[Unit]
Wants=docker-storage-setup.service
[サービス] セクション
サービスユニットの[Service]セクションでは、サービスが開始されたときに実行されるコマンドや、サービス自体の種類などを指定できます。それらのいくつかを見てみましょう。
サービスの開始、停止、および再読み込み
サービスは開始、停止、再起動、または再読み込みできます。これらの各アクションを実行する際に実行されるコマンドは、[Service]セクションの関連オプションを使用して指定できます。
サービスが開始されるときに実行されるコマンドは、ExecStartオプションを使用して宣言されます。オプションに渡される引数は、スクリプトへのパスであることもできます。オプションとして、サービスが開始される前後に実行されるコマンドを、それぞれExecStartPreおよびExecStartPostオプションを使用して宣言することもできます。ネットワークマネージャーサービスを開始するために使用されるコマンドは以下の通りです:
[Service]
ExecStart=/usr/sbin/NetworkManager --no-daemon
同様に、サービスが再読み込みまたは停止されたときに実行されるコマンドを、ExecStopおよびExecReloadオプションを使用して指定できます。ExecStartPostで起こることと同様に、プロセスが停止した後に起動するコマンドまたは複数のコマンドを、ExecStopPostオプションで指定できます。
サービスの種類
systemdは、サービスの期待される動作に応じて、いくつかの異なるタイプのサービスを定義し区別します。サービスのタイプは、Typeオプションを使用して、これらの値のいずれかを指定することによって定義できます:
- シンプル
- フォーク
- ワンショット
- dbus
- 通知する
サービスのデフォルトタイプは、TypeおよびBusnameオプションが定義されていない場合、かつExecStartオプションを介してコマンドが提供されている場合、simpleです。このタイプのサービスが設定されると、ExecStartで宣言されたコマンドがメインプロセス/サービスと見なされます。
フォーク型は異なる動作をします:ExecStartで提供されるコマンドはフォークして子プロセスを起動することが期待されており、これがメインプロセス/サービスになります。親プロセスは、起動プロセスが終了した後に終了することが期待されています。
デフォルトでは、TypeおよびExecStartオプションが定義されていない場合、oneshotタイプが使用されます。このタイプはsimpleとほぼ同じように動作しますが、プロセスは他のユニットが起動される前にその作業を完了することが期待されるという違いがあります。ただし、RemainAfterExitオプションが「yes」に設定されている場合(デフォルトは「no」)、コマンドが終了した後でもユニットは「アクティブ」と見なされます。
次のタイプのサービスはdbusです。このタイプのサービスが使用される場合、デーモンはBusNameオプションに指定されたとおりにDbusから名前を取得することが期待されます。これが必須になります。それ以外はsimpleタイプと同様に動作します。ただし、DBus名が取得された後に限り、次のユニットが起動されます。
もう一つのプロセスは、simpleと似たように動作し、notifyです:違いは、デーモンがsd_notify関数を介して通知を送信することが期待されていることです。この通知が送信された後にのみ、次のユニットが起動されます。
プロセスのタイムアウトを設定する
特定のオプションを使用することで、サービスのためのタイムアウトを定義することが可能です。まずは RestartSec から始めましょう。このオプションを使用することで、systemd がサービスを再起動する前に待機する時間(デフォルトでは秒単位)を設定できます。このオプションの値として、例えば「5分20秒」のように時間の範囲も利用できます。デフォルトは 100ms です。
TimeoutStartSec と TimeoutStopSec オプションは、それぞれサービスの起動と停止のタイムアウトを秒単位で指定するために使用できます。最初のケースでは、指定されたタイムアウト後にデーモンの起動プロセスが完了していない場合、それは失敗と見なされます。
第二のケースでは、サービスが停止されるべきだが、指定されたタイムアウト後も終了しない場合、まず SIGTERM が送信され、次に同じ時間が経過した後に SIGKILL 信号が送信されます。両方のオプションには、値としての時間範囲も受け入れられ、同時に設定することができ、ショートカットとして TimeoutSec を使用できます。infinity が値として提供されると、タイムアウトは無効になります。
最後に、RuntimeMaxSecを使用して、サービスが実行できる時間の制限を設定できます。このタイムアウトを超えたサービスは終了され、失敗したと見なされます。
[インストール] セクション
[install]セクションでは、サービスのインストールに関連するオプションを使用できます。たとえば、Aliasオプションを使用することで、systemctlコマンド(enableを除く)を使用する際にサービスに使用されるエイリアスのスペースで区切られたリストを指定できます。
「[Unit]」セクションの Requires と Wants オプションで発生するのと同様に、依存関係を確立するために、「[install]」セクションでは RequiredBy と WantedBy を使用できます。どちらの場合も、設定しているユニットに依存するユニットのリストを宣言します。前者のオプションではそれに強く依存し、後者では弱く依存すると見なされます。例えば:
[Install]
WantedBy=multi-user.target
上記の行で、multi-user ターゲットが私たちのユニットにソフト依存性を持つことを宣言しました。systemd の用語では、.target サフィックスで終わるユニットは、他の初期化システムで runtimes と呼ばれていたものに関連付けることができます。したがって、私たちの場合、multi-user ターゲットに達したときには、私たちのサービスを含むべきです。
サービスユニットの作成とインストール
systemdサービスユニットがインストールされるファイルシステム内の基本的な場所は2つあります: /usr/lib/systemd/system と /etc/systemd/system です。前者のパスはインストールされたパッケージによって提供されるサービスに使用され、後者はシステム管理者が独自のサービス用に使用でき、デフォルトのサービスを上書きすることができます。
カスタムサービスの例を作成しましょう。特定のEthernetインターフェース(ここではens5f5)のウェイクオンLAN機能を起動時に無効にし、停止時に再度有効にするサービスを作成したいとします。主要なタスクを達成するためにethtoolコマンドを使用できます。私たちのサービスファイルがどのように見えるかは以下の通りです:
[Unit]
Description=Force ens5f5 ethernet interface to 100Mbps
Requires=Network.target
After=Network.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/ethtool -s ens5f5 wol d
ExecStop=/usr/sbin/ethtool -s ens5f5 wol g
[Install]
WantedBy=multi-user.target
私たちは単純なユニットの説明を設定し、サービスがnetwork.targetユニットに依存し、それが到達した後に起動するべきであると宣言しました。[Service]セクションでは、サービスのタイプをoneshotとして設定し、コマンドが実行された後にサービスがアクティブであるとsystemdに考慮させるためにRemainAfterExitオプションを使用しました。また、サービスが起動および停止する際に実行されるコマンドも定義しました。最後に、[Install]セクションでは、基本的に私たちのサービスがmulti-userターゲットに含まれるべきであると宣言しました。
サービスをインストールするために、ファイルを/etc/systemd/systemディレクトリにwol.serviceとしてコピーし、その後起動します:
sudo cp wol.service /etc/systemd/system && sudo systemctl start wol.service次のコマンドを使用して、サービスがアクティブであることを確認できます:
systemctl is-active wol.service
active
コマンドの出力は予想通り、activeです。次に、「wake on lan」がdに設定されていることを確認します。現在、それは無効化されていますので、以下を実行できます:
sudo ethtool ens5f5|grep Wake-on
Supports Wake-on: pg
Wake-on: d
今、サービスを停止することで逆の結果が得られるはずです。そして、WOLを再度有効にします:
sudo systemctl stop wol.service && sudo ethtool ens5f5|grep Wake-on
Supports Wake-on: pg
Wake-on: g
結論
このチュートリアルでは、systemd サービスファイルがどのように構成されているか、そのセクションが何であるか、そして各セクションで使用できるオプションのいくつかを見ました。サービスの説明をセットアップし、依存関係を定義し、開始、停止、またはリロードされるときに実行されるべきコマンドを宣言する方法を学びました。
systemdは、好むと好まざるとにかかわらず、Linuxの世界で標準のinitシステムとなったため、そのやり方に慣れることが重要です。公式のsystemdサービスドキュメントはfreedesktopのウェブサイトにあります。また、systemdを使ったサービスの管理に関する記事にも興味があるかもしれません。
