2014年6月10日、とうとうRHEL7が正式リリースを迎えた。RHEL7での変更点については、このスライドに詳しく記載されているが、今回の記事では特に大きな変更点である「Systemd」について解説していきたいと思う。
Systemdは従来のinit/Upstartの代替であり、非常に高速なシステム起動・終了や、様々なシステム管理機能を提供する。ただし、その仕組みや管理方法は従来のinit/Upstartとは大きく異なり、init/Upstartに慣れ親しんだ方は習熟に時間を要するだろう。
Systemdは様々な機能と役割を持っているが、今回の記事ではSystemdによるシステム起動に焦点を当てた内容とさせていただく。Systemdを利用したシステム管理については、後日別の記事を投稿する予定だ。なお、当記事はRHEL7のベースとなっているFedora19を元に動作確認させていただいた。
それでは、Systemdの世界に飛びだそう!
Systemdとは
Systemdとは従来のSysVinitやUpstartの代替となるデーモンだ。ただし、Systemdはプロセスの起動だけではなく、プロセスの管理や様々なトリガーによるプロセス起動など、その適用範囲は広範だ。Systemdは以下の特徴を持つ。
- 高速なシステム起動と終了
(Upstartよりも)高い並列度でプロセスを扱うため素早い起動・終了ができる - 設定ファイルによるシステム管理の共通化
シェルスクリプトは利用しないため、一度使い方を覚えれば様々な場面に対応できる - 柔軟なプロセス起動
様々な方法でプロセス起動できる
・タイマーによる起動(cron代替)
・socketへの通信検出によるプロセス起動(xinetd代替)
・所定のパスへのファイル作成をトリガーとしたプロセス起動 etc… - cgroupsによるプロセス管理
cgroupsによるリソースの利用制限や、優先度設定によるプロセス管理を実現できる
それではSystemdによるシステム起動プロセスを紐解いていこう。
ブートプロセスにおけるSystemdの影響範囲
先ずはSystemdそのものが起動されるタイミングについて記載しておく。Systemdはinit/Upstartの代替だから、BIOS/UEFIから始まるブートプロセスでの立ち位置は旧来のinit/Upstartと同様である。すごく大雑把に書くと以下のようなイメージである。
Systemdはinitの代替であるから、init以前の起動プロセスに影響しない。 ちなみに、Fedora19上にも/sbin/init
は存在するが、これは従来のinitではなく、systemdへのシンボリックリンクとなっている。
$ cat /etc/redhat-release Fedora release 19 (Schrödinger’s Cat) $ #/sbin/initは存在するがSystemdへのシンボリックリンクとなっている $ ls -l /sbin/init lrwxrwxrwx. 1 root root 22 6月 16 22:40 /sbin/init -> ../lib/systemd/systemd
Unitを理解する
Systemdによる具体的な起動プロセスを見る前に、Systemdで重要な概念となるUnitについて解説する。
Systemdにおける処理の単位 – Unitとは?
システム起動がいかなる仕組みによって実現されようが、”システムを起動するためにやらなければいけないこと”(ネットワークの初期化や必要に応じたプロセス起動など)は変わらない。システム起動を実現する仕組みの違いは、これらの処理を”どうやって管理し、どのように実行するか”という点にある。
init/Upstartでは、これらの処理をrc.sysinit
や、/etc/rc.d
のスクリプトで定義していたのに対し、Systemdでは”Unit”という単位で管理する。Unitは以下のような特徴を持っている。
- スクリプトではなく設定ファイルである
Unitの設定を元にSystemd自身が処理を実行する - Unit同士の関係を定義できる
Aプロセスを起動するためにはBプロセスが必要、というような関係を定義できる
SystemdはUnit間の関係を読み取り、最適な起動順を選択する(詳細は後述) - 用途別に様々な種類が存在する
”プロセスに関する設定”、”マウントに関する設定”等、様々な種類がある
Unitの種類はUnitファイルの拡張子で判断する
主なUnitの種類は以下の通りだ。個々の種類は拡張子で見分けることができ、用途に応じた様々な設定ができるようになっている。
Unitの種類 (拡張子) | 設定内容 |
---|---|
.service | プロセスの起動/停止に関する設定 ファイル名は「プロセス名.service」となる (httpd.service、sshd.serviceなど) |
.mount | ファイルシステムのマウント/アンマウントに関する設定 ファイル名は「マウントポイント.mount」となる /etc/fstabの内容を元にSystemdが自動作成する |
.socket | ソケットの監視設定 ”ソケットへの接続を検出すると特定のプロセスを起動”といった動作を実現可 (xinet.dの代替にできる) |
.device | システムが認識しているデバイス情報を保持する udevデーモンによって自動作成される |
.path | パスの監視設定 ”監視ディレクトリにファイルが置かれたらサービス起動”といった動作を実現可 |
.target | 複数のUnitをとりまとめるUnit |
例えば、apacheと対応するUnitであるhttpd.service
は以下のように定義される。従来のinit/Upstartでは、このようなhttpdに関する起動設定はスクリプト(/etc/rc.d/init.d/httpd
)として定義されていただろう。httpd.service
では単なる設定ファイルとなっていることが分かる。
$ cat /usr/lib/systemd/system/httpd.service [Unit] Description=The Apache HTTP Server After=network.target remote-fs.target nss-lookup.target [Service] Type=notify EnvironmentFile=/etc/sysconfig/httpd ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND ExecReload=/usr/sbin/httpd $OPTIONS -k graceful ExecStop=/usr/sbin/httpd $OPTIONS -k graceful-stop KillSignal=SIGCONT PrivateTmp=true [Install] WantedBy=multi-user.target
httpd.service
はプロセスに関するUnitに分類されるため、プロセス関連の設定項目を保持するが、.mount
などのその他Unitは、個々の用途に応じた設定項目を保持している。
Unitファイルの格納先
Unitファイルの格納先は以下の2カ所である。
ディレクトリ | 設定内容 |
---|---|
/usr/lib/systemd/system | インストール時の初期設定 当ディレクトリ内のUnitは編集しない |
/etc/systemd/system | ユーザによる個別設定 デフォルトの設定を変更する場合は、当ディレクトリにUnitファイルをコピーして編集する (Systemdは当ディレクトリのUnitを優先する) |
インストール直後の設定と、個別の設定を格納するディレクトリが分かれている。個別の設定を行う場合は/etc/systemd/system
を利用しよう。
Unit間の関係定義
Systemdでは、Unit間の関係を定義できる。関係には”依存”と”起動順”の2種類がある。
- 依存
プロセス同士が”同時に起動するべきか”を定義する。
例えばプロセスAがBに依存(プロセスAは、プロセスBと同時に起動されるべき)と定義した場合、プロセスAが起動対象となった場合にプロセスBも同時に起動される。
依存はあくまでも”同時起動されるべきか”を定義するものであり、プロセスの起動順(プロセスA、Bのどちらを先に起動するか)は表現しない。
依存には”Wants”(可能な限り同時起動)、”Requires”(必ず同時起動)、”Comflict”(同時起動しない)などがある。 - 起動順
プロセス起動の前後関係を定義する。
例えば”プロセスAはプロセスBよりも先に起動するべき”といった関係を定義できる。
順序関係は”After”、”Before”などがある。
Systemdでは、依存と起動順を別々に定義することによって、Upstartよりもプロセス起動の並列度を上げている。例えば、依存関係があるものの起動順の指定が無いプロセスA、Bが存在したとする。この場合、Systemdは”システム起動完了時点でプロセスA、Bが起動状態となっていれば良い”と判断できるため、プロセスA、Bを並行して起動するといった動作が実施可能となるのだ。
依存関係と順序関係の定義方法は以下の通りだ。
関係 | 分類 | 内容と設定方法 |
---|---|---|
Wants | 依存 | 強制力の無い依存関係。 Systemdはプロセスの同時起動を試みるが、依存先の起動に失敗した場合であっても、依存元の起動は引き続き実施する。 ■「 AはBに依存する」の定義方法 方法1.AのUnit定義ファイルに直接設定を記述 A.service ------------------------------------------- [Unit] Wants=B.service --------------------------------------------------- 方法2.AのwantsディレクトリにBのシンボリックリンクを作成 A.serviceファイルが存在するディレクトリ上に「A.service.wants」ディレクトリを作成し、このディレクトリ内にB.serviceへのシンボリックリンクを作成。 |
Requires | 依存 | 強制力の有る依存関係。 Systemdはプロセスの同時起動を試み、依存先の起動に失敗した場合、依存元は起動しない。 ■「AはBに依存する」の定義方法 Wantsと同様の定義方法。A.serviceファイル内に記載する場合の設定項目は「Requires=B.service」となる。また、シンボリックリンクを利用する場合、作成するディレクトリ名は「A.service.requires」となる。 |
Conflict | 依存 | 競合する関係。 Systemdはこの関係が定義されたプロセスは同時起動しない。 ■「AとBは競合する」の定義方法 A.service ------------------------------------------- [Unit] conflicts=A.service --------------------------------------------------- ※ B.serviceに「conflicts=A.service」と設定しても良い。 |
After | 起動順 | 自身よりも前に起動するプロセスを定義。 ■ 「Aよりも前にBを起動する」の定義方法 A.service ------------------------------------------- [Unit] After=B.service --------------------------------------------------- ※ ”B.serviceより後に自分を起動してね”という設定 |
Before | 起動順 | 自身よりも後に起動するプロセスを定義。 ■ 「Aの後にBを起動する」の定義方法 A.service ------------------------------------------- [Unit] Before=B.service --------------------------------------------------- ※ ”自分よりも後にBserviceを起動してね”という設定 |
複数のUnitをまとめる「target」Unit
targetと呼ばれる種類のUnitを利用することで複数のUnitをグルーピングすることができる。
targetは他Unitとの関係を保持し、複数のUnitをとりまとめる。例えばネットワーク関連のUnitをとりまとめたnetwork.target
や、従来のrc.sysinit
に相当する処理をとりまとめたsysinit.target
等が存在する。targetを利用することで、Unit同士の依存や起動順定義が楽に行うことができる。
例えばsysinit.target
の場合、targetで取りまとめられているUnitはsysinit.target
自身やsysinit.target.wants
ディレクトリに保存されている。
$ pwd /usr/lib/systemd/system $ 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 $ ls -l sysinit.target.wants/ 合計 0 lrwxrwxrwx. 1 root root 20 6月 16 22:40 cryptsetup.target -> ../cryptsetup.target lrwxrwxrwx. 1 root root 22 6月 16 22:40 dev-hugepages.mount -> ../dev-hugepages.mount lrwxrwxrwx. 1 root root 19 6月 16 22:40 dev-mqueue.mount -> ../dev-mqueue.mount lrwxrwxrwx. 1 root root 30 6月 16 22:42 plymouth-read-write.service -> ../plymouth-read-write.service lrwxrwxrwx. 1 root root 25 6月 16 22:42 plymouth-start.service -> ../plymouth-start.service lrwxrwxrwx. 1 root root 36 6月 16 22:40 proc-sys-fs-binfmt_misc.automount -> ../proc-sys-fs-binfmt_misc.automount lrwxrwxrwx. 1 root root 32 6月 16 22:40 sys-fs-fuse-connections.mount -> ../sys-fs-fuse-connections.mount lrwxrwxrwx. 1 root root 26 6月 16 22:40 sys-kernel-config.mount -> ../sys-kernel-config.mount lrwxrwxrwx. 1 root root 25 6月 16 22:40 sys-kernel-debug.mount -> ../sys-kernel-debug.mount lrwxrwxrwx. 1 root root 36 6月 16 22:40 systemd-ask-password-console.path -> ../systemd-ask-password-console.path lrwxrwxrwx. 1 root root 25 6月 16 22:40 systemd-binfmt.service -> ../systemd-binfmt.service lrwxrwxrwx. 1 root root 32 6月 16 22:40 systemd-journal-flush.service -> ../systemd-journal-flush.service lrwxrwxrwx. 1 root root 27 6月 16 22:40 systemd-journald.service -> ../systemd-journald.service lrwxrwxrwx. 1 root root 31 6月 16 22:40 systemd-modules-load.service -> ../systemd-modules-load.service lrwxrwxrwx. 1 root root 35 6月 16 22:40 systemd-random-seed-load.service -> ../systemd-random-seed-load.service lrwxrwxrwx. 1 root root 25 6月 16 22:40 systemd-sysctl.service -> ../systemd-sysctl.service lrwxrwxrwx. 1 root root 37 6月 16 22:40 systemd-tmpfiles-setup-dev.service -> ../systemd-tmpfiles-setup-dev.service lrwxrwxrwx. 1 root root 33 6月 16 22:40 systemd-tmpfiles-setup.service -> ../systemd-tmpfiles-setup.service lrwxrwxrwx. 1 root root 31 6月 16 22:40 systemd-udev-trigger.service -> ../systemd-udev-trigger.service lrwxrwxrwx. 1 root root 24 6月 16 22:40 systemd-udevd.service -> ../systemd-udevd.service lrwxrwxrwx. 1 root root 33 6月 16 22:40 systemd-vconsole-setup.service -> ../systemd-vconsole-setup.service
例えばsysinit.target
ファイルはAfter=local-fs.target swap.target emergency.service emergency.target
といった設定になっている。local-fs.target
とswap.target
はwants指定しているため、”同時起動”対象としてSystemdに認識されるが、emergency.service
とemergency.target
は起動順の設定(After)のみとなっており、依存(Wants、Requires)の設定は行われていない。
これは、sysinit.target
の起動に伴ってemergency.service
やemergency.target
の起動は行わない事を指しているが、emergency.service
、emergency.target
が起動対象となった場合、これらのtarget/serviceはsysinit.target
よりも前に起動されるべきという関係を定義している。
Systemdにおけるランレベルの扱いと「default.target」
Unitの概念を理解した所で、Systemdにおけるランレベルの扱いを説明する。
ランレベルをUnitとして表現する
Systemdでは全てがUnit上で表現されるため、従来のランレベルというプロセスの管理体系は存在しない。Systemdではランレベルに相当する機能を先述のtarget Unitを利用して表現している。
ランレベルの目的は、ランレベル別に定義された状態でシステムを起動するために必要となるプロセスを管理することにあり、init/Upstartでは、/etc/rc.d/rc[runlevel].d
にランレベルに応じたプロセス群を定義することでこれを実現していた。
Systemdでは、これらのプロセスをtargetとしてグルーピングすることでランレベルと同様のプロセス管理を実現している(※)。例えば、従来のランレベル3(マルチユーザ・コンソールログインのみ)に必要となるプロセス群はmulti-user.target
として定義されている。
※ 従来のランレベルは0 – 6の7種類のみ定義されていたのに対し、targetは好きなだけ定義できるため、より柔軟になったと言える。
システムの起動後状態を表す定義済targetは以下の通りである。
target名 | 内容 | 従来のランレベル (RedHat) |
---|---|---|
poweroff.target | システム停止 | 0 |
rescue.target | シングルユーザモード | 1 |
multi-user.target | マルチユーザモード (コンソールログイン) | 3 |
graphical.target | マルチユーザ+GUI | 5 |
reboot.target | 再起動 | 6 |
emergency.target | 緊急シェル rescue.targetよりも起動対象が少ない ルートファイルシステムですら マウントできない場合などに利用 | 無し |
また、互換性?のために従来のランレベルを意図した名称のtargetが定義されている。ただし、これらのtargetは上表のtargetへのシンボリックリンクとなっている。
$ pwd /usr/lib/systemd/system $ ll runlevel*.target lrwxrwxrwx. 1 root root 15 6月 16 22:40 runlevel0.target -> poweroff.target lrwxrwxrwx. 1 root root 13 6月 16 22:40 runlevel1.target -> rescue.target lrwxrwxrwx. 1 root root 17 6月 16 22:40 runlevel2.target -> multi-user.target lrwxrwxrwx. 1 root root 17 6月 16 22:40 runlevel3.target -> multi-user.target lrwxrwxrwx. 1 root root 17 6月 16 22:40 runlevel4.target -> multi-user.target lrwxrwxrwx. 1 root root 16 6月 16 22:40 runlevel5.target -> graphical.target lrwxrwxrwx. 1 root root 13 6月 16 22:40 runlevel6.target -> reboot.target
default.targetを切り替えてランレベルを制御する
詳細は後述するが、Systemdにおける起動の基点となるUnitはdefault.target
である。Systemdは起動の後、このdefault.target
を探し、default.target
に記載されたUnit間の関係を元に最終的に起動するプロセス群を把握する。
default.target
自身は特に処理を持たず、任意のUnitへのシンボリックリンクとして利用するのが一般的だ。このシンボリックのリンク先を上述の起動後状態を表す.targetとすることで従来のランレベル変更に相当する操作を行う事ができる。
$ # multi-userモード(従来のRunlevel3)で起動する場合の設定 $ # default.targetのリンク先をmulti-user.targetとする $ ll /etc/systemd/system/default.target lrwxrwxrwx. 1 root root 37 6月 21 17:09 /etc/systemd/system/default.target -> /lib/systemd/system/multi-user.target
init、Systemdによるシステム起動の比較
次に従来のinitによるシステム起動とSystemdの起動を比較してみよう。
温故知新ということで、従来のinit起動後の動作について解説しておく。従来のinitの動作がSystemdではどのような振る舞いに置き換わるのか確認してみよう。
init/Upstartの起動プロセス
initは「1.init起動」した後、「2./etc/inittabを読み込み」、「3.rc.sysinitを実行」する。次に「4.inittabに記載されたランレベルに応じて、/etc/rc.d/rc[Runlevel].d内のスクリプトを実行」していく。各プロセスの起動が終了した後、ターミナルからの接続プロセスである「5.ログイン受付プロセスを起動」(mingettyなど)し、起動プロセスを完了する。まとめると以下のようになる。
この起動プロセスはUpstartの場合であってもさほど変わりは無い。
initによる起動では、プロセスの起動順序を/etc/rc.d/rc[runlevel].d
内のシンボリックリンク名によって制御していた。例えば/etc/rc.d/rc3.d
は以下のようになっている。
$ pwd /etc/rc.d/rc3.d $ ls -l lrwxrwxrwx. 1 root root 16 4月 8 20:02 2014 K01smartd -> ../init.d/smartd lrwxrwxrwx. 1 root root 17 4月 8 20:00 2014 K02oddjobd -> ../init.d/oddjobd lrwxrwxrwx. 1 root root 17 4月 8 20:03 2014 K05wdaemon -> ../init.d/wdaemon lrwxrwxrwx. 1 root root 16 4月 8 20:02 2014 K10psacct -> ../init.d/psacct lrwxrwxrwx. 1 root root 19 4月 8 20:00 2014 K10saslauthd -> ../init.d/saslauthd lrwxrwxrwx. 1 root root 22 4月 8 20:00 2014 K15htcacheclean -> ../init.d/htcacheclean # ============================= 一部抜粋 ============================= lrwxrwxrwx. 1 root root 17 4月 8 20:01 2014 S01sysstat -> ../init.d/sysstat lrwxrwxrwx. 1 root root 22 4月 8 20:01 2014 S02lvm2-monitor -> ../init.d/lvm2-monitor lrwxrwxrwx. 1 root root 19 4月 8 20:00 2014 S08ip6tables -> ../init.d/ip6tables lrwxrwxrwx. 1 root root 18 4月 8 19:58 2014 S08iptables -> ../init.d/iptables lrwxrwxrwx. 1 root root 17 4月 8 19:58 2014 S10network -> ../init.d/network lrwxrwxrwx. 1 root root 16 4月 8 20:02 2014 S11auditd -> ../init.d/auditd lrwxrwxrwx. 1 root root 21 4月 8 19:55 2014 S11portreserve -> ../init.d/portreserve # ============================= 一部抜粋 =============================
ディレクトリ内の個々のファイルは、/etc/rc.d/init.d
内にある起動スクリプトへのシンボリックリンクとなっている。また、シンボリックリンクの名称が「Kxxプロセス名」「Sxxプロセス名」となっており、この「xx」の部分で起動・停止順を定義している。(Kxxは該当ランレベルで停止するプロセス、Sxxは起動するプロセス)
先述したSystemdにおける、依存といった区分けは無く、単純に起動順のみが定義された状態である。
Systemdの起動プロセス
Systemdの起動プロセスは以下の通りである。
initと比較するとプロセスの起動順をSystemd自体が決定していることが分かる。initの場合は/etc/rc.d/rc[runlevel].d
ディレクトリのシンボリックリンクの命名規則によって起動順を指定していたが、Systemdでは、Unitに定義されたUnit間の関係を元にSystemd側で起動順を決定する。
また、全てがUnitによって定義されているため、initのようなrc.sysinit
やランレベルといった区分けは無く、システム起動に必要な処理を分け隔て無く管理できていることが分かる。
起動プロセスのゴール地点(default.target)からUnit間の関係を遡ることによって、システム起動に必要となる全てのUnitをSystemd側が把握することで、最適なシステム起動順によるシステム起動を実現している事が分かる。
まとめ
ご覧いただいたように、Systemdによるシステム起動のプロセスは従来のinit/Upstartとは大きく異なる。inittabやランレベルという考え方が無くなった事など、Systemdに初めて触れる方は戸惑ってしまう場面も多いかもしれない。
しかしSystemdは設定ファイルベースで様々な管理が行えるため、使い方を一度覚えてしまえば、様々な場面に対応することができるだろう。また、一度触れてみれば分かるが、Systemdを利用したシステム起動は非常に速い。RHEL7で最初に感動するポイントは起動時間かもしれない。
SystemdがRHELに正式採用されたことで、Systemdは更に重要な位置づけとなっていくだろう。この機会にSystemdに触れてみるのはいかがだろうか。
参考資料
Systemdに関する調査に、以下サイトの情報を参考とさせていただいた。どのサイトも非常に詳しい情報が記載されている。是非参考としていただければと思う。(皆様に感謝!)
Linux女子部 systemd徹底入門
http://www.slideshare.net/enakai/linux-27872553
systemd
http://www.slideshare.net/moriwaka/systemd
Systemd入門(1) – Unitの概念を理解する
http://d.hatena.ne.jp/enakai00/20130914/1379146157
ArchLinux – systemd(日本語)
https://wiki.archlinux.org/index.php/Systemd_(%E6%97%A5%E6%9C%AC%E8%AA%9E)
Systemd公式(英語)
http://www.freedesktop.org/wiki/Software/systemd/