Linuxの起動処理は、これまでinit/upstartと呼ばれる仕組みで行われていました。Red Hat Enterprise Linux 7 (RHEL7)では、これが、systemdと呼ばれるまったく新しい仕組みに置き換わります。Fedoraでは、すでに先行してsystemdが採用されていますが、この連載(?)では、Fedora 17での実装をベースとして、systemdの考え方や仕組み、利用方法を説明していきます。今回は、systemdの動作の基礎となる「Unit」の概念を理解します。
systemdを採用したFedoraでLinuxの基礎を学びなそう!という方には、「「独習Linux専科」サーバ構築/運用/管理――あなたに伝えたい技と知恵と鉄則」がお勧めです。(^^/
systemdの考え方
参考資料
・Rethinking PID 1:systemdの開発者であるLennartによるイントロダクション。
・systemd System and Service Manager:systemdのプロジェクトページ
従来のinit/upstratでは、「サービス」の単位で処理が管理されており、大雑把には次の流れでシステムが起動します。
・/etc/rc.d/rc.sysinitでルートファイルシステムのマウントなど、サービス起動の下準備を行う。
・/etc/init.d/以下のシェルスクリプトで順次サービスを起動する。
rc.sysinitを含めて、個々のサービスの起動処理はすべてシェルスクリプトで実装されており、スクリプトの書き方次第でさまざまな処理が実施可能ですが、その一方でいくつかの欠点もあります。
・お作法に従わないシェルスクリプトがあるとシステムの動作が予測不能になる。
・シェルスクリプトを順番に実行していくので、システムの起動に時間がかかる。
・システム起動後にオンデマンドでサービスを起動・停止する仕組みがない。
・サービスごとにcgroupsでリソース配分を調整するなどの仕組みがない。
最後のcgroupsは、サービスの起動スクリプトを書き換えて対応することもできなくはないですが、すべてのサービスが自動的にcgroupsの管理下に入るようなシステム標準の仕組みがあった方が便利ですよね。systemdは、シェルスクリプトに依存した起動処理を完全に排除してこれらの問題を解決します。
Unitによる管理
systemdでは、「Unit」という単位で処理を管理します。これまで、rc.sysinitやサービス起動スクリプトが実施していた処理の内容は、すべて、Unitとして定義されます。Unitは、「target」「mount」「service」「device」など、役割によってタイプがわかれています。それぞれのUnitは、依存関係が定義されており、PID=1の最初のプロセスとしてsystemdが起動すると、「default.target」というUnitを頂点とする、依存関係のツリーを構築した後、依存するUnitを起動していきます。
この時、それぞれのUnitは、依存関係(AはBが必要)の情報とは別に、起動順序(AはBの後に起動)についての設定が与えられます。起動順序の指定がない場合、依存関係のあるUnitについても並列に起動処理が行われます。systemdは、順序関係の情報をもとにして、複数のUnitをできるかぎり並列に起動していきます。
Unit間の「依存関係」と「順序関係」を分けて定義できるところが、systemdの特徴と言えるかも知れません。Upstartでは、イベントの連鎖によって依存する処理が順次実行されていきますが、この場合は、「依存関係=順序関係」となるため、「依存はしているが並列に起動してもかまわない」というような処理を並列化することができませんでした。
さらに、Unitの中には、「特定のデバイスが接続された」「D-Bus経由でサービスが要求された」など、システム稼働中に発生するイベントをトリガーとして、オンデマンドに起動するものもあります。つまり、必ずしもシステム起動時に立ち上げる必要の無いサービスを起動処理から除外することが可能になります。
このように、systemdを利用すると、
・シェルスクリプトを使わずにsystemdが直接Unit(サービス)を起動する。
・Unitの起動処理を可能な限り並列化する。
・Unitの起動をオンデマンド化する。
という工夫にによって、システムの起動時間が圧倒的に短縮されます。
ちなみに、LennartのBlogによると、systemdが標準提供するUnitで実施可能な処理には、次のようなものがあります。
- Checking and mounting of all file systems
- Updating and enabling quota on all file systems
- Setting the host name
- Configuring the loopback network device
- Loading the SELinux policy and relabelling /run and /dev as necessary on boot
- Registering additional binary formats in the kernel, such as Java, Mono and WINE binaries
- Setting the system locale
- Setting up the console font and keyboard map
- Creating, removing and cleaning up of temporary and volatile files and directories
- Applying mount options from /etc/fstab to pre-mounted API VFS
- Applying sysctl kernel settings
- Collecting and replaying readahead information
- Updating utmp boot and shutdown records
- Loading and saving the random seed
- Statically loading specific kernel modules
- Setting up encrypted hard disks and partitions
- Spawning automatic gettys on serial kernel consoles
- Maintenance of Plymouth
- Machine ID maintenance
- Setting of the UTC distance for the system clock
ファイルシステムのfsckとマウントなど、これまで、rc.sysinitの中で実行されていた処理も個別のUnitとして提供されていることがわかります。
Unitの依存関係
まず、Unitの定義ファイルは以下のディレクトリ配下にあります。/usr/lib/systemd/system/以下の設定ファイルを変更する際は、一旦、/etc/systemd/system/以下にコピーしてから変更するようにします。
ディレクトリ | 説明 |
---|---|
/usr/lib/systemd/system | システムデフォルトの設定。RPMパッケージが提供するデフォルト設定を配置する。 |
/etc/systemd/system | ユーザ設定。ここに同名のファイルを配置するとこちらのファイルの内容が優先される。 |
Unitには、「target」「service」などのタイプがあると説明しましたが、設定ファイル名の末尾「.target」「.service」で判別ができます。また、ある設定ファイルに対して、「設定ファイル名.wants」というディレクトリが用意されている場合、このディレクトリ内に前提となるUnitを記載する(前提Unitの設定ファイルへのシンボリックリンクを作成する)ことで、依存関係が定義されます。
依存関係の定義方法には、.wantsディレクトリを使う他に、設定ファイル内の[Unit]セクションにおいて、「Wants=」オプション、もしくは「Requires=」オプションで指定する方法があります。一般に、システム的に必須の依存関係は「Wnats/Requires=」オプションに記載しておき、システム管理者が環境に応じて設定する依存関係は、.wantsディレクトリを使用します。特に、後述のsystemctlコマンドで、サーバ起動時に自動起動するサービスを設定した場合は、.wantsディレクトリにシンボリックリンクを作成することで、自動起動が設定されます。
「Wants=」と「Requires=」には微妙な違いがあります。Requiresの場合は、前提のUnitが起動に失敗すると、このUnitの起動処理を中断します。一方、Wantsの場合は、前提のUnitが起動に失敗した場合でも、このUnitの起動処理は継続します。
話が抽象的になってきたので、少し具体例を示しておきましょう。まず、主なUnitのタイプをまとめておきます。
Unitのタイプ | 説明 |
---|---|
mount | 指定のファイルシステムをマウントする |
automount | オートマウント処理を実施する(automountdの代替的な機能) |
service | 指定のバイナリを実行する(主にはデーモンの起動に使用する) |
socket | systemdがSocketをListenして、接続があるとプロセスに受け渡す(xinetdの代替的な機能) |
path | 指定のファイルが作成されると、指定されたサービスを起動する |
device | udevから通知されたデバイスを表す |
target | 複数のUnitをまとめるために使用する |
上記のようにデーモンプロセスの起動など、従来の「サービス」に相当するUnitは「service」です。一方、「target」は、複数のUnitを束ねるために使用するもので、それ自体は特定の処理を行うわけではありません。たとえば、従来rc.sysinitの内部で行っていた処理に対応するservice群を「sysinit.target」の前提Unitとしてまとめておけば、「sysinit.taget」を起動することで、これらをまとめて実行することができます。
sysinit.targetに対して、依存関係が設定されているUnitは、「system.target.wants」ディレクトリにあるはずですが、実際に確認すると次のようになっています。
# tree /usr/lib/systemd/system/sysinit.target.wants /usr/lib/systemd/system/sysinit.target.wants |-- cryptsetup.target -> ../cryptsetup.target |-- dev-hugepages.mount -> ../dev-hugepages.mount |-- dev-mqueue.mount -> ../dev-mqueue.mount |-- plymouth-read-write.service -> ../plymouth-read-write.service |-- plymouth-start.service -> ../plymouth-start.service |-- proc-sys-fs-binfmt_misc.automount -> ../proc-sys-fs-binfmt_misc.automount |-- sys-fs-fuse-connections.mount -> ../sys-fs-fuse-connections.mount |-- sys-kernel-config.mount -> ../sys-kernel-config.mount |-- sys-kernel-debug.mount -> ../sys-kernel-debug.mount |-- systemd-ask-password-console.path -> ../systemd-ask-password-console.path |-- systemd-binfmt.service -> ../systemd-binfmt.service |-- systemd-journald.service -> ../systemd-journald.service |-- systemd-modules-load.service -> ../systemd-modules-load.service |-- systemd-random-seed-load.service -> ../systemd-random-seed-load.service |-- systemd-sysctl.service -> ../systemd-sysctl.service |-- systemd-tmpfiles-setup.service -> ../systemd-tmpfiles-setup.service `-- systemd-vconsole-setup.service -> ../systemd-vconsole-setup.service
前述のように、.wantsディレクトリ配下に前提Unitの設定ファイルへのシンボリックリンクが作成されています。それぞれのUnit名から、特殊ファイルシステムのマウントやカーネルパラメータの設定(sysctl)などの処理が想像されます。それから、設定ファイル内に直接に記載された前提Unitもあります。次のように、「local-fs.target」と「swap.target」が前提として設定されています。このように、targetの前提として、さらに他のtargetをネストすることもできるわけです。
# cat /usr/lib/systemd/system/sysinit.target | grep -E '(Wants|Requires)' Wants=local-fs.target swap.target
それでは、「sysinit.target」自体はどのようにして起動されるのでしょうか? システム上でsystemdが起動すると、まずはじめに「default.target」を起動するようになっています。したがって、default.targetの前提としてsysinit.targetを設定することで、これが行われます。
default.targetからの依存関係ツリー
それでは改めて、default.targetからどのような依存関係が定義されているかを見てみましょう。default.targetはシステム管理者が変更できるように、/etc/systemd/system/以下に用意されており、他のtargetへのシンボリックリンクになっています。このシンボリックリンクが指す先を変えることは、従来の「デフォルトrunlevelを変更する」という処理に相当します。テキストログイン環境のFedora 17では、次のようになっています。
# 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/runlevel3.target lrwxrwxrwx. 1 root root 17 9月 14 19:33 /lib/systemd/system/runlevel3.target -> multi-user.target
ちょっとまわりくどいですが、シンボリックリンクを2回たどって、「/lib/systemd/system/multi-user.target」を差しています。最初からmulti-user.targetを指してもいいのですが、旧来のrunlevelとの対応が分かるように、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
したがって、systemdは、「multi-user.target」の前提となるUnitを探して、これを起動します。これもまた、システム管理者が変更できるように、/etc/systemd/system以下に.wantsディレクトリが用意されています。
# tree /etc/systemd/system/multi-user.target.wants /etc/systemd/system/multi-user.target.wants |-- NetworkManager.service -> /usr/lib/systemd/system/NetworkManager.service |-- arp-ethers.service -> /usr/lib/systemd/system/arp-ethers.service |-- auditd.service -> /usr/lib/systemd/system/auditd.service |-- crond.service -> /usr/lib/systemd/system/crond.service |-- remote-fs.target -> /usr/lib/systemd/system/remote-fs.target |-- rsyslog.service -> /usr/lib/systemd/system/rsyslog.service |-- sendmail.service -> /usr/lib/systemd/system/sendmail.service |-- sm-client.service -> /usr/lib/systemd/system/sm-client.service `-- sshd.service -> /usr/lib/systemd/system/sshd.service
それっぽいserviceが起動されることがわかります。設定ファイルに内に直接に記載された前提は次のとおりです。
# cat /usr/lib/systemd/system/multi-user.target | grep -E '(Wants|Requires)' Requires=basic.target # cat /usr/lib/systemd/system/basic.target | grep -E '(Wants|Requires)' Requires=sysinit.target sockets.target
これもちょっとまわりくどいですが、「multi-user.target -> basic.target -> sysinit.target」という順番で、最終的にsysinit.targetがmulti-user.targetの前提になっていることがわかります。おそらく、複数の"runlevel"で必要なサービスをbasic.targetにまとめているのでしょう。
この例から分かるように、設定ファイルだけを追って、依存関係の全体像を把握するのはかなり面倒です。Fedora 19では、「systemctl list-dependencies」コマンドにより、systemdが実際に認識している依存関係を表示することができるようになっています。
systemctlコマンドによるUnitの確認
「systemctl list-units」コマンドを利用すると、現在の設定で稼働しているUnitを確認することができます。次は、稼働しているserviceの一覧です。
# systemctl list-units --type=service UNIT LOAD ACTIVE SUB JOB DESCRIPTION auditd.service loaded active running Security Auditing Service crond.service loaded active running Command Scheduler dbus.service loaded active running D-Bus System Message Bus fedora-readonly.service loaded active exited Configure read-only root support fedora-storage-init-late.service loaded active exited Initialize storage subsystems (RAID, LVM, etc.) fedora-storage-init.service loaded active exited Initialize storage subsystems (RAID, LVM, etc.) fedora-wait-storage.service loaded active exited Wait for storage scan getty@tty1.service loaded active running Getty on tty1 ip6tables.service loaded active exited IPv6 firewall with ip6tables iptables.service loaded active exited IPv4 firewall with iptables lvm2-monitor.service loaded active exited Monitoring of LVM2 mirrors, snapshots etc. using dmeventd or p NetworkManager.service loaded active running Network Manager rsyslog.service loaded active running System Logging Service sendmail.service loaded active running Sendmail Mail Transport Agent sm-client.service loaded active running Sendmail Mail Transport Client sshd.service loaded active running OpenSSH server daemon systemd-journald.service loaded active running Journal Service systemd-logind.service loaded active running Login Service systemd-remount-fs.service loaded active exited Remount Root and Kernel File Systems systemd-sysctl.service loaded active exited Apply Kernel Variables systemd-tmpfiles-setup.service loaded active exited Recreate Volatile Files and Directories systemd-user-sessions.service loaded active exited Permit User Sessions systemd-vconsole-setup.service loaded active exited Setup Virtual Console udev-settle.service loaded active exited udev Wait for Complete Device Initialization udev-trigger.service loaded active exited udev Coldplug all Devices udev.service loaded active running udev Kernel Device Manager
serviceの中には、デーモンプロセスとして起動したままになるものと、必要な処理を実行して終了するものがあります。「SUB」が「running」のものは、デーモンとして起動中のもので、「exited」は必要な処理が終了した状態のものです。その他のタイプも同様に確認できます。mountタイプはファイルシステムをマウントすることが仕事でしたが、次のように有効化されてマウント処理が終わっていることがわかります。
# systemctl list-units --type=mount UNIT LOAD ACTIVE SUB JOB DESCRIPTION -.mount loaded active mounted / boot.mount loaded active mounted /boot dev-hugepages.mount loaded active mounted Huge Pages File System dev-mqueue.mount loaded active mounted POSIX Message Queue File System media.mount loaded active mounted Media Directory sys-kernel-config.mount loaded active mounted Configuration File System sys-kernel-debug.mount loaded active mounted Debug File System
一方、現在の状況とは関係なく、すべてのUnitを表示するには次のようにします。次はserviceタイプの例です。
# systemctl list-unit-files --type=service UNIT FILE STATE arp-ethers.service enabled auditd.service enabled autovt@.service disabled console-getty.service disabled console-shell.service disabled crond.service enabled dbus-org.freedesktop.hostname1.service static dbus-org.freedesktop.locale1.service static dbus-org.freedesktop.login1.service static dbus-org.freedesktop.NetworkManager.service enabled dbus-org.freedesktop.timedate1.service static dbus.service static debug-shell.service disabled display-manager.service static dm-event.service disabled dnsmasq.service disabled dracut-shutdown.service static emergency.service static fedora-autorelabel-mark.service static fedora-autorelabel.service static fedora-configure.service static fedora-import-state.service static fedora-loadmodules.service static fedora-readonly.service static fedora-storage-init-late.service static fedora-storage-init.service static fedora-wait-storage.service static fsck-root.service static fsck@.service static getty@.service enabled halt-local.service static halt.service static hibernate.service static ip6tables.service enabled iptables.service enabled kexec.service static lvm2-monitor.service enabled messagebus.service static NetworkManager-wait-online.service disabled NetworkManager.service enabled plymouth-halt.service static plymouth-kexec.service static plymouth-poweroff.service static plymouth-quit-wait.service static plymouth-quit.service static plymouth-read-write.service static plymouth-reboot.service static plymouth-start.service static poweroff.service static prefdm.service static quotacheck.service static quotaon.service static rc-local.service static rdisc.service disabled reboot.service static rescue.service static rsyslog.service enabled saslauthd.service disabled sendmail.service enabled serial-getty@.service static single.service static sm-client.service enabled sshd.service enabled suspend.service static syslog.service enabled ...(以下省略)...
ここで、「enabled」「disabled」「static」という表記が出てきました。これは、以前のchkconfigによるon/offに対応するものです。まず、「enabled/disabled」が表示されているものは、次のようにシステム起動時の自動起動(default.targetの前提とするかどうか)をon/offすることができます。
# systemctl disable sshd.service rm '/etc/systemd/system/multi-user.target.wants/sshd.service' # systemctl enable sshd.service ln -s '/usr/lib/systemd/system/sshd.service' '/etc/systemd/system/multi-user.target.wants/sshd.service'
これは、sshd.serviceの自動起動を無効化/有効化している例ですが、systemctlコマンドの実行直後に「rm/ln」コマンドが表示されています。有効化した場合は、multi-user.target.wantsディレクトリにシンボリックリンクをおいて、multi-user.targetの前提としています。無効化した場合は、シンボリックリンクを削除して依存関係をなくしています。先に説明した仕組みと動きが合致していますね。
一方、[Install]セクションを持たないserviceは、有効化/無効化の対象にはなりません。先の出力で「static」と表示されたものがこれにあたります。一般には、他のUnitからの固定的な依存関係で起動することになります。たとえば、plymouth-poweroff.serviceは「static」になっていますが、これは、システムをシャットダウンする際に起動するpoweroff.targetの前提として設定されています。
なお、serviceを有効化した際に、どのtargetの前提として設定するかは、そのserviceの設定ファイルの[Install]セクションにおいて、「WantedBy=」オプションで指定されています。
# cat /usr/lib/systemd/system/sshd.service ...(中略)... [Install] WantedBy=multi-user.target
WantedByには、次にように、複数のtargetを指定することも可能です。
[Install] WantedBy=multi-user.target rescue.target
ただし、runlevel 5に相当する「graphical.target」に対しては、「multi-user.target」が前提として定義されていますので、multi-user.targetで有効化されたserviceは、自動的にgraphical.targetでも有効化されます。multi-user.targetとgraphical.targetの両方を指定する必要はありません。
# cat /usr/lib/systemd/system/graphical.target | grep -E '(Wanted|Requires)' Requires=multi-user.target
まとめると、
・runlevel 3と5で有効化したい → WantedByにmulti-user.targetを指定
・runlevel 5だけで有効化したい → WantedByにgraphical.targetを指定
ということになります。
最後に、systemdが特定のUnitに対して認識している設定の詳細は、次のコマンドで表示します。該当Unitに対して、デフォルト値として自動設定されているオプションを確認することもできます。
# systemctl show sshd.service Id=sshd.service Names=sshd.service Requires=systemd-journald.socket basic.target WantedBy=multi-user.target Conflicts=shutdown.target Before=shutdown.target multi-user.target After=syslog.target network.target auditd.service systemd-journald.socket basic.target Description=OpenSSH server daemon ...(以下省略)...