朝から昼寝

整理しておきたいIT関連の小ネタ

(基本を詳しく)Systemdによるサービス管理ノウハウ

概要

Systemdは、RHEL7、CentOS7以降における基本的なサービス管理機能(などを含むシステム管理デーモンやツールの一式)です。従来はUpstart、SysVinitがありました。
本記事は、RHELCentOSのサービス管理に関するポイントをまとめたものです。 参考までに、同じくSystemdに関してAfter=network.target関連の詳しい話の記事もあります。

本記事の目的

  • Systemdで管理されているサービスやその設定を確認する。
  • Systemdで管理されているサービスを手動で停止、起動する。
  • 自身で作成したプログラム等をSystemdのサービスとして登録する。


目次

基本

サービス管理(systemctl)

本記事では、サービス管理コマンドの例としてhttpd.serviceを使用します。httpd.serviceはApache HTTP ServerサービスがSystemdに登録されたものです。適宜、サービス名を読み替えてください。
各項目の詳細は$ man systemctlで確認できます。

サービスの起動状態の一覧表示(systemctl list-units)

$ systemctl list-units --all --type=service

UNIT          LOAD   ACTIVE SUB     DESCRIPTION
…
httpd.service loaded active running The Apache HTTP Server 
…
  • ACTIVE列にてサービス起動状態を確認可能
    • active : サービスが起動した状態である
    • inactive : サービスが停止した状態である
    • failed : サービスが何らかエラーの状態である(サービス起動失敗等)
    • (その他) reloading, activating, deactivating
  • SUB列でもサービス起動状態を確認可能(typeがサービスの場合)
    • running: プロセスが起動した状態である
    • exited: 処理を終えてプロセスが終了した状態である

--type serviceはサービスのみを表示するためのオプションです。省略するとサービス以外のUnitも表示されます。
特定のサービスのみ表示させる場合は、$ systemctl list-units httpd*のようにパターン指定するか、$ systemctl list-units | grep httpdのように実行します。

ACTIVE列とSUB列の違いは微妙ですが、プロセスが常時起動するサービスは基本的にACTIVE列がactiveのときはSUB列はrunningになっているはずです。処理の実行後、プロセスが終了する類の特殊なサービスはACTIVE列がactiveかつSUB列がexitedになります。
参考までに、コマンド実行時に以下の説明が表示されます。

ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type. 

サービスの自動起動設定の一覧表示(systemctl list-unit-files)

$ systemctl list-unit-files --type service

UNIT FILE                                     STATE   
…
httpd.service                                enabled 
…
  • STATE列にてサービスの自動起動設定を確認可能
    • enabled : 自動起動が有効
    • disabled : 自動起動が無効
    • static : 単体では自動起動しない (staticはsystemctlで有効化、無効化できないもの。他サービスとの依存関係に従い起動するもの等)

サービスの起動/停止/再起動/リロード(systemctl start / stop / restart / reload)

(例としてhttpdサービスに対するコマンドを記載)
サービス起動
# systemctl start httpd
サービス停止
# systemctl stop httpd
サービス再起動
# systemctl restart httpd
サービスリロード
# systemctl reload httpd
reloadは、設定ファイルの変更点を反映する際に使用されます。例えばhttpdであれば、httpd.confの変更点の反映などです。そのサービスの詳細仕様によりますが、reloadはrestartと異なり、基本的にサービス停止せず設定反映できます。ただし、サービスによってはreloadに対応していない場合があります(ユニットファイルにExecReloadが定義されていない場合)。
補足として、httpdの場合はsystemctl graceful httpdのようにgracefulを指定する設定反映方法もありますが、詳細はhttpdのマニュアルを参照願います。

サービスの状態確認(systemctl status)

(例としてhttpdサービスに対するコマンドを記載)
サービス状態確認
$ systemctl status httpd  (★マークは説明用の目印です)

● httpd.service - The Apache HTTP Server
  Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled★; vendor preset: disabled)
  Active: active (running)★ since Thu 2022-05-01 02:02:02 UTC; 2min 2s ago
    Docs: man:httpd(8)
          man:apachectl(8)
Main PID: 5001 (httpd)
  Status: "Total requests: 0; Current requests/sec: 0; Current traffic:   0 B/sec"
  CGroup: /system.slice/httpd.service
          ├─5002 /usr/sbin/httpd -DFOREGROUND
          ├─5003 /usr/sbin/httpd -DFOREGROUND
 …

Loaded:行は、Unitファイルのパスと、サービスの自動起動設定(enabled or disabled)が表示されます。前述のサービスの自動起動設定の一覧表示と同様の内容です。
Active:行は、サービスの起動状態(active or inactive等)が表示されます。前述のサービスの起動状態の一覧表示(systemctl list-units)と同様の内容です。
Main PID:行、CGroup:行は、それぞれメインプロセスと、メインプロセスからフォークされた子プロセスです。

詳細

Unitの構成、種類

systemdはサービス等をUnitとして扱います。 Unitの構成や配置について簡単に説明します。

ユニットファイル

Unitはファイル形式で定義されます。 そのファイル名の書式はunit_name.type_extensionです。
unit_nameは、そのユニットの名前(サービスであればサービス名)です。
type_extensionは、.service(サービスを定義するもの)、.target(複数のサービスをまとめるためのもの)、.device(デバイス)、.timer(cronのように使えるもの)等、他にもあります。
詳細は、man systemd.unitや、 RedHat社のマニュアルが参考になります。

ユニットファイルの格納先パス

主に使用されるパスは以下です。

  • /etc/systemd/system/:管理者によって配置されるユニットファイル
  • /usr/lib/systemd/system/:パッケージマネージャによって配置されるユニットファイル

同名のユニットファイルが/usr/lib/systemd/system//etc/systemd/system/の両方に配置された場合、/etc/systemd/system内のファイルが優先されます。
詳細はman systemd.unitの「UNIT_FILE_LOAD_PATH」セクションが参考になります。
httpdの場合、関連するユニットファイルは以下の通りです。

$ ls -l /usr/lib/systemd/system/

(★マークは説明用の目印です)
…
-rw-r--r--  1 root root  314  3月 22 02:27  httpd-init.service
-rw-r--r--  1 root root  944  3月 22 02:27  httpd.service ★
drwxr-xr-x  2 root root   26  4月 13 09:32  httpd.service.d
-rw-r--r--  1 root root  244  3月 22 02:27  httpd.socket
drwxr-xr-x  2 root root   31  4月 13 13:17  httpd.socket.d
-rw-r--r--  1 root root  662  3月 22 02:27  httpd@.service
…

ファイルがたくさんありますが、基本的なWebサーバとして使用するhttpdサービスのユニットファイルはhttpd.serviceです。

ユニットを有効化した際の動作

# systemctl enable httpdによりユニットを有効化すると、そのユニットファイル内の定義(後述の[Install]セクション)に従い動作します。一般的なサービスであれば、/etc/systemd/system/multi-user.target.wants/配下にシンボリックリンクが作成されます。このディレクトリ配下にあるユニットファイルは通常のマルチユーザモード(multi-user.target)でのシステム起動時に読み込まれますので、システム起動時にサービス自動起動することになります。
例として、httpd.serviceユニットファイル内の[Install]セクションは以下のように記載されています。

[Install]
WantedBy=multi-user.target

このユニットを有効化すると、multi-user.target配下にシンボリックリンクが作成されるということです。

作成されたシンボリックリンクは以下です。
$ ls -l /etc/systemd/system/multi-user.target.wants/

…
lrwxrwxrwx  1 root root 37  4月 14 11:29 httpd.service -> /usr/lib/systemd/system/httpd.service
… 

ユニットファイルをカスタマイズする場合

RPM等のパッケージインストールにより、/usr/lib/systemd/system/配下にパッケージ標準のユニットファイルが格納されることが多いです。
起動時のオプション変更等、ユニットファイルをカスタマイズしたい場合、/usr/lib/systemd/system/配下のユニットファイルを直接編集せず、そのユニットファイルを/etc/systemd/system/配下にいったんコピーし、コピーしたユニットファイルを編集することをお勧めします。
/usr/lib/systemd/system/配下のユニットファイルを直接編集した場合、パッケージのアップグレード時に初期設定に戻ってしまうことがあるので、トラブル等の原因になります。
systemdは/usr/lib/systemd/system/配下より/etc/systemd/system/配下のユニットファイルが優先するので、このようなアップグレード時のトラブルを回避できます。

なお、既に自動起動が有効(enable)なサービスのユニットファイルをカスタマイズし/etc/systemd/system配下に新たに配置した際には、以下のようにサービスの無効化→有効化のやり直しが必要です。

# systemctl disable httpd.service
# systemctl enable httpd.service

これは、/usr/lib/systemd/system/配下のユニットファイルに対して既にシンボリックリンクが作成されていたサービスについて、新たに配置した/etc/systemd/system配下のユニットファイルに対してシンボリックリンクを作成し直すための操作です。

ユニットファイルの読み方

任意のプログラム等をSystemdのサービスとして登録にまとめて記載してあります。

(補足)テンプレート形式のユニット(name@xxx.service)

httpd@.serviceのように@(アットマーク)がついたサービスは、テンプレートです。例えば仮想コンソール(gettyやvnd等)で複数インスタンス起動するようなサービスの場合に使用されます。
systemctl start name@1.servicesystemctl enable name@1.serviceのように、@の後に任意の文字列を指定できます。(getty@tty1.service等)
@の後に指定した文字列は、ユニットファイル内の%l %i 変数に置き換えられます。

任意のプログラム等をSystemdのサービスとして登録

以下の流れです。

  1. サービスとして実行するプログラムを準備
  2. ユニットファイルを作成
  3. 作成したユニットファイルを読み込み、有効化

1. サービスとして実行するプログラムを準備

任意のプログラムを準備します。
プログラムの例として、簡単なシェルスクリプトを作成します。
# vi /usr/local/bin/sleeping.sh

#!/bin/sh
while true; do echo "zzz..." >> /tmp/sleeping.log; sleep 5 ; done

2. ユニットファイルを作成

/etc/systemd/system/配下にユニットファイルを作成します。各オプションの詳細はman systemd.serviceman systemd.unitで確認できます。
# vi /etc/systemd/system/sleeping.service

[Unit]
Description = just keep sleeping process

[Service]
ExecStart = /usr/local/bin/sleeping.sh
Type = simple

[Install]
WantedBy = multi-user.target

上記はとてもシンプルなユニットファイルの例です。実際の運用に際しては、マニュアルを参照の上、各オプションの精査が必要です。

  • [Unit]セクションのオプション ([Unit]セクションは任意)

    • Description:ユニットの説明
    • After:このAfterオプションで指定したユニットの起動後に自身のユニットを起動する。
    • Before:Afterの逆。
    • Wants:自身のユニットの起動時に、このWantsオプションで指定したユニットを起動する。緩い依存関係を指定しており、指定したユニットの起動が失敗しても自身のユニットは起動しようとする。
    • Requires:Wantsと同様に依存関係を指定するが、必須の依存関係を指定しており、指定したユニットの起動が失敗した場合に自身のユニットは起動失敗として扱われる。

    After、Beforeはサービス起動の「順序関係」を指定するオプションです。Wants、Requiresは「依存関係」を指定するオプションです。なお、Wants、Requiresを指定しても、起動の「順序」は制御されません。

  • [Service]セクションのオプション ([Service]セクションは必須)
    [Service]セクションは、サービス定義において重要です。いくつか記載しますが、他にも多くのオプションがあります。

    • ExecStart:サービス起動時(systemctl start)に実行するコマンド。
    • ExecStop:サービス停止時(systemctl stop)に実行するコマンド。
    • ExecReload:サービスリロード時(systemctl reload)に実行するコマンド。
    • Restart:サービスのプロセス停止時の挙動を指定。デフォルトはno(再起動しない)。例えばalwaysに指定するとプロセスが終了時には再起動される。
    • Type:サービスの起動方法。この指定に従いサービスが正常起動したかを判定する。例えば、プログラム単体を実行し続けるものであればsimpleを指定、子プロセスがそのサービスのメインプロセスになるものであればforkingを指定する。より詳細にサービスの起動判定を行いたい場合はnotify(やdbus)の指定が推奨される。
      • simple:ExecStartで指定したコマンドがサービスのメインプロセスである場合に使用する。プロセス生成した時点でサービス起動完了と判定する。ただし、プロセス生成後にExecStartで指定したコマンドを正常に実行できなくてもサービス起動が成功した扱いとなる(例:Userオプションで指定したユーザが存在しない場合でもサービス起動成功となる、等)。
      • forking:ExecStartで指定したコマンドにより実行されたプロセス(親)から起動(フォーク)された子プロセスがサービスとして動作する場合に使用する。親プロセスが終了した時点で起動完了と判定する。PIDFileオプションの使用が推奨される。なお、man systemd.serviceには、近年PIDFileが使用されないため、特に必要性が無ければType=notifyかType=simpleを使用するよう記載がある。
      • notify:simpleと同様、ExecStartで指定したコマンドがサービスのメインプロセスである場合に使用するが、そのプロセスがsystemdのライブラリ関数sd_notify()に対応している場合に使用する。プロセスは自身の起動完了をsd_notify()にてsystemdに通知する。
      • oneshot:simpleと同様、ExecStartで指定したコマンドがサービスのメインプロセスである場合に使用するが、そのプロセスが終了した時点で起動完了と判定する。その後、(RemainAfterExit=yesを指定しない限り)activeになることはなく、そのままサービスは停止(deactivatingやdead)となる。なお、RemainAfterExit=yesを指定した場合、コマンド終了後もサービスはactiveな状態となる。
    • PIDFile:PIDファイルのパスを指定する。主に、Type=forkingのサービスではこのオプションでメインプロセスのPIDファイルを指定することが推奨される。
       
  • [Install]セクションのオプション ([Install]セクションは任意)
    • WantedBy:このWantedByオプションで指定したユニットの起動時、自身のユニットを起動する。

    [Install]セクションでは、systemctl enablesystemctl disableでユニットを有効化、無効化した際の処理を定義します。例えば、enable時にはWantedByで指定したユニットの.wantsディレクトリに自身のユニットを登録する等です。 前述のWantsの逆でWantedBy、Requiresの逆でRequiredByというオプションがあります。これらは、そのオプションで指定したユニットの起動時に自身のユニットを起動するものです。

3. 作成したユニットファイルを読み込み、有効化

ユニットファイルを作成後、以下のように反映、有効化することでシステム起動時に自動起動するサービスとして設定完了です。
ユニットファイルの変更後は以下のようにsystemdにて読み込みが必要です。
# systemctl daemon-reload
作成したユニットファイルのサービスを有効化します。
# systemctl enable sleeping
手動でサービスを起動する際には以下のコマンドを実行します。
# systemctl start sleeping
上記のシェルスクリプトは、ファイル/tmp/sleeping.logに文字列を出力するものなので、ファイルの中身を確認すると、その動作を確認できます。
# tail -f /tmp/sleeping.log

参考

サービス起動時のエラー等

systemctlコマンドによるサービス起動が失敗した際には以下のようなメッセージが表示されます。
# systemctl start httpd

(サービス起動失敗時のメッセージ例)
Job for httpd.service failed because the control process exited with error code.
See "systemctl status httpd.service" and "journalctl -xe" for details. 

メッセージの指示に従い、# systemctl status httpd.service and journalctl -xeにてログを確認することが可能です。あるいは、messagesにもログが出力されている場合があります。

ユニットファイルの例

備忘用に、httpdデフォルトのユニットファイルを記載しておきます(RHEL8系)。

httpd.service (httpd本体)

/usr/lib/systemd/system/httpd.service

# See httpd.service(8) for more information on using the httpd service.

# Modifying this file in-place is not recommended, because changes
# will be overwritten during package upgrades.  To customize the
# behaviour, run "systemctl edit httpd" to create an override unit.

# For example, to pass additional options (such as -D definitions) to
# the httpd binary at startup, create an override unit (as is done by
# systemctl edit) and enter the following:

#       [Service]
#       Environment=OPTIONS=-DMY_DEFINE

[Unit]
Description=The Apache HTTP Server
Wants=httpd-init.service
After=network.target remote-fs.target nss-lookup.target httpd-init.service
Documentation=man:httpd.service(8)

[Service]
Type=notify
Environment=LANG=C

ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful
# Send SIGWINCH for graceful stop
KillSignal=SIGWINCH
KillMode=mixed
PrivateTmp=true

[Install]
WantedBy=multi-user.target

httpd-init.service (TLS用の鍵生成をするためのoneshotのサービス)

/usr/lib/systemd/system/httpd-init.service

[Unit]
Description=One-time temporary TLS key generation for httpd.service
Documentation=man:httpd-init.service(8)

ConditionPathExists=|!/etc/pki/tls/certs/localhost.crt
ConditionPathExists=|!/etc/pki/tls/private/localhost.key

[Service]
Type=oneshot
RemainAfterExit=no

ExecStart=/usr/libexec/httpd-ssl-gencerts

httpd\@.service (テンプレート)

/usr/lib/systemd/system/httpd\@.service

# This is a template for httpd instances.
# See httpd@.service(8) for more information.

[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=man:httpd@.service(8)

[Service]
Type=notify
Environment=LANG=C
Environment=HTTPD_INSTANCE=%i
ExecStartPre=/bin/mkdir -m 710 -p /run/httpd/instance-%i
ExecStartPre=/bin/chown root.apache /run/httpd/instance-%i
ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND -f conf/%i.conf
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful -f conf/%i.conf
# Send SIGWINCH for graceful stop
KillSignal=SIGWINCH
KillMode=mixed
PrivateTmp=true

[Install]
WantedBy=multi-user.target