はじめに
お恥ずかしながら最近、仕事で初めてRHEL7(7.6)の構築をする機会がありました。その際に、特にサービス管理周りで大幅な変更があり、戸惑いつつ調べていましたので、まとめてみたいと思います。手垢のついたトピックで恐縮ですが、少しでもなにかの復習になったりしたら幸いです。今回の記事では、下記点に関して記載しています。
- systemctl では、Unitという単位で処理を管理する
- systemctl では起動処理を並列に実行出来るようになった
- ランレベル は廃止され、target(Unitの一種) 配下の依存関係で定義されるようになった
- xinetd で実行していたようなアクセス契機でサービスを起動させる処理が、systemctl の socket で実行出来るようになった
- socketを使用する場合、デフォルトでは hosts.allow/hosts.deny 設定が効かないことがあるため、注意が必要である
~RHEL6のサービス起動・管理方式
RHEL6までは init という仕組みで、スクリプトでサービス起動・停止時の処理を実施していました。自動起動/停止設定は、ランレベルの概念により chkconfig コマンドで各ランレベルの on/off を切り替えて管理していました。スクリプトなので起動処理内容の自由度はありましたが、スクリプトが間違っているとシステムの動作が予測不能になったり、スクリプトを順番に実行する必要がありシステムの起動に時間がかかる、等の問題がありました。
RHEL7~のサービス起動・管理方式
RHEL7からは systemd と呼ばれる仕組みに変更となりました。systemd では、サービス起動処理を全て「Unit」という単位で定義します。Unit間には依存関係が定義されており、依存関係のツリーを構築した後に、依存に従い各Unitを起動します。起動順序の指定に関しては別の設定になるため、依存関係はあるけど起動順序はどちらでもよい、といった定義も可能です。これにより、起動順序の指定されていないUnitの処理を並列に実施出来るようになりました。Unit間の依存関係に関しては、「systemctl list-dependencies ...」コマンドを用いて下記のように参照することが可能です。
# systemctl list-dependencies multi-user.target
multi-user.target
├─abrt-ccpp.service
├─abrt-oops.service
├─abrt-vmcore.service
├─abrt-xorg.service
├─abrtd.service
├─atd.service
├─auditd.service
├─avahi-daemon.service
├─brandbot.path
├─chronyd.service
├─crond.service
Unitの一覧・起動設定は、下記のように表示出来ます。STATE列は、enable(自動起動有効)、disable(自動起動無効)、static(依存関係により、単体設定不可)を示しています。
# systemctl list-unit-files
UNIT FILE STATE
proc-sys-fs-binfmt_misc.automount static
dev-hugepages.mount static
dev-mqueue.mount static
proc-sys-fs-binfmt_misc.mount static
...
arp-ethers.service disabled
atd.service enabled
auditd.service enabled
...
また、ランレベルの概念は廃止され、targetと呼ばれるUnitの1種によってUnitをグループ化することにより、立ち上げるサービスを制御するようになりました。ただ、リンクで下位互換的な対応付けがされています。デフォルトの起動処理はdefault.target(リンク)で定義されています。下記のように調べると、default.target->multi-user.targetになっていることがわかります。
# ls -l default.target
lrwxrwxrwx. 1 root root 36 9月 14 19:57 default.target -> /lib/systemd/system/runlevel3.target
# ls -l /lib/systemd/system/runlevel*.target
lrwxrwxrwx. 1 root root 15 9月 14 19:33 /lib/systemd/system/runlevel0.target -> poweroff.target
lrwxrwxrwx. 1 root root 13 9月 14 19:33 /lib/systemd/system/runlevel1.target -> rescue.target
lrwxrwxrwx. 1 root root 17 9月 14 19:33 /lib/systemd/system/runlevel2.target -> multi-user.target
lrwxrwxrwx. 1 root root 17 9月 14 19:33 /lib/systemd/system/runlevel3.target -> multi-user.target
lrwxrwxrwx. 1 root root 17 9月 14 19:33 /lib/systemd/system/runlevel4.target -> multi-user.target
lrwxrwxrwx. 1 root root 16 9月 14 19:33 /lib/systemd/system/runlevel5.target -> graphical.target
lrwxrwxrwx. 1 root root 13 9月 14 19:33 /lib/systemd/system/runlevel6.target -> reboot.target
Unitについて
Unitの種類は、RedHatの公式ページを見るに12種類ありますが、基本的には下記3点の内容を意識することになるかと思います。
-.target Unitのグループ
-.service プロセスの起動/停止設定
-.socket ソケットの監視設定(アクセス契機で、同名の.serviceに紐付いてプロセスを起動出来る)
Unitの実体はテキストの設定ファイルであり、下記3種類のディレクトリに格納されます。同名のファイルが存在する場合、下に記載されているディレクトリほど高優先度で読み込まれます。
-/usr/lib/systemd/system/ インストール時の初期設定が格納されるディレクトリ。ここのファイルは編集しない。
-/run/systemd/system/ ランタイム時に作成されたUnitファイルを格納する場所らしい。公式には記載があるが、いくつか調べた構築系の記事で全く言及されていないため、あまり意識する必要はなさそう。
-/etc/systemd/system/ ユーザ設定用のディレクトリ。設定変更の際は、上記ディレクトリからここへコピーして編集する。systemdはこちらを優先して読み込む。
Unitはいずれも"systemd の作法に従った設定ファイル"であり、それを解釈して systemd がプロセス起動に関する処理を実施する、というのがRHEL6までと比べた大きな変更点です。Unitのうち3種類に関して、例を下記に示します。
targetファイル
Wants句で依存関係、After句で起動順序、Conflicts句で競合Unitを定義します。なお、Wants句の依存関係は強制ではなく、強制の場合はRequire句を使用します。
$ cat sysinit.target
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=System Initialization
Documentation=man:systemd.special(7)
Conflicts=emergency.service emergency.target
Wants=local-fs.target swap.target
After=local-fs.target swap.target emergency.service emergency.target
RefuseManualStart=yes
serviceファイル
[Service]項目にて、プロセス実行時の環境ファイル(EnvironmentFile句)や、実行時オプション(ExecStart句)を定義します。[Unit]項目は共通なので、targetと同じくWants句等も設定可能です。設定を変えるとすれば、主に実行時オプションになるかと思います。
[Unit]
Description=Simple HTTP Proxy
[Service]
ExecStart=/usr/lib/systemd/systemd-socket-proxyd 192.168.4.99:80
PrivateTmp=yes
socketファイル
[Socket]項目にて、リッスンするポートを指定します。下記例では指定していませんが、X.X.X.X:23、のように、待ち受けアドレスも指定可能です。
[Unit]
Description=Telnet Socket
[Socket]
ListenStream=23
socketファイルを使用する場合は、ソケットアクティベーション(ソケットへのTCPアクセス契機でのサービス起動)という動きになり、アクセスを受けて同名のサービスファイル(telnet.service)が起動されます。なお、通常は同じサービスファイルから一つのプロセスしか起動出来ませんが、今回の例のtelnetのように、socketを使用する場合は複数プロセスを起動する必要があるケースが多いです。その場合は、telnet@.serviceのように、サービス名の最後に「@」を付与します。公式ページを見るに、これを「Unitをインスタンス化する」と表現するみたいです。
telnet.socketに対応する、telnet@.serviceは下記のようになります。
[Unit]
Description=Telnet Server
After=local-fs.target
[Service]
ExecStart=-/usr/sbin/in.telnetd
StandardInput=socket
RHEL7~では、.socket と (@).service ファイルを作成して socket を起動することで、~RHEL6の xinetd に相当するようなことを実現可能です(※再現出来ないオプション等が一部あるようなので、移行時は要件などを調査のうえでお願いします)。
socketを使う際のアクセス制御の注意点
systemctlのアップデート情報に、下記の記載があります。
- systemd におけるネイティブの tcpwrap サポートは廃止されました。tcpwrap を使用する必要がある場合は、従来の inetd デーモンの場合と同様に、tcpd デーモンを使用して socket-activated サービスを呼び出すことを検討してください。
ここでいうtcpwrapは、hosts.allow/hosts.deny によるアクセス制御を含んでいるため、デフォルト設定でsocketを使うとアクセス制御が効かない事象が発生します(私も構築ではまりました...)。対処としては、下記のようにserviceファイルの実行時オプションを変更します。
...
[Service]
(変更前)ExecStart=-/usr/sbin/in.telnetd
↓
(変更後)ExecStart=@-/usr/sbin/tcpd /usr/sbin/in.telnetd
...
さいごに
長文にお付き合い頂きありがとうございました。今回は、たまたま仕事で直近に触れた話題を選びましたが、今後このような機会があればもう少し新しいことやニッチなことを話せればなぁと思っております。品川アドベントカレンダーの今後の記事も楽しみにさせていただきます。