#きょうのsystemd : Ubuntuで新しいめのsystemdを使おうとしてコケた話
はじめに
きょうは何だか翻訳の気分ではないし、自分の文章を書きたいので、寄り道をする。
翻訳が増えることを期待してくれた方には申し訳ない。
新しいめのsystemdを試したい
systemd 235では、以前に紹介したような機能が搭載された。
例えば、「Ubuntuでちょっとこれを使ってみたいな」と思ったとしよう、というのが本稿のスタート地点である。
ところで、先月リリースされたばかりのUbuntu 17.10のsystemdのバージョンは234である。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 17.10 Release: 17.10 Codename: artful $ systemctl --version systemd 234 +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN default-hierarchy=hybrid
というわけで、systemd 235のパッケージをビルドして、インストールしようというわけだ。
比較的正しい手順
@MurabitoL さんが「比較的正しい手順」を教えてくれた。
簡単に言うと、dpkg-buildpackageがquiltでパッチ適用した際にワーキングディレクトリを編集するので、そのままビルドしようとするとmesonに怒られます。というわけで、比較的正しい手順はこちら。https://t.co/jqLrcYjQTv
— 村人 (@MurabitoL) 2017年11月8日
こちらに従うのが良いだろう。
おことわり
本稿の作業を最後までおこなうと、ディストリビューション標準のパッケージ、しかも、システムの根幹に関わるsystemdのパッケージを上書きインストールすることになる。
壊れてもいい環境か、本番環境を壊してもいいという覚悟かのいずれかをもって臨んでほしい。
パッケージビルド
流れは以下の記事の「すでにソースパッケージがある場合」に沿っている。
Ubuntuの条件
systemd 235のビルドではmesonというビルドシステムが使われるようなのだが、Ubuntu 17.04でインストールできるmesonはsystemd 235で指定されているバージョンに届かないようだ。 Ubuntu 16.04でインストールできるmesonもバージョンが足りないことは想像に難くない*1。
一方、Ubuntu 17.10でインストールできるmesonはバージョンの要件を満たしているようである。
したがって、要件を満たすmesonを自前でビルド・インストールするか、Ubuntu 17.10を使うかのどちらかの選択となる。
おそらく、仮想マシンなり、任意のシステムコンテナなりで作業しているであろうから、素直に後者ということで話を進める。
ビルドに必要なパッケージをインストールする
さて、systemdのビルドに必要なパッケージをインストールする。
デフォルトではソースパッケージをレポジトリから取ってくるようになっていないはずなので、これを有効にする。
$ sudo sed -i /etc/apt/sources.list -e 's/# deb-src/deb-src/'
そして、パッケージリストを更新する。ついでに、アップグレードもしておこう。
$ sudo apt update $ sudo apt upgrade
systemdのパッケージビルドに必要なパッケージをインストールする
$ sudo apt -y install fakeroot $ sudo apt -y build-dep systemd
どこからソースコードを取ってくるのが良いか?
Ubuntu 17.10で提供されているsystemd 234のパッケージ・ビルドに必要なものが整ったところで、ディストロ提供のsystemdのソースをダウンロードしてみるフリをする。
$ apt source --dry-run systemd パッケージリストを読み込んでいます... 完了 注意: 'systemd' パッケージは以下の場所の 'Git' バージョン制御システムで保守されています: https://anonscm.debian.org/git/pkg-systemd/systemd.git パッケージの最新の (まだリリースされていないかもしれない) 更新を取得するには、 git clone https://anonscm.debian.org/git/pkg-systemd/systemd.git を使用してください。 4,948 kB のソースアーカイブを取得する必要があります。 ソース systemd を取得
我々がビルドしたいのは234ではなく235なので、注意に従うことにする*2。
ソースコードを取得する
インストールされていなければ、gitをインストールする
$ sudo apt install git
任意の場所で指示通り、git cloneする
$ git clone https://anonscm.debian.org/git/pkg-systemd/systemd.git
一旦、クローンしたURLをブラウザで開いてみる。
pkg-systemd/systemd - systemd packaging
'Tag'の欄に'debian/235-2'というのが見える。今回はこれをビルド・インストールしてみる。
ここで、クローンしたgitのローカルレポジトリに移動する。
$ cd systemd
さっき確認したタグでチェックアウトする。メッセージが表示されるが、ビルドだけなら特に気にしなくて良い。
$ git checkout refs/tags/debian/235-2
次のコマンドでdebian/235-2
と見えていればOKだ。
$ git branch
ビルドする
ここからは簡単なはずであった。先に挙げた記事にあるとおり、ビルドのコマンドを叩く。
$ dpkg-buildpackage -r -uc -b
うーん、エラーでコケる。
$ ./configure [...] Meson encountered an error in file meson.build, line 2387, column 8: File src/test/test-helper.c does not exist.
これがヒントになる。gitで調べると
$ git status src/test/test-helper.c On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) deleted: src/test/test-helper.c no changes added to commit (use "git add" and/or "git commit -a")
なぜか消えている。復活させる*3。
$ git checkout src/test/test-helper.c
再度、ビルドを走らせる。
$ dpkg-buildpackage -r -uc -b
今度はビルドが走る。お茶を飲みながら待つ。
インストールする
ビルドが終わると、systemdディレクトリの1つ上の階層に*.debができているはずである。
$ cd .. $ *.deb -rw-r--r-- 1 popo popo 105316 11月 8 21:59 libnss-myhostname_235-2_amd64.deb -rw-r--r-- 1 popo popo 174338 11月 8 21:59 libnss-mymachines_235-2_amd64.deb -rw-r--r-- 1 popo popo 173768 11月 8 21:58 libnss-resolve_235-2_amd64.deb -rw-r--r-- 1 popo popo 171718 11月 8 21:58 libnss-systemd_235-2_amd64.deb -rw-r--r-- 1 popo popo 174736 11月 8 21:59 libpam-systemd_235-2_amd64.deb -rw-r--r-- 1 popo popo 249148 11月 8 21:58 libsystemd-dev_235-2_amd64.deb -rw-r--r-- 1 popo popo 267560 11月 8 21:58 libsystemd0_235-2_amd64.deb -rw-r--r-- 1 popo popo 93654 11月 8 21:58 libudev-dev_235-2_amd64.deb -rw-r--r-- 1 popo popo 125620 11月 8 21:58 libudev1_235-2_amd64.deb -rw-r--r-- 1 popo popo 297980 11月 8 21:58 systemd-container_235-2_amd64.deb -rw-r--r-- 1 popo popo 116260 11月 8 21:58 systemd-coredump_235-2_amd64.deb -rw-r--r-- 1 popo popo 129242 11月 8 21:58 systemd-journal-remote_235-2_amd64.deb -rw-r--r-- 1 popo popo 83696 11月 8 21:58 systemd-sysv_235-2_amd64.deb -rw-r--r-- 1 popo popo 3111888 11月 8 21:58 systemd-tests_235-2_amd64.deb -rw-r--r-- 1 popo popo 2933620 11月 8 21:58 systemd_235-2_amd64.deb -rw-r--r-- 1 popo popo 1156966 11月 8 21:58 udev_235-2_amd64.deb
これらを全部インストールしようとする。
$ sudo dpkg -i *.deb
処理中にエラーが発生しました: systemd-coredump_235-2_amd64.deb
と出るが、普通*4に使う分には特に問題がないことは確認しているので、気にしない。
再起動する*5。
$ sudo reboot
バージョンを確認する。
$ systemctl --version systemd 235 +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN default-hierarchy=hybrid
IPアカウンティングで遊んでみる。
$ unset LANG $ sudo systemd-run -p IPAccounting=yes --wait ping -c 5 localhost Running as unit: run-u14.service Finished with result: success Main processes terminated with: code=exited/status=0 Service runtime: 4.096s IP traffic received: 520B IP traffic sent: 520B $ journalctl -u run-u14.service -- Logs begin at Wed 2017-11-08 22:06:05 JST, end at Wed 2017-11-08 22:10:14 JST. -- Nov 08 22:10:10 systemd systemd[1]: Started /bin/ping -c 5 localhost. Nov 08 22:10:10 systemd ping[931]: PING localhost(localhost (::1)) 56 data bytes Nov 08 22:10:10 systemd ping[931]: 64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.035 ms Nov 08 22:10:11 systemd ping[931]: 64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.055 ms Nov 08 22:10:12 systemd ping[931]: 64 bytes from localhost (::1): icmp_seq=3 ttl=64 time=0.068 ms Nov 08 22:10:13 systemd ping[931]: 64 bytes from localhost (::1): icmp_seq=4 ttl=64 time=0.056 ms Nov 08 22:10:14 systemd ping[931]: 64 bytes from localhost (::1): icmp_seq=5 ttl=64 time=0.057 ms Nov 08 22:10:14 systemd ping[931]: --- localhost ping statistics --- Nov 08 22:10:14 systemd ping[931]: 5 packets transmitted, 5 received, 0% packet loss, time 4092ms Nov 08 22:10:14 systemd ping[931]: rtt min/avg/max/mdev = 0.035/0.054/0.068/0.011 ms Nov 08 22:10:14 systemd systemd[1]: run-u14.service: Received 520B IP traffic, sent 520B IP traffic
ヒャッハー
追記(コケる件について)
エラーについて報告する必要があるかもしれないので、メモ。
1回目のdpkg-buildpackage -r -uc -b
は最後に以下のようなダイイングメッセージを残して死ぬ。
Running command: /usr/bin/git --git-dir=/home/popo/systemd/.git ls-files :/*.[ch] Died at /usr/share/perl5/Debian/Debhelper/Buildsystem/meson.pm line 61. debian/rules:153: recipe for target 'override_dh_auto_configure' failed make[1]: *** [override_dh_auto_configure] Error 2 make[1]: Leaving directory '/home/popo/systemd' debian/rules:293: recipe for target 'binary' failed make: *** [binary] Error 2 dpkg-buildpackage: error: fakeroot debian/rules binary gave error exit status 2
その時のgit status
は次の通り。
On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: man/journald.conf.xml modified: man/rules/meson.build modified: man/systemd-system.conf.xml modified: meson.build modified: po/POTFILES.in modified: rules/50-udev-default.rules.in modified: src/basic/cgroup-util.c modified: src/basic/time-util.c modified: src/core/cgroup.c modified: src/core/dynamic-user.c modified: src/core/dynamic-user.h modified: src/core/execute.c modified: src/core/locale-setup.c modified: src/core/main.c modified: src/core/mount-setup.c modified: src/core/namespace.c modified: src/core/system.conf modified: src/core/unit.c modified: src/fstab-generator/fstab-generator.c modified: src/hostname/org.freedesktop.hostname1.service modified: src/journal/journald-audit.c modified: src/journal/journald-server.c modified: src/journal/journald.conf modified: src/libsystemd/sd-id128/sd-id128.c modified: src/libsystemd/sd-login/sd-login.c modified: src/locale/keymap-util.c modified: src/locale/org.freedesktop.locale1.service modified: src/login/logind-core.c modified: src/login/org.freedesktop.login1.service modified: src/login/pam_systemd.c modified: src/shared/sleep-config.c modified: src/systemctl/systemctl.c modified: src/test/meson.build modified: src/test/test-bpf.c modified: src/test/test-cgroup-mask.c modified: src/test/test-engine.c modified: src/test/test-execute.c modified: src/test/test-fs-util.c deleted: src/test/test-helper.c modified: src/test/test-helper.h modified: src/test/test-path.c modified: src/test/test-sched-prio.c modified: src/test/test-unit-file.c modified: src/test/test-unit-name.c modified: src/timedate/org.freedesktop.timedate1.service modified: src/timedate/timedated.c modified: src/timesync/meson.build modified: src/udev/udev-event.c modified: src/udev/udev.conf modified: sysctl.d/50-coredump.conf.in modified: tmpfiles.d/legacy.conf modified: tmpfiles.d/tmp.conf modified: units/meson.build modified: units/systemd-fsck-root.service.in modified: units/systemd-fsck@.service.in modified: units/systemd-logind.service.in modified: units/user/graphical-session-pre.target Untracked files: (use "git add <file>..." to include in what will be committed) .pc/ man/systemd-fsckd.service.xml src/fsckd/ units/systemd-fsckd.service.in units/systemd-fsckd.socket no changes added to commit (use "git add" and/or "git commit -a")
modified
なのもよくわからないが、とりあえず、切り分けたところ、原因はdeleted
になっているsrc/test/test-helper.c
である。
本文の再掲となるが
$ ./configure [...] Meson encountered an error in file meson.build, line 2387, column 8: File src/test/test-helper.c does not exist.
がヒントだった。
git checkout src/test/test-helper.c
で復活させて、2回めのdpkg-buildpackage -r -uc -b
後のgit status
は以下の通り
On branch master Your branch is up-to-date with 'origin/master'. Untracked files: (use "git add <file>..." to include in what will be committed) debian/.debhelper/ debian/debhelper-build-stamp debian/files debian/install/ debian/libnss-myhostname.substvars debian/libnss-myhostname/ debian/libnss-mymachines.substvars debian/libnss-mymachines/ debian/libnss-resolve.substvars debian/libnss-resolve/ debian/libnss-systemd.substvars debian/libnss-systemd/ debian/libpam-systemd.substvars debian/libpam-systemd/ debian/libsystemd-dev.substvars debian/libsystemd-dev/ debian/libsystemd0.substvars debian/libsystemd0/ debian/libudev-dev.postinst.debhelper debian/libudev-dev.postrm.debhelper debian/libudev-dev.preinst.debhelper debian/libudev-dev.prerm.debhelper debian/libudev-dev.substvars debian/libudev-dev/ debian/libudev1-udeb.substvars debian/libudev1-udeb/ debian/libudev1.substvars debian/libudev1/ debian/shlibs.local debian/systemd-container.postinst.debhelper debian/systemd-container.postrm.debhelper debian/systemd-container.preinst.debhelper debian/systemd-container.prerm.debhelper debian/systemd-container.substvars debian/systemd-container/ debian/systemd-coredump.substvars debian/systemd-coredump/ debian/systemd-journal-remote.substvars debian/systemd-journal-remote/ debian/systemd-sysv.substvars debian/systemd-sysv/ debian/systemd-tests.substvars debian/systemd-tests/ debian/systemd.postinst.debhelper debian/systemd.postrm.debhelper debian/systemd.preinst.debhelper debian/systemd.prerm.debhelper debian/systemd.substvars debian/systemd/ debian/udev-udeb.substvars debian/udev-udeb/ debian/udev.postinst.debhelper debian/udev.postrm.debhelper debian/udev.preinst.debhelper debian/udev.prerm.debhelper debian/udev.substvars debian/udev/ nothing added to commit but untracked files present (use "git add" to track)
modified
はきれいに消えてる。というか、戻しているというのが正確か。
察しの良い方はお気づきだろうが、origin/master
でも再現する。
*1:確認してはいない
*2:当初はアップストリームのsystemdをgit cloneしてビルドするつもりだった。systemd-234のdebianディレクトリをこれに差し込んで、ちょっとファイルいじったりして、なんとかビルドできないか試した。しかし、うまくいかなかった。アップストリームのものではないという意味で「新しいめのsystemd」である。
*3:コケる原因はわかったし、ビルドもできるが、バリバリのワークアラウンドで納得はいかない。パッケージングのことはほとんどわからない。debian/controlあたりに問題があるんだろうか。そして、どこに報告すれば良いんだろう。というか、unstableなものを使ってる自信はあるので、報告すべきことなのだろうか。
*4:普通って何
*5:virt-managerとkvmとの組み合わせでVMを立ててやったら、落ちきらなかったので、強制的に再起動をかけた。
#きょうのsystemd : 一覧
きょうのsystemd
リンクなしは予定*1。
レナートのブログ記事翻訳
ちゃんと訳したやつ
一応、レナート本人の許可は取ってから翻訳している。( #きょうのsystemd : レナートにブログ翻訳の許可を取り付けた - ぽぽの備忘録 )
The systemd for Administratorsシリーズ
- 1: ブートアップを確かめる(Verifying Bootup)
- 2: Which Service Owns Which Processes?
- 3: How Do I Convert A SysV Init Script Into A systemd Service File?
- 4: Killing Services
- 5: The Three Levels of "Off"
- 6: Changing Roots
- 7: The Blame Game
- 8: The New Configuration Files
- 9: On /etc/sysconfig and /etc/default
- 10: Instantiated Services
- 11: Converting inetd Services
- 12: Securing Your Services
- 13: Log and Service Status
- 14: The Self-Explanatory Boot
- 15: Watchdogs
- 16: Gettys on Serial Consoles (and Elsewhere)
- 17: Using the Journal
- 18: Managing Resources
- 19: Detecting Virtualization
- 20: Socket Activated Internet Services and OS Containers
- 21: Container Integration
MLとかレナートのブログで集めたネタ
つまみぐいで訳してる感じのやつ
*1:予定は予定であって未定である
#きょうのsystemd: (翻訳)PID 1 を考え直す
レナートがsystemdの構想について語ったブログエントリーの翻訳。
原文はこちら:
投稿は2010年4月30日とかなり古いものではあるが、この時点で現在に至るまでのsystemdの設計思想がしっかり示されており、いまもなお色あせていないと思う。
レナートからブログエントリーの翻訳の許可を取り付けたので、せっかくだから一番最初に訳すのはこの記事にしたい。
- 一気に訳すだけの気力と時間はないので、何回かに分けて訳す。
- 記事を分割すると検索性が落ちるので、この記事に加筆修正していく。
- 最初からきちんと訳せるという気はしておらず、誤字・脱字・誤訳がたぶんいっぱいある。
- なので、見つけた方はご指摘ください。徐々にいいものにしていきたい。 *1
PID 1 を考え直す
十分な関わりがあったり、行間を読むのが得意であったりすれば、このブログ投稿が何に関することかはもう察しがついているであろう。 だが、そうであったとしても、このストーリーは面白いと思うかもしれない。 なので、1杯のコーヒーを手に取り、座って、何が来るのか読んでほしい。
このブログストーリーは長い。私としては長くてもこのストーリーを読むことしかおすすめできないのだが、1文でまとめるとこうである。 我々は新しいinitを試しており、それはとても楽しい。
プロセス識別子 1
すべてのUnixシステムで、特別なプロセス識別子である1を与えられる1つのプロセスが存在する。 これは他のすべてのプロセスよりも前にカーネルによって起動され、他に親となるプロセスを持たない他のプロセスすべての親となるものである。 そのため、このプロセスは他のプロセスにはできないようなたくさんのことをすることができる。 そして、ブート時にユーザースペースを立ち上げ、維持するといった、他のプロセスが責任を負えないようなことに対しても責任を負うのである。
歴史的に、LinuxではPID 1として振る舞うソフトウェアは由緒あるsysvinitパッケージであった。一方で、sysvinitはかなりの時間、衰えを感じさせていた。 多くの代替案が提案されてきたが、そのうちのたった一つだけが実際に成功した。それはUpstartであり、これは今のところすべてのメジャーなディストリビューションにまで到達することができた。
すでに述べたとおり、initシステムの主たる責任はユーザースペースの立ち上げにある。 そして、優れたinitシステムはそれを速くおこなうものである。 不幸なことに、伝統的なSysV initシステムは特に速いというわけではない。
素早く、そして、効果的なブートアップには2つのことが重要になる
- 小さくスタート
- よりパラレルにスタート
これは何を意味するのか?小さくスタートは、より少ないサービスを起動させる、あるいは、サービスが実際に必要とされるまではその起動を遅らせることを意味する。 サービスの中には、すでに知られているように、早かれ遅かれ必要とされるものもある(syslog, D-Busシステムなどである)が、その他の多くはこのケースには当てはまらない。 例えば、bluetoothdはBluetoothドングルが実際にプラグインされるか、アプリケーションがD-Busインターフェイスとトークしたいと思わない限りは、実行されている必要はない。 同じことはプリンティングシステムにも言える。つまり、マシンに物理的にプリンターが接続されているか、アプリケーションが何かを印刷したいと思うまでは、CUPSといったプリンティングデーモンが起動している必要はないのである。 Avahiもそうである。もしマシンがネットワークに繋がれていない場合、アプリケーションがAPIを使いたいと思わない限りは、Avahiが起動している必要がない。 SSHにだって同じことが言える。誰もマシンに接続しようとしなければ、SSHが起動している必要はなく、最初に接続が来たときに起動させればよい。(そして、sshdがリスンしているかもしれないマシンの殆どは誰かが数カ月にたった1度といった感じで接続していると認めよう。)
よりパラレルにスタートは何かを起動させなければならないなら、スタートアップを(sysvinitがするように)シリアルでおこなうべきではなく、すべてを同時に実行すべきであるということを意味する。こうすることで、使用可能なCPUやディスクIOの帯域を織り交ぜて、全体としてのスタートアップ時間を最小化できる。
ハードウェアとソフトウェアが動的に変化する
モダンなシステム(特に汎用OS)は設定と利用においてかなり動的である。それらはモバイルであり、様々なアプリケーションが起動・停止し、様々なハードウェアが脱着される。 initシステムはサービスの維持に責任があり、ハードウェア・ソフトウェアの変化に注意を向ける必要がある。 initシステムはプログラムを実行したり、ハードウェアを有効にしたりするのに必要なサービスを動的に起動(そして、ときには停止)させる必要がある。
ブートアップをパラレル化しようとしているほとんどの流通しているシステムは、まだ、関連する様々なデーモンの起動を同期させている。 つまり、AvahiがD-Busを必要とするため、D-Busを最初に起動させ、D-Busが準備ができたとシグナルを送らないと、Avahiはスタートしない。 類似のことは他のサービスにも言える。 libvirtdとX11はHAL(ここではFedora13を念頭に置いているので、HALが推奨されないことは無視してほしい)を必要とする。なので、libvirtdとX11が起動する前に、HALが最初に起動する。 そして、libvirtdもまたAvahiを必要とするため、libvirtdはAvahiも待つ。 そして、これらすべてがsyslogを必要とするため、全部が全部syslogが完全にスタートアップし、初期化されるのを待つのである。他もこんな感じだ。
ソケットサービスをパラレル化する
この類のスタートアップの同期で、ブートプロセスの重要な部分がシリアル化してしまう。
もし、同期とシリアル化とのコストを取り除くことができたら、素晴らしくないか?
あぁ、実際はできるんだよ。
そのためには、デーモン同士が正確に何を要求しあっているか、スタートアップが遅れるのはなぜかについて理解する必要がある。
伝統的なUnixデーモンにとって、これに対する一つの答えがある。それは、他のデーモンが提供するサービスのソケットが接続の準備ができるまで、デーモンが待っているからである。
ふつう、それはファイルシステムにあるAF_UNIXソケットだが、AF_INET[6]というのもありうる。
例えばD-Busのクライアントは/var/run/dbus/system_bus_socket
に接続できるようになるまで待つ。
syslogのクライアントは/dev/log
を待つ。
CUPSのクライアントは/var/run/cups/cups.sock
を待つ。
NFSマウントは/var/run/rcpbind.sock
とportmapper IPポートを待つ、など。
こう考えると、実はデーモンが待っている唯一のものはこれなのである。
さて、もしデーモンが待っているものがこれだけなのであれば、我々が、もっと早く接続できるようにこれらのソケットを管理し、デーモンの完全な起動ではなく、実際にはソケットの起動を待つようにすれば、ブート全体をスピードアップさせ、より多くのプロセスをパラレルに起動させることができる。
さて、これはどうやったら可能なのだろうか?
実際はUnixライクなシステムではかなり簡単に可能である。
つまり、我々はリスンするソケットを、デーモンが実際に起動する前に作ること、そして、ソケットをexec()
している中でデーモンに渡すだけということが可能なのである。
この方法で、我々はすべてのデーモンのためのすべてのソケットをinitシステムの1ステップで作成することができ、2つ目のステップですべてのデーモンを一度に起動させることができる。
もし、あるサービスがその他のサービスを必要としており、それが完全にスタートアップしていなかったとしても、それはまったく問題はない。
というのも、それで起こることは、接続は提供側のサービスにキューイングされ、クライアント側はその一つのリクエストで潜在的にそれ以上進めなくなるということなのである。
しかし、一つのクライアントがそれ以上進めなくなるだけであり、それは、一つのリクエストでしか起こらないのである。
また、サービス間の依存関係も、もはや、より適切にパラレル化されたスタートアップをするために設定される必要はなくなるのである。
つまり、一度にすべてのソケットを起動し、あるサービスが他のサービスを必要とするのであれば、そのサービスがそのソケットに接続できることを確実なものにすることができるからである。
これはこの後に続くことの核心となるものなので、違う言葉で例を使ってもう一度これを説明させてほしい。
syslogと様々なsyslogサービスを同時に起動させると、上で説明したようなスキームの場合、何が起こるかといえば、クライアントのメッセージは/dev/log
ソケットバッファーに追加されるということが起こるのである。
バッファーが満杯にならない限りは、クライアントは待つ必要はまったくないし、自身のスタートアップをすぐに続けることが可能なのである。
syslog自身がスタートアップを完了したらすぐに、syslogはすべてのメッセージをデキューし、それらを処理する。
もし、同期的なバスのリクエストが送られ、リプライが期待される場合、何が起こるかといえば、そのクライアントはそれ以上先に進めないということである。
ただ、それは、1つのクライアントでのみ起こることであり、D-Busがそれをキャッチアップし、処理するように管理されるまでのことである*3。
基本的に、カーネルソケットバッファーを使えば、パラレル化を最大化することができる。そして、序列化と同期はカーネルによっておこなわれるので、ユーザースペースからの管理は不要なのである! そして、デーモンが実際に起動する前にソケットがすべて利用可能になるのであれば、依存関係の管理も不要なものに(あるいは、少なくとも二の次)となるのである。 つまり、もし、あるデーモンが他のデーモンを必要とするのであれば、そのデーモンはただ単に必要とするデーモンに接続すれば良いのである。 もし、必要としているデーモンがすでに起動しているのであれば、これはすぐに成功するであろう。 もしそうでなく、起動中であれば、そのデーモンが同期的なリクエストを発行しない限りは、必要とされているデーモンを待つ必要はないのである。 そして、必要とされているデーモンがまったく起動していなかったとしても、自動的にスポーンさせることができるのである。 他のデーモンを必要とするデーモンからみると、違いは存在せず、依存関係の管理はほとんど不必要か、すくなくとも二の次となる。そして、このすべては最適なパラレル化の状態であり、任意にオンデマンドでロードする状態にある。 その上、もっと堅牢でもある。というのも、実際のデーモンが一時的に(おそらくクラッシュしているために)利用不可能であったとしても、ソケットは利用可能な状態に保つからである。 実際、これを使って我々は簡単に、起動し、終了(ないしはクラッシュ)し、また起動させて、また終了する(以下続く)ようなデーモンを作ることができ、この起動終了のすべてをクライアントが気づかなかったり、リクエストを1つも取りこぼさないようにすることができるのである。
さて、そろそろ休憩の時間だろう。席を立って、コーヒーをカップに補充しよう。安心してくれ。面白いことはまだ続く。
だが、まず、いくつかの点についてクリアにしておこう。この類のロジックは新しいものなのか?いや、そうではない。 このように動作するもっとも有名なシステムはAppleのlaunchdシステムである。MacOSではソケットのリスンはすべてのデーモンから切り離され、launchdにより実行されている。 したがって、サービス自体はパラレルに起動することができ、それらの依存関係は設定する必要がない。 そしてこれは本当に巧妙なデザインで、MacOSの夢のような起動時間を実現することができる主な理由なのである。 私はlaunchd関係者が、自分たちが何をやっているかを説明しているこの動画を非常におすすめする。 残念ながら、このアイディアはApple陣営の外では決して人気を得ることはなかった。
このアイディア自体はlaunchdよりもさらに古い。
launchd以前の、由緒あるinetd
はかなりこのように動作していた。ソケットは実際のサービスデーモンを起動し、ソケットのファイル記述子をexec()
の中で渡すデーモンを中心につくられたのである。
しかし、inetd
のフォーカスははっきりとローカルサービスにはなく、インターネットサービスにあった(後の再実装ではAF_UNIXソケットもサポートされたが)。
inetd
はブートアップをパラレル化するツールでもなかったし、潜在的な依存関係を正しく把握するのに役立つものでもなかった。
TCPソケットに対し、inetd
が主に使われていた方法は、入ってくる接続それぞれに対し、1つの新しいデーモンインスタンスをスポーンさせるというものであった。
これは、それぞれの接続に対し、1つの新しいプロセスがスポーンされ、初期化されるというものであり、ハイパフォーマンスのサーバーのためのレシピではなかった。
しかし、当初より利口なことに、inetd
は別のモードもサポートしていた。このモードでは、最初の接続で1つのデーモンがスポーンし、その後、このインスタンスが残り続けて後続の接続の受付もするというものである(これはinetd.conf
にあるwait
およびnowait
オプションで設定されるが、残念なことにまったく適切に説明されていないオプションである)。
接続ごとにデーモンがスタートすることで、おそらく、inetdは遅いという悪評を得てしまった。だが、これはまったくフェアではない。
バスサービスをパラレル化する
Linuxのモダンなデーモンは素のAF_UNIXソケットではなくD-Busを通じてサービスを提供する傾向がある。さて、ここで問題がある。これらのサービスに、伝統的なソケットサービスの場合と同一の、ブートロジックをパラレル化は適用可能なのだろうか? 答えはイエス、できる。D-Busはこのための正しいフックをすべて、すでに持っている。バスアクティベーションを使うことで、サービスは最初にアクセスされたときに起動させることができる。 ただ、D-Busサービスの提供側と利用側とを同時に起動させるのに我々はリクエストごとの同期が必要なのだが、このアクティベーションは最小限しか提供しない。 つまり、AvahiとCUPS(注: CUPSはmDNS/DNS-SDプリンターをブラウズするためにAvahiを利用する)を同時に起動させようとすると、我々は単純にこの2つを同時に起動させれば良い。 もし、バスアクティベーションロジックを通じて、CUPSがAvahiより速く起動すれば、Avahiが自身のサービス名を確立するまでD-Busに(訳注: CUPSの)リクエストをキューさせておけば良い。
なので、要点はこうである。 ソケットベースのサービスアクティベーションとバスベースのサービスアクティベーションは、ともに、さらなる同期なしに、すべてのデーモンをパラレルに起動させることを可能にする。 アクティベーションはサービスの遅延読み込みも可能にする。 もし、あるサービスがめったに使われないのであれば、ブート時にそれを起動させる代わりに、誰かがソケットやバス名に初めてアクセスしてきたときにそれを読み込めばよい。
これが素晴らしくなければ、一体何が素晴らしいというのだろう!
ファイルシステムのジョブをパラレル化する
現在のディストリビューションのブートプロセスをシリアル化したグラフ*4をみれば、デーモンのスタートアップよりも多くの同期ポイントがある。
もっとも目立つのがファイルシステムに関連するジョブである。つまり、マウント、fsck、クォーターである。
今まさに、ブート時には多くの時間を消費して、/etc/fstab
に記述されているすべてのデバイスがデバイスツリーに現れ、fsckされ、マウントされ、(有効であれば)クォーターがチェックされるのをアイドリングして待つことになる。
これが完全に終わってからようやく、後続作業にとりかかり、実際のサービスをブートさせるのである。
これは改善できるのであろうか?できることがわかっている。Herald Hoyerは由緒あるautofsシステムをこれに使うというアイディアに至った。
connect()
コールがサービスが他のサービスに関心があることを示すのとちょうど同じように、open()
(ないしは類似のコール)で、サービスが特定のファイルやファイルシステムに関心があることを示す。
なので、どれだけパラレル化の度合いを改善するために、これらのアプリが探しているファイルシステムがマウントされていなかったり、すぐに利用できなかったりする場合は、これらのアプリに待たせるようにすることができる。
つまり、autofsのマウントポイントをセットアップし、その後、通常のブートアップで実行されることになっているファイルシステムがfsckやクォーターを終えたら、本物のマウントとautofsのマウントとを置き換えるのである。
ファイルシステムがまだ準備できていなければ、アクセスはカーネルによりキューに入れられ、アクセスしてきたプロセスは待たされる。しかし、その1つのデーモンと1つのアクセスに限る。
この方法で、ファイルシステムが完全に利用可能になる前にでも、デーモンを起動させることが可能になる。デーモンは1つもファイルを失わないし、パラレル化も最大化される。
ファイルシステムジョブとサービスジョブのパラレル化は/
には当てはまらない。サービスのバイナリーが通常はそこに格納されているからである。
しかし、/home
のようなファイルシステムについて言えば、通常は、/
よりも大きかったり、ひょっとすると暗号化されていたり、リモートであるかもしれなかったり、通常のブートでデーモンがめったにアクセスしなかったりする。これ(訳注: パラレル化)によりブート時間をかなり改善できる。
言うまでもないかもしれないが、procfsやsysfsといった仮想のファイルシステムはautofsでマウントすべきではない。
initシステムにautofsを組み込むことを、少し危ういと思ったり、異様だと思ったり、もしかしたら、「狂った*5」ものの見方だと思ったりする読者がいても驚きはしない。 しかし、これをいじくり回してみて、広く言えることとしては、これは確実に、かなり正しいと感じるということだ。 autofsをここで使うということは、単純に、マウントポイントをそれが紐づくファイルシステムを提供する必要なしに、マウントポイントを作成できるということを意味する。 実際にはアクセスを遅らせているだけに過ぎない。 アプリケーションがautofsのファイルシステムにアクセスしようとしており、それを本物のファイルシステムに置き換えるのに時間がかかってしまえば、そのアプリケーションは割り込み可能なスリープ状態でハングすることになる。これは、例えばC-cなどで安全にキャンセルすることができることを意味している。 また、留意してほしいのは、任意のポイントで、マウントポイントが最終的にマウントすべきでない場合(たぶん、fsckが失敗したからだ)は、autofsに(ENOENTのような)クリーンなエラーコードを返すように伝えればいいということだ。 なので、私が言いたいと思っていることは、initシステムにautofsを組み込むことが、一見、冒険的に見えたとしても、我々の書いたコードはこのアイディアが--正しい理由で正しい方法で行われていれば--実際には驚くほどうまく動くことを示しているということだ。
さらに、これらはダイレクトなautofsマウントであるべきだということにも留意してほしい。これはアプリケーションから見ると、古典的なマウントポイントとautofsベースのマウントポイントには実質的な差異はほとんどないことを意味する。
最初のユーザーPIDを小さく保つ
MacOSのブートアップロジックから学んだもう1つのことはシェルスクリプトは有害であるということである。
シェルは速くもあり、遅くもある。
つまり、ハックするのは早いが、実行は遅いのである。
シェルが/bin/bash
であろうが、(シェルスクリプトをより速く実行できるように書かれた)その他のシェルであれ、結局、このアプローチは遅くなる運命にある。
私のシステムで/etc/init.d
にあるスクリプトはgrep
を少なくとも77回呼び出している。
awk
は92回、cut
は23回、sed
は74回である。
これらのコマンドやその他のコマンドが呼び出されるたびにプロセスがスポーンし、ライブラリは検索され、i18nのようなものがいくつかセットアップされ、その他もろもろがおこなわれる。
そして、一部の例外を除き、ちょっとした文字列を操作する程度のことを行った後、プロセスは再び終了する。
もちろん、これは間違いなく、驚くほど遅い。
ほかでもないシェルがこのように物事を実行するのである。
これにくわえて、シェルスクリプトは非常に壊れやすくもあり、環境変数やその種のもの、予測と統制のしづらい要素によって、劇的にその挙動を変化させるものなのである。
なので、ブートプロセスからシェルスクリプトを取り除こう! それをする前に、我々はシェルスクリプトが現時点で正確には何のために使われているかを把握する必要がある。 あぁ、全体像としては、ほとんどの時間、シェルスクリプトがやっていることは実際、かなりつまらないことであろう。 スクリプトでやっていることのほとんどは取るに足らないセットアップであったり、サービスの取り壊しであったりで、Cで書き直すべきであり、別の実行ファイルにあるべきだったり、デーモン自身の中に移すべきだったり、あるいは、シンプルにinitシステムがやるべきだったりするのだ。
システムのブートアップからシェルスクリプトをすっかり取り除くというのは、近いうちにできるようなことではない。 これらをCで書き直すのには時間がかかるし、いくつかのケースではまったく意味がなかったりするだろうし、場合によってはシェルスクリプトがこれなしに済ませるには便利すぎるということもあるだろう。 だが、少なくとももっと目立たせないようにすることはできる。
ブートプロセスへのシェルスクリプトの蔓延具合を図る良きメトリックはシステムが完全にブートアップしたあとに、開始される最初のプロセスのPIDである。
ブートアップ後、ログインし、ターミナルを開き、echo $$
をタイプしよう。
これを手元のLinuxシステムで試してみて、MacOSでやってみた結果と比べてみよう!(ここでヒント。こんな感じだ。我々が持っているテストシステムでやってみたところ、LinuxのPIDは1823なのに対しMacOSのPIDは514だ。)
プロセスを追跡し続ける
サービスを起動し、維持するシステムの中心的部分は、プロセスのベビーシッターを務めること、つまり、サービスを監視するということである。 サービスがシャットダウンすれば再起動させる。 サービスがクラッシュすれば、関連する情報を集め、管理者の手元に置き、その情報とABRTのようなクラッシュダンプシステムから利用可能なものとつなぎ合わせ、syslogやauditシステムのようなロギングシステムに入れておく。
このシステムはサービスを完全にシャットダウンさせる能力が必要である。 これは簡単に聞こえるが、考えているよりは難しいものである。 Unixの伝統ではプロセスはダブルフォークしており、その親の監督下を逃れることができていしまう。 古い親は、自身が確かに起動させたサービスと新しく生まれたプロセスとの関係を知ることはないのである。 例を挙げよう。 現在のところ、ダブルフォークされ、誤った挙動をしているCGIスクリプトはApacheをシャットダウンしても停止されることはない。 さらに言えば、名前と目的を知らない限りはそのプロセスとApacheの関係を知ることさえできないのである。
さて、プロセスを追跡し続け、数え切れないほどフォークしているような場合でも、それらがベビーシッターから逃れることなく、1つのユニットとしてコントロールするにはどうしたらいいのだろうか?
様々な人々がこれに対する様々な解法を考えついた。
ここで詳細を触れることはしないが、少なくとも次のことは言わせてほしい。
ptraceないしはnetlinkコネクター(カーネルインターフェイスで、任意のプロセスがfork()
やexit()
するたびにnetlinkメッセージを得られるようになるもの)は、いくらかの人々が調査し、生み出したものだが、これらをベースにしたアプローチは、美しくなく、そこまでスケーラブルではないとして批判されてきた。
では、どうすればよいか?
あぁ、ちょっと前から、カーネルはコントロールグループ("cgroups"としても知られている)を知っている*6。
基本的にcgroupではプロセスのグループの階層を作り出すことが可能になる。
階層が仮想ファイルシステムとして直接見えるため、簡単にアクセスすることができる。
グループの名前は基本的にそのファイルシステムのディレクトリ名である。
特定のcgroupに属している、あるプロセスがfork()
をすると、その子は同じグループのメンバーとなる。
その子が特権を与えられ、cgroupファイルシステムにアクセスできない限りは、そのグループから逃れることはできない。
もともと、cgroupがカーネルに導入されたのはコンテナーのためであった。
あるカーネルのサブシステムはあるグループのリソースに対し制限をかけることができるのだ。例えば、CPUやメモリーの使用量だ。
伝統的なリソース制限(setrlimit()
で実装されたようなもの)は(ほとんど)プロセスごとである。
一方、cgroupはプロセスのグループ全体に対し制限をかけることができる。
また、cgroupはコンテナーというユースケース直接でなくても、制限をかけるのに便利である。
例えば、Apacheとその子すべてが使ってもよいメモリーやCPUの合計量に制限をかけることができる。
そして、誤った挙動をするCGIスクリプトは、簡単にフォークすることで、setrlimit()
のリソースコントロールからもはや逃れることはできないのである。
コンテナーやリソース制限の強化に加え、cgroupはデーモンを追跡し続けるのに大変便利である。
cgroupのメンバーであることは確実に子に継承され、逃れることはできないのだ。
ある通知システムが利用できるのだが、これによってcgroupが空になったら監督者のプロセスは通知を受けることが可能になる。
あるプロセスのcgroupは/etc/$PID
を読むことで調べることができる。
したがって、cgroupはベビーシッターの目的でプロセスを追跡し続けるのにとても良い選択肢なのである*7。
プロセスの実行環境をコントロールする
良きベビーシッターはデーモンの起動・終了・クラッシュを監督・コントロールするだけのものではない。 デーモンに優れた、最小限で、安全な実行環境をセットアップすることもある。
これはsetrlimit()
によるリソース制限やユーザーIDやグループID、環境ブロックといった、プロセスの明らかなパラメータを設定することを意味するが、それだけでは終わらない。
Linuxカーネルによりユーザーと管理者にプロセスに対する多大なコントロール(いくつかは現時点ではめったに使われない)を享受する。
それぞれのプロセスに対し、CPUとIOスケジューラーのコントロール、ケーパビリティ―バウンディングセット*8、CPUアフィニティやもちろん、追加の制限をcgroup環境に設定することなどができる。
例えば、ioprio_set()
とIOPRIO_CLASS_IDLE
を組み合わせるとlocate
のupdatedb
によるシステムの対話への影響を最小限にする優れた方法である*9。
加えて、ある高度なコントロールも非常に便利である。読み込み専用のbindマウントをベースにしたリードオンリーのファイルシステムのオーバーレイを設定するようなことである。 この方法だと、あるデーモンをすべて(あるいは一部)のファイルシステムをデーモンに対し、読み込み専用で見せることができ、それによりすべての書き込み要求に対しEROFSを返すことになる。 このような感じで、これはデーモンができることを制限するのに使うことができる。 これはやり方としては手軽なSELinuxポリシーシステムと似ている(ただ、SELinuxを確実に置き換えるものではない。そういう悪い考えは持たないでくれ、頼むから。)
最後に、ログを取ることはサービスを実行する上で重要な部分である。
理想的にはサービスが生み出す、僅かな出力のすべてをログに取るべきである。
そのため、initシステムは自分がスポーンさせたデーモンを起動直後からログを取る機能を提供し、stdout(訳注: 標準出力)やstderr(訳注: 標準エラー出力)をsyslogにつなぐ。
あるいは、いくつかのケースでは/dev/kmsg
に繋ぐ。
ちなみに、/dev/kmsg
は多くのケースで便利なsyslogの代替となるものだ(組み込みな人たちよ、よく聞くがいい!)。
特に、カーネルログバッファーが非常に巨大なすぐ使えるものとして設定されている場合は特にそうだ *10。
Upstartの場合
最初に、強調しておきたいのは、私はUpstart*11のコードが本当に好きで、十分にコメントがついていて、中身を追いやすい。他のプロジェクトもここから学ぶべきものがある(自分も含めて。)
それはそうだが、私はUpstartの全体的なアプローチには同意できない。 だが、最初に少し、このプロジェクトについて説明しておこう。
Upstartはsysvinitとはコードを共有していないが、機能面では上位互換であり、よく知られたSysV initスクリプトとある程度は互換性がある。 Upstartの主な特徴はイベントベースのアプローチである。 プロセスの起動と停止はシステムに起こる「イベント」に紐付いており、「イベント」は多種多様なものがなりえる。例えば、ネットワークインターフェイスが有効になったり、他のソフトウェアが起動したりだ。
Upstartはこれらのイベントによりサービスをシリアル化している。
syslog-start
イベントがトリガーされると、これはD-Bus
を開始することの指示として使われる。というのも、D-BusがSyslogを使えるようになったからだ。
そして、dbus-started
がトリガーされると、NetworkManager
が起動する。これはNetworkManagerが今やD-Busを使えるようになったからだ、という具合だ。
この方法で、存在し、管理者や開発者が理解する実際の論理的な依存関係のツリーがイベントやアクションのルールに翻訳、エンコードされるという人がいる。 すべての「AがBを必要とする」というルールは管理者や開発者によって「Bが起動したらAが起動する」プラス「Bが停止したらAも停止する」ものとして、理解されるのである。 ある点ではこれは確かに簡素化ではある。Upstart自身のコードにとっては特にそうだ。 しかし、この簡素化は実際には有害であると私は主張したい。 何と言っても最初に、論理的な依存関係の体系はどこへも行っておらず、Upstartファイルを書く人間がいまや依存関係を手動でこれらのイベントやアクションルール(実際には、それぞれの依存関係のための2つのルール)に翻訳しなければならないのである。 なので、コンピューターに依存関係をベースに何をすべきかを示させる代わりに、ユーザーが手動で依存関係を簡単なイベントやアクションルールに翻訳しなければならないのである。 また、依存関係の情報は決してエンコードされないため、実行時に利用することはできず、これは事実上、なぜ、これが起きたのか、例えば、なぜ、Bが起動したら、Aが起動したのか、を調べようとする管理者には何もわからないということを意味する。
さらに、イベントロジックはすべての依存関係を、つま先から頭の先までぐるっと一回りすることになる。仕事の量を最小化する(これはこのブログストーリーの冒頭で指摘したとおり、良きinitシステムがフォーカスすべきものである)代わりに、実際には作業中にすべき仕事の量を最大化することになる。 他の言い方をすれば、明確なゴールを持ち、そのゴールに到達するために本当に必要なことだけをする代わりに、Upstartは一歩進んでしまうと、その後、それに続くすべてのステップを歩んでしまうのである*12。
より簡単に説明してみよう。 ユーザーがD-Busを起動させただけという事実は、決してNetworkManagerも起動すべきという指示にはならない(しかし、これがUpstartのやっていることである)。 逆に考えてみよう。ユーザーがNetworkManagerを必要としたときに、これが明確な指示となって、D-Busも起動すべきだ(これがほとんどのユーザーが本当に期待するものではないかい?)。
良きinitシステムは必要なものだけを起動すべきで、オンデマンドであるべきだ。遅延かパラレル化と先行だ。 しかし、initシステムは必要以上のものを起動させるべきではない。とくに、そのサービスを使えるようなもので、インストールされているすべてを起動させてはいけない。
最後に、私はイベントロジックの実際の利便性を見出すことができなかった。 Upstartで見せられるほとんどのイベントが実際には本来は即座のものではなく、持続性があるこように私には思えた。 サービスが起動し、実行中になり、停止する。 サービスがプラグインされ、利用可能になり、またプラグアウトされる。 マウントポイントがマウントされるプロセスにあり、完全にマウントされ、アンマウントの途上にある。 電源プラグが差し込まれ、AC電源でシステムが稼働し、電源プラグが抜かれる。 initシステムやプロセスの監督者が取り扱うべきイベントのほんの少数だけが、即座のものであり、それらのほとんどは、起動、実行状態、停止のタプルである。 Upstartに戻ると、こういった情報はUpstartでは利用できないのだ。なぜなら、Upstartは単一のイベントにフォーカスしており、持続性のある依存関係は無視してしまうからである。
さて、気づいてしまったのだが、上で私が指摘したような問題のいくつかは、最近のUpstartの変化でいくらか和らげられている。
とくに、Upstartルールファイルの状況ベースのシンタックス、例えば、start on local-filesystems and net-device-up IFACE=lo
といったものだ。
だが、私からすれば、これはほとんど、コアのデザインに弱点を抱えるシステムを直そうとする試みのように見えてしまう。
いくつかの選択に疑問があるかもしれない(上記参照)としても、そして、実際に多くの機会を逸している(これも上記参照)としても、その他は、Upstartにデーモンの子守としてOKを出せる。
sysvinitを除けば、他にinitシステムとして挙げられるのはUpstartとlaunchdだ。 initシステムの多くはUpstartやsysvinit以上のものを打ち出せていない。 この他、最も興味深い競争相手はSolaris SMFである。これはサービス間の適切な依存関係をサポートする。 しかし、多くの点でSolaris SMFはとても複雑であり、言わせてもらえば、XMLを過度に使っていて、知られていることへの新しい術語がちょっとアカデミックである。 これはcontractシステムといったSolarisに固有な機能に密接に結びついてもいる。
すべてをうまくやる: systemd
おっと、もう1回、ちょっと小休止を挟むのにいい時間になったようだ。というのも、ここまでで、私が考える良きPID 1が何をすべきで、現時点で最も使われているinitシステムが何をしているかを説明できたことだし、そろそろメインディシュに移るころだからだ。 さて、席を立って、もう一度、コーヒーカップをいっぱいにしよう。 それだけの価値はあるはずだ。
読者はこう思っているかもしれない。
ここまでで理想的なinitシステムの必要条件と機能として提案してきたようなものが、実際には、systemd
と呼ばれる(まだ実験段階の)initシステムにはもうあるのだろと。これこそが、私がここでアナウンスしたいものだ。
再度になるが、コードはここにある*13。
そして、ここではその機能について簡単な紹介とその背後にある理由付けだ。
systemdはシステム全体の起動と監督をおこなう(だから、この名前なんだ)。
systemdはここまでで指摘してきたような機能プラスアルファを実装している。
systemdはユニットという概念を軸につくられている。
ユニットは名前とタイプを持つ。
ユニットの設定は通常はシステムより直接読み込まれるため、ユニット名は実際にはファイル名である。
例えばこうだ。
avahi.service
というユニットは同名の設定ファイルから読み込まれ、もちろんAvahiデーモンをカプセル化したユニットになる。
ユニットにはいくつかの種類がある。
service
: ユニットの中でも明確な種類のユニットである。 デーモンは起動、停止、再起動、リロードすることができる。 SysVとの互換性のため、我々は自分たちのサービスの設定ファイルだけでなく、古典的なSysv initのスクリプトも読み込むことができる。 とりわけ、存在すれば、LSBヘッダーをパースする。 したがって、/etc/init.d
は別の設定ファイルの置き場所以上のものである。socket
: このユニットはファイルシステム上やインターネット上のソケットをカプセル化する。 現時点ではAF_INET
、AF_INET6
、AF_UNIX
ソケットをサポートし、種類としてはストリーム、データグラム、シーケンシャルパケットをサポートしている。 また、古典的なFIFOも転送としてサポートしている。 それぞれのsocket
ユニットは対応するservice
ユニットを持っており、ソケットやFIFOに最初の接続が来ると、対応するservice
ユニットが起動される。 例えば、nscd.socket
は、接続があるとnscd.service
を起動させる。device
: このユニットはLinuxのデバイスツリーにあるデバイスをカプセル化する。 デバイスがudevのruleを通じて、このユニットにマークされると、systemdではdevice
ユニットとして見ることができるようになる。udev
に寄って設定されたプロパティーはデバイスユニットの依存関係を設定するための設定情報として利用することができる。mount
: このユニットはファイルシステム階層のマウントポイントをカプセル化する。 systemdはすべてのマウントポイントを監視し、マウントポイントが行ったり来たりする様子を把握する。 また、マウントポイントをマウント・アンマウントするのにも使える。/etc/fstab
はここではこれあのマウントポイントに関する追加の設定情報として使われる。 これはSysVのinitスクリプトがservice
ユニットの追加の設定情報として使われるのと似ている。
5.automount
: この種類のユニットはファイルシステム階層のautomountポイントをカプセル化する。
それぞれのautomount
ユニットは対応するmount
ユニットを持ち、mount
ユニットはautomountディレクトリにアクセスがあるとすぐに起動(=マウント)される。
target
: このユニットタイプはユニットの論理的なグループとして使われる。 それ自身では一切何もしない代わりに、target
ユニットは単純に他のユニットを参照し、参照されたユニットを一緒にコントロールすることができる。 例を上げるとこうだ。multi-user.target
は基本的に、古典的なSysVシステムではランレベル5の役割を果たすユニットである。bluetooth.target
はBluetoothドングルが利用可能になるとすぐに要求されるターゲットで、Bluetooth関係のサービスを制御する。 これによって制御されるサービスはこういうときでなければ起動されないようなサービスで、bluetoothd
やobexd
といったものだ。snapshot
:target
ユニットと同様に、snapshot
ユニットも自分自身では何もせず、他のユニットを参照することだけを目的とするユニットである。 スナップショットはinitシステムのすべてのサービスやユニットの状態を保存し、ロールバックするのに使える。 主として2つのユースケースが考えられている。 1つは、「エマージェンシーシェル」のような特殊な状態に一時的にユーザーが入ることを許可する場合だ。 現在のサービスを停止し、元の状態に戻るための簡単な方法を用意し、一時的に引き下げたすべてのサービスを再び引き上げるのである。 もう1つはシステムのサスペンドである。 未だに多くのサービスが正しくシステムのサスペンドを取り扱えないが、サスペンドする前にこれらをシャットダウンし、後から復帰させてしまうのはたいていベターな考えだ。
これらすべてのユニットには互いに依存関係がある(依存関係はポジティブなのもネガティブなのもある。例えば、「必要とする(Requires)」と「対立する(Conflicts)」だ)。
デバイス(訳注: ユニット)にはサービスに対する依存関係をもたせることができる。これは、デバイスが利用可能になれば、すぐにあるサービスが起動することを意味する。
マウント(訳注: ユニット)には、自身がそこからマウントされているデバイスに対する明示的な依存関係を得ることになる。
マウント(訳注: ユニット)はまた、それらのプレフィックスであるマウント(訳注: ユニット)に対し明示的な依存関係を持つことになる(つまり、/home/lennart
というマウントは追加で/home
のマウントに明示的に依存関係を得るのだ)。
その他の機能を短いリストに記す
スポーンされたプロセスに対して、それぞれ管理することができる。 環境、リソース、制限、ワーキングディレクトリ・ルートディレクトリ、umask、OOMキラー調整、niceレベル、IOのクラスと優先度、CPUのポリシと優先度、CPUアフィニティ、timer slack*14、ユーザーID、グループID、補助グループID、読み込み可能・書き込み可能・アクセス不可のそれぞれのディレクトリ、共有・プライベート・スレーブそれぞれのマウントフラグ、ケーパビリティ・バウンディングセット*15、セキュアビット、CPUスケジューラーのフォークの再設定、プライベートな
/tmp
名前空間、様々なサブシステムへのcgroup。 また、簡単にサービスの標準入力、標準出力、標準エラー出力をsyslogや/dev/kmsg
や任意のTTYに接続することができる・ 入力にTTYを接続すると、systemdは必要に応じてそれを待ったり、強制したりし、プロセスが排他的なアクセスを得られるようにする。実行されるプロセスはすべて自分自身のcgroupを獲得し(現時点のデフォルトではdebugサブシステムにおいて。というのも、このサブシステムは他では使用されておらず、プロセスの最も基本的なグループ化以上でもなければ以下でもないから*16である)、systemdを設定し、外部ですでに設定されたcgroupにサービスを置くことができる。例えば、libcgroupユーティリティー経由で(外部でcgroupを設定する)といった具合である。
ネイティブな設定ファイルの構文はよく知られている
.desktop
ファイルのそれに厳格に従っている。 これは簡単な構文で、すでに多くのソフトウェアフレームワークでこれのパーサーが存在する。 また、これのおかげで、サービスの説明やその他類似のもので、国際化のための既存のツールを利用することができるようになる。 管理者や開発者は新しい構文を学ぶ必要はないのである。すでに述べたとおり、我々はSysV initスクリプトに対するご完成を提供する。 我々はLSBおよびRed Hat chkconfigヘッダーが利用可能であれば、それを駆使する。 もし、これらが利用できなければ、
/etc/rc.d
の起動の優先順といったその他の利用可能な情報で最善を尽くすようにする。 これらのinitスクリプトは単純に設定の異なるソースとして考えられており、そのため、systemdサービスへの簡単なアップグレードパスを提供することができる。 必要に応じて、古典的なPIDファイルをサービスがデーモンのメインプロセスのIDを特定するのに読み込むことも可能である。 我々はLSB initスクリプトヘッダーからの依存関係の情報を利用し、これらをネイティブなsytemdの依存関係に翻訳することに注意してほしい。 さらに、Upstartはこれらの情報を取得し、利用できないことに注意してほしい。 したがって、素のUpstartシステムと大半のLSB SysV initスクリプトとの組み合わせでは、パラレル化はできない一方、systemdを動かしている似たようなシステムではそれができるのである。 事実、Upstartにとって、SysVスクリプトはまとまって、1つのジョブとなり、実行されるため、これらを別個に扱うことができない。 再び、systemdと比べると、systemdではSysV initスクリプトはただ別の設定元であり、これらはすべて、その他のネイティブなsystemdのサービスと同じように別個に扱われ、制御される。同様に、我々は既存の
/etc/fstab
設定ファイルも読み込むことができ、これを、ただの別の設定元として考慮する。commnet=
というfstabオプションを使うことで、/etc/fstab
エントリーをsystemd
制御下のautomountのポイントとしてマークすることさえできる。複数の設定素で同じユニットが設定されている場合(例えば、
/etc/systemd/system/avahi.service
と/etc/init.d/avahi
とが存在する場合)、ネイティブな設定が常に上位となり、レガシーなフォーマットは無視される。 これにより、簡単なアップグレードパスを用意でき、パッケージはSysV initスクリプトとsystemdのサービスファイルとの双方をしばらくの間、持つことができる。我々は簡単なテンプレートおよびインスタンスのメカニズムをサポートする。 例えば、6つのgettyのための6つの設定ファイルを用意する代わりに、我々は1つの
getty@.service
ファイルを用意するだけで良い。 これにより、getty@tty2.service
といった具合に、インスタンス化することができる。 インターフェイス部分さえも依存関係の表現により継承することができる。 例えば、eth0
という文字列はワイルドカードで扱いながら、dhcpd@eth0.service
というサービスはavahi-autopid@eth0.service
を制御するようにエンコードすることが簡単にできる。ソケットアクティベーションにより我々は伝統的なinetdモードとの完全な互換性をサポートする。これは、launchdのソケットアクティベーションを真似しようとするもので、新しいサービスには推奨されている非常に簡単なモードに加えてサポートされるものである。 inetdモードはソケットを起動済みのデーモンに渡すことだけができるが、ネイティブモードでは任意の数のファイル記述子を渡すことができる。 また、我々は接続ごとに1つのインスタンスをサポートする。これは、すべての接続に対し1つのインスタンスというモードに加えてサポートされる。 前者では、接続パラメーターに従って、デーモンがcgroupを名付け、これのために、既述のテンプレートロジックを利用する。 例えば、
sshd.socket
はsshd@192.168.0.1-4711-192.168.0.2-22.service
をsshd@.service/192.168.0.1-4711-192.168.0.2-22
というcgroupでスポーンさせるかもしれない(つまり、IPアドレスとポート番号がインスタンス名に使われるのである。AF_UNIXソケットに対してはPIDと接続してきたクライアントのユーザーIDを利用する)。 これにより、管理者はデーモンの様々なインスタンスを識別子、その実行を別個に制御するための素晴らしい方法を手に入れる。 ネイティブなソケットを渡すモードはアプリケーションで非常に簡単に実装できる。$LISTEN_FDS
が設定されていると、これには渡されたソケットの番号が含まれており、デーモンは.service
ファイルに一覧でソートされているとおりに、ソケットを見つけてくる。 このリストはファイル記述子3から始まる(うまく書かれたデーモンは自身が受け取るソケットが1つ以上の場合に、fstat()
とgetsockname()
も、ソケットを識別するのに使うことができる)。 加えて、$LISTEN_PID
をファイル記述子を受け取るデーモンのPIDに設定することもできる。というのも、環境変数は通常、サブプロセスにより継承されるものであり、(訳注: プロセスの親子関係の)鎖を下るほどプロセスを混乱させるものとなるためである。 このソケットを渡すロジックをデーモンで実装するのが非常に簡単であったとしても、ソケット渡しをどうすべきかを示しているBSDライセンスのリファレンス実装をサポートする。 我々はすでに既存のデーモンのいくつかを新しいスキームへポートしている。我々は
/dev/initctl
との互換性をある程度は提供する。 この互換性は実際にはFIFOによりアクティブ化されるサービスとして実装される。 これはレガシーな要求をD-Busの要求へと単純に変換するものである。 事実上、これは、Upstartやsysvinit
の古いshutdown
やpoweroff
、あるいは類似のコマンドをsystemdでも動かし続けられるということを意味する。utmp
やwtmp
との互換性も提供する。utmp
やwtmp
のお粗末さを考えると、妥当な程度以上のものになるかもしれない。systemdはユニット間のいくつかの依存関係をサポートする
After
やBefore
を使って、ユニットがどのようにアクティブになるかを決めることができる。 これは完全にRequires
やWants
と直行している。この2つは必須ないしは任意のポジティブな要件の依存関係を表現するものである。 そして、Conflicts
はネガティブな要件の依存関係を表現する。 最後には、これらはあまり使われないが、さらに3つの依存関係がある。systemdは最小限のトランザクションシステムを持っている。 意味するところはこうだ。 あるユニットが起動ないしはシャットダウンを要求された場合、一時的なトランザクションにそれとそのすべての依存関係が追加される。 そして、そのトランザクションが一貫性のあるものか(つまり、すべてのユニットの
After
やBefore
を順番付けることが循環しないかどうか)を検証する。 そうでなければ、systemdはこれを修復しようとし、ループを除くかもしれない不必要なジョブをトランザクションから除く。 また、systemdはトランザクションの中の不必要なジョブで実行中のサービスを止めるようなジョブを抑制しようとする。 不必要なジョブとは、オリジナルのリクエストには直接含まれていないが、Wants
タイプの依存関係により制御されているようなジョブのことである。 最後に、トランザクションのジョブがすでにキューに入れられているジョブと矛盾しないかどうか、そのときに任意にトランザクションを中止できるかをチェックする。 すべてがうまくいき、トランザクションには一貫性があって、影響が最小限に抑えられれば、そのトランザクションはすでに進行中のジョブとマージされ、実行キューに追加される。 事実上、これが意味するところは、我々は、要求された操作が実行する前に、これが妥当なものであるかを検証し、可能であれば修復し、本当に実行できない場合にのみ、失敗させるようにすることである。スポーンし、監督するすべてのプロセスのPIDと終了ステータスに加え、開始・停止時間も記録する。 このデータは、abrtdやauditd、syslogのデータを持つデーモンとの橋渡しに、使うことができる。 こう考えてほしい。クラッシュしたデーモンをハイライトして見せてくれるUIであり、syslogやabrtsic.)、auditdそれぞれのUIへ簡単にナビゲートしてくれるUIへだ。 syslogやabrt[sic.]やauditdは特定の進行の際に、このデーモンからデータを生成したり、このデーモンのためにデータを生成するだろう。
initプロセス自身を任意のタイミングで再実行することをサポートする。 デーモンの状態は再実行前にシリアル化され、事後にシリアル化を解除される。 この方法で、我々はinitrdデーモンから最終のデーモンまでをハンドオーバーし、initシステムのアップグレードを容易にする簡単な方法を提供する。 開かれたソケットとautofsマウントは適切にシリアル化される。 これは、クライアントがinitシステム自身の再実行に気づきすらしない方法で、ソケットやマウントをいつでも接続可能な状態に保ち続けるためである。 また、システムサービスの大部分はともかくcgroupの仮想ファイルシステムにエンコードされていることで、シリアル化のデータにアクセスすることなく再開することさえ可能である。 再実行のコードパスはinitシステムの設定をリロードするコードパスと実際にはほとんど同じであり、このコードパスでは、再実行(これはおそらく、(訳注: リロードに比べれば)トリガーされることは少ないだろう)がリロード(こちらのほうがおそらくより一般的)として同様のテストを受けられることを保証するものである。
ブートプロセスからシェルスクリプトを取り除く作業をはじめたことで、基本的なシステムのセットアップの部分をCで再コーディングし、これを直接systemdへと移している。 この中には、APIファイルシステムのマウント(つまり、
/proc
や/sys
、/dev
といった仮想ファイルシステム)やホスト名の設定がある。ソケットベースやバス名ベースのアクティベーションを我々は強調したく、それゆえ、ソケットとサービスの間の依存関係をサポートしている一方、伝統的なサービス間の依存関係もサポートしている。 我々はこういったサービスが自身の準備が整ったことを知らせる方法をいくつもサポートしている。 設定したサービス名が現れるまでバスを監視することに加え、フォークし、起動プロセスを終了させること(つまり、伝統的な
daemonize()
の振る舞い)で(訳注: サービスの準備が整ったことを知らせることができる)。systemdによりプロセスがスポーンされるたびに確認を求めるインタラクティブモードがある。 これは、カーネルコマンドラインに
systemd.confirm_spawn=1
を渡すことで有効にすることができる。systemd.default=
というカーネルコマンドラインパラメーターにより起動時にどのユニットをsystemdが開始するかを指定することができる。 通常、ここでは、multi-user.target
のようなものを指定するが、ターゲットの代わりに単一のサービスを指定することも可能である。 例えば、提供されたままの状態で、我々はemergency.service
というサービスを同梱している。 これは、使い勝手という点ではinit=/bin/bash
と似たものであるが、実際にはinitシステムを動かしているという利点があり、それゆえ、緊急のシェルから完全なシステムを起動させるというオプションも提供している。サービスを起動、停止、検証することを可能にする最小限のUIがある。 完成にはほどとおいが、デバッグツールとしては便利なものである。 これはValaで書かれており(イェイ!)、
systemadm
という名前でいく。
systemdはLinux固有の機能を多く使っており、POSIXに閉じこもっていない点は注意すべきでだろう。 他のオペレーティングシステムへの移植性のためにデザインされているシステムでは提供できないような機能の多くを解き放っている。
状態
上で挙げたすべての機能はすでに実装されている。 今からでも、すでにsystemdはUpstartやsysvinitを気軽に入れ替えるものとして使うことができる(少なくとも、多すぎるほどのネイティブなUpstartのサービスがまだない限りは。幸いなことに、多くのディストリビューションで、ネイティブなUpstartサービスは多すぎるというほどには多くない)。
しかし、テストは最小限であり、バージョン番号は現時点では象徴的な0である。 現在の状態で実行すれば、壊れることは予期してほしい。 とは言うものの、かなり安定しているし、我々の中にはすでに通常の開発環境をsystemdで動かしているものもいる(VMだけと比べて)。 特に、もし、これを我々開発者が使っていないディストリビューションで使えば、どうなるかはわからない。
どこへ向かうのか?
上で説明したような機能は確かにすでに包括的である。 だが、我々は取り掛かったばかりである。 私はビッグマウスは好まないのだが、これは、我々が推し進める方向の短い概要に過ぎない。
我々は少なくともユニットの種類をあと2つは追加したいと思っている。
swap
はスワップデバイスを、すでにマウントを制御しているのと同じ方法で制御するのに使えるものだ。
つまり、デバイスツリーのデバイスの自動的な依存関係により、それらがアクティブになるのと同じような感じだ。
timer
ではcron
と似たような機能を提供したい。
つまり、時間イベントをベースにサービスを開始させる。
ここで対象となっているのは単調な時計(monotonic clock)と経過時間・カレンダーイベントとの双方である(つまり、前者が「最後に実行されてから5時間後に開始」で、後者が「毎週月曜午前5時に開始」といったものだ)。
しかし、より重要なのことに、我々の計画では、systemd
で実験するのは、起動時間の最適化だけではなく、systemdを理想のセッションマネージャーとすること、つまり、gnome-session
やkdeinit
、その他類似のデーモンを置き換える(ないしは可能であれば修正するだけ)ことも目的の1つである。
セッションマネージャーとinitシステムの抱える問題は非常に似通っている。
迅速な起動が必須であり、プロセスのベビーシッターを務めることだ。
したがって、それぞれの使用目的のために同じコードを使うということが自ずと考えとして出てくる。
Appleはこれをわかっていて、launchdでこれをやった。
我々もそうすべきだ。
ソケットとバスのアクティベーションとパラレル化は、セッションサービスとシステムサービスとが平等に恩恵を受けることができるものである。
これらの機能のうち3つすべてがすでに部分的に現在のコードベースで利用できること、しかし、完璧ではないことはおそらく書いておくべきだろう。 例えば、すでに、一般ユーザーとしてsystemdは十分に動き、systemdはそのように動いていることを検出でき、最初から、このモードをサポートし、核心である*18。(これは例外的にデバッグにも便利なのである!これはシステムをsystemdでブートするようにしなくてもうまく動くのである。)
しかし、この仕事を終わらせる前に、カーネルや他の場所で修正すべきことがいくつかあるのである。
我々はスワップのステータス変更の通知をカーネルから受け取るようにする必要がある。これは、すでに我々がマウントの変更を見ることができるようになっているのと同じようなものだ。
CLOCK_REALTIME
がCLOCK_MONOTONIC
に対してジャンプしたときに通知を受け取りたい。
通常のプロセスがinit-likeなパワーの一部を得られるようにしたい。
ユーザーのソケットを置ける、十分に定義された場所が必要だ。
これらの問題点で本当にsystemdにとって必要なものはないが、きっと物事は良くなるだろう。
動いているこれが見たいかい?
現時点では、tarballによるリリースはないが、我々のレポジトリからコードをチェックアウトするのが簡単だろう。 さらに、使い始めようと思えば、ここにあるユニット設定ファイルのtarball*19を使えば、これ以外に変更を加えていないFedora 13のシステムでsystemdを動かすことができる。 今のところ、提供できるようなRPMはない。
より簡単な方法はこのFedora 13のqemuイメージ*20を使う方法である。これはsystemdを動かすように準備ができている。
grubのメニューでシステムをUpstartでブートさせるか、systemdでブートさせるかを選ぶことができる。
このシステムは最小限の修正しかされていないことに注意してほしい。
サーバーの情報は既存のSysV initスクリプトからしか読み出していない。
したがって、ここまでに書いたような、完全なソケットないしはバスによるパラレル化の恩恵は受けていない。
しかし、LSBヘッダーからパラレル化のヒントを解釈し、Upstartのシステムより速く起動させることができる。Upstartは現時点のFedoraではいかなるパラレル化もしていない。
このイメージでは、カーネルログバッファー(これはdmesg
でアクセスできる)に加えて、デバッグ情報はシリアルコンソールに書き出されるように設定されている。
仮想シリアル端末で設定されたqemu
を実行したくなるかもしれない。
すべてのパスワードはsystemd
に設定されている。
qemuイメージをダウンロードしてブートさせるよりもさらに簡単なのは小奇麗なスクリーンショットを見ることだ。
initシステムは通常、ユーザーインターフェイスの下に十分に隠されているため、systemadm
やps
のショットを数枚見せる。
これはsystemadmが読み込まれたすべてのユニットを表示しているところで、gettyインスタンスの1つの詳細な情報を下に表示している。
これはps xaf -eo pid,user,args,cgroup
の出力の抜粋で、プロセスがそれらのサービスのcgroupにどれだけきちんとソートされているかを示している。
(4つ目の列はcgroupでdebug:
という接頭辞が表示されているのは、先に述べたとおrい、我々がsystemd用にデバッグのcgroupコントローラーを使っているからである。これは一時的なものに過ぎない。)
この2枚のスクリーンショットはどちらも、Fedora 13のLive CDインストールを最小限に修正したものであることに注意してほしい。 ここでは、サービスは既存のinitスクリプトからのみロードされている。 したがって、既存のサービスのためにソケットやバスのアクティベーションは使われていない。
残念だが、現時点では、起動時間に関するブートチャートやハードデータはない。 デフォルトのFedoraインストールのすべてのサービスを完全にパラレル化したらすぐに公開するつもりだ。 なので、我々は読者がsystemdのアプローチをベンチマークすることを歓迎するし、我々自身のベンチマークデータを提供することもする。
さて、おそらく、みんな、これが気になってしつこく聞くだろうから、2つの数字を伝えておこう。 だが、これらは完全に非科学的なものだ。 というのも、VM(シングルCPU)で測ったものや、私の時計のストップウォッチで測ったものだからだ。 Feodra 13はUpstartではブートに27秒かかったが、systemdは24秒だった(これは、grubからgdmまでの時間で、同じシステム、同じ設定、2回の起動のうち短い方の値で、このうち1つはもう1つのすぐ後に続いた((Upstartとsystemdとで2回ずつブートさせたが、それぞれの2回はそんなに差がなかったと言いたいのだろう)))。
しかし、これは、initスクリプトからパースされたLSB依存関係情報をパラレル化に利用しただけで到達できたスピードアップ効果以外のことは示していないことに注意してほしい。 ソケットないしはバスによるアクティベーションはこれには利用されていない。 したがって、これらの数字は上で述べたようなアイディアを評価するには不適切なのだ。 また、systemdはデバッグ用の詳細出力をシリアルコンソールに出すように設定されている。 だから、再度となるが、このベンチマークデータはほとんど価値がない。
デーモンを書く
systemdで使われる理想的なデーモンは伝統的に行われてきたことと、数個の違いしかない。 あとで、もっと長いガイドを出して、systemdで使われるデーモンをどう書くべきかを説明、提案するつもりではある。 基本的にはデーモン開発者にとって物事は簡単になる。
デーモンの作成者はプロセスをフォークやダブルフォークをするのではなく、systemdが起動させる最初のプロセスからイベントのループを始めること。 また、
setsid()
は呼び出さないこと。デーモン自身でユーザー特権をドロップさせず、これをsystemdに任せ、systemdのサービス設定ファイルの中で設定すること。 (例外はある。例えば、いくつかのデーモンにとって、特権の昇格を必要とする初期化フェーズのあとで、デーモンコードの中で特権をドロップさせるべき正しい理由があるかもしれない。)
PIDファイルは書き出さないこと。
バス名をつかむこと
ロギングはsystemdに任せることができる。標準エラー出力にログを出す必要があるものは何でもログに出すのは歓迎だ。
systemdにソケットをつくらせ、監視させること。これでソケットアクティベーションが動作する。 それゆえに、上で述べたように
$LISTEN_FDS
や$LISTEN_PID
が解釈される。デーモンをシャットダウンするように要求するにはSIGTERMを使うこと
この上のリストはAppleがlaunchdと互換性のあるデーモンにすすめているものと非常によく似ている*21。 すでにlaunchdアクティベーションをサポートしているデーモンを拡張して、systemdアクティベーションもサポートするようにするのは簡単なはずだ。
systemdはこのスタイルで書かれていないデーモンもすでに完全にサポートする。というのも、互換性の理由からだ(launchdはこれを部分的にしかサポート指定いない)。 すでに述べたとおり、これは、使用可能で、systemdによるソケットアクティベーションに対応させるための修正を加えていない、inetd対応の既存のデーモンにも広げることさえできる。
なので、そうだ、systemdが我々の実験の中で自らを示し、ディストリビューションで採用されれれば、これらのサービスがソケットやバスベースのアクティベーションをデフォルトに起動するようにポートすることも腑に落ちるだろう。 すでに、概念実証のためのパッチを書いており*22、チューンアウトされたポーティングは非常に簡単だ。 また、すでにlaunchdでおこなわれた業績をある程度、利用することもできる。 さらに、ソケットによるアクティベーションのサポートを追加することで、サービスが非systemdのシステムと非互換になるというわけでもないのである。
FAQs
バックに控えているのは何?
えー、現時点でのコードベースはほとんど私、Lennart Poettering (Red Hat)の成果だ。 だが、その詳細のすべてのデザインに関してはKay Sievers (Novell)*23と私の密接な強力の結果である。 他に関わっている人物としては、Harald Hoyer (Red Hat)やDhaval Giani (前IBM)*24、そして、その他、IntelやSUSE、Nokiaといった様々な会社からの数人だ。
これはRed Hatのプロジェクトなのか?
いいえ。これは私の個人的なサブのプロジェクトだ。これも強調させてくれ: ここに書かれている意見は私自身の意見だ。これらは私の雇用者やRonald McDonaldやその他の人の見解ではない。
これはFedoraになるのか?
もし実験でこのアプローチがうまく動くとわかり、Fedoraのコミュニティーでの議論がこれに賛意を示すなら、イエスになるだろう。 実際、これをFedoraに入れようとしている。
OpenSUSEには来るのか?
KayはFedoraがやるのと同じような方法でそうしようとしている。
これはDebian/Gentoo/Mandriva/MeeGo/Ubuntu/(ここにお好みのディストロを入れる)に来るのか?
彼ら次第だ。関心を示してくれるのは実際、歓迎するし、統合の手伝いだってするつもりだ。
どうしてこれをUpstartに追加しなかったのか?なぜ新しいものを発明したのか?
Upstartについて述べた部分で指摘したのは、Upstartの核のデザインには欠点がある、というのが我々の意見だということだ。 もし既存のソリューションが核に欠点を抱えているなら、完全にスクラッチで始めることがそれを示唆することになる。 しかし、我々はUpstartのコードベースその他で多大なインスピレーションを受けたことは注意しなければならない。
そんなにAppleのlaunchdが好きなら、なぜそれを使わなかった?
launchdは偉大なイノベーションだが、それがLinuxに合うとも、Linuxのような、数多くの目的と用途とに対しかなりのスケーラビリティーと柔軟性とを持つシステムに適合的とは思えないからだ。
これはNIHのプロジェクトなのか?*25
えっと、ここまでの文章で、我々がなぜ新しい物に至り、Upstartやlaunchdの上に作ろうとしなかったかを説明できていればいいなと思う。 systemdに落ち着いたのは技術的な理由で政治的理由ではない。
NIHと呼ばれるライブラリ(glibの再実装の一種)を含んでいるのはsystemdではなく、Upstartであることは忘れないでほしいな!*26
これは(お好みの非Linux OSをここに入れる)で動くのか?
多分動かない。 すでに書いたとおり、systemdは多くのLinux固有のAPI(epoll、signalfd、libudev、cgroup、その他もろもろ)を使っており、これを他のオペレーティングシステムにポートするのはそこまで妥当とは思えない。 また、我々、関わった人間は他のプラットフォームにもしかしたらありうるかもしれないポートをマージすることに関心はないし、制限のもとでこれを導入することに従事するつもりもない。 そう入っても、本当にこれをポートしたいと考える人がいれば、gitがブランチやリベースでかなり助けてくれるだろう。
実際、ポータビリティーの限界は他のOSに移す以前の問題である。 我々はかなり最近のLinuxカーネル、glibc、libcgroupおよびlibudevを要求している。 あまり最近ではないLinuxシステムのサポートはない。すまない。
他のオペレーティングシステムに似たようなものを実装したい人たちがいれば、協力の好ましい状態はおそらく、そうしたいシステムとどのインターフェイスが共有できて、systemdとsystemdに対応するものとの双方をサポートするデーモンを書く人の生活をより楽にさせる事で我々が手伝いをする、というものになるだろう。 おそらく、焦点はインターフェイスの共有であって、コードの共有ではないはずだ。
私は(次のうち1つを選んで埋める: Gentooのブートシステムやinitng、Solaris SMF、runit、uxlaunch、…)が素晴らしいinitシステムで、パラレルな起動もおこなうと聞いたんだが、なぜ、それを使わないのか?
さて、これを始める前に、実際、我々は様々なシステムを非常に細かく調べてきたが、どれ一つとしてsystemdとして心に描いたものではなかった(もちろんlaunchdは例外だ)。 わからないようだったら、もう一度、上で書いたことを読み直してほしい。
貢献
パッチやヘルプに大変関心がある。 すべてのフリーソフトウェアプロジェクトは幅広い可能性ある外部の貢献からのみ恩恵を受けられることは広く共有されている感覚である。 これは特にinitシステムのようなOSのコア部分では特に真実である。 我々は皆さんの貢献を評価しており、したがって、著作権の割り当てを要求したりはしない(Canonical/Upstartとは大違いだ!) そして、gitも使っている。みんな大好きなVCSだ。イェイ!
我々は特に、systemdをFedoraやOpenSUSEに加え、他のディストリビューションで動かすための助けを欲している(へい、DebianやGentoo、Mandriva、MeeGoを使っている人で何かやることを探している人はいないか?((Ubuntuが入ってないのが面白い。)))。 しかし、それを抜きにしても、各レベルで貢献者を集めることに必死である。C開発者の人、パッケージャー、ドキュメントを書くのに関心がある人、ロゴを作ってくれる人、歓迎する。
コミュニティ
現時点では、我々にはソースコードレポジトリとIRCチャンネル(#systemd
on Freenode)しかない。
メーリングリストも、ウェブサイトも、バグトラッキングシステムもない。
freedesktop.orgに間もなく何かをセットアップするかもしれない。
何か質問や我々と連絡を取りたい、その他あれば、招待するからIRCに参加してくれ!
*1:編集履歴 17.11.16-01: 誤字を修正 thanks to Kazuhiro NISHIYAMAさん、いくやさん 17.11.14-01: 最後まで翻訳 17.11.13-01: 'Putting it All Together: systemd'の残りを訳出 / 'Status'と'Where is This Going?'を訳出 17.11.12-02: 手元の環境がおかしく、UpstartがUbuntu 17.10でインストール可能と書いていたのを修正 thanks to MurabitoLさん 17.11.12-01: 'A short list of other features' in 'Putting it All Together: systemd'の8までを訳出 17.11.09-01: 'Putting it All Together: systemd'の前半を訳出 17.11.07-02: 'On Upstart' を訳出 17.11.07-01: 'Controlling the Process Execution Environment' を訳出 17.11.06-01: 'Keeping Track of Processes'を訳出 17.11.05-03: 誤字を修正 原文へのリンクを追加 thanks to nekomatuさん 17.11.05-02: 誤字を修正 thanks to いくやさん 17.11.05-01: 'Keeping the First User PID Small'まで訳出
*2:原文のリンクは http://git.0pointer.net/?p=systemd.git どうやら、レナートの個人サイトにgitレポジトリがあったようだ。訳文ではgithubのレポジトリにリンクを張り直した
*3:原文の記述が複雑で、訳文としては適切かはちょっと怪しい。ただ、ここで言いたいことは、1. 他のサービスのソケットにメッセージを送るだけで、返信を必要としないサービスであれば、ソケットのバッファーに貯めておけば、そのサービスは起動を続けられる。2. 他のサービスとの同期(メッセージの送受信)が必要なサービスであれば、メッセージを受け取るだけ受け取っておいて、待たせておけばいい、ということである。
*4:画像があったはずだが、picasa終了に伴いリンク切れ
*5:原文は'crakish'で、「麻薬中毒者」を形容する口語のようである。日本語で相当するのは「ラリった」なのかもしれないが、訳文として書くのが憚られたため、このように訳した。
*6:原文のリンクは切れていたため適切なものに付け替えた。cgroupについて日本語で読めるものに Redhatのドキュメントがあるほか、幸いにして、 LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術:連載|gihyo.jp … 技術評論社 もある。
*7:原文では案内がないが、このへんで一旦コーヒーを入れに行くのが良い。
*8: https://linuxjm.osdn.jp/html/LDP_man-pages/man7/capabilities.7.html
*9:原文は'a great away'とあるが、'a great way'の間違いか。
*10:この一文は自信がない。また、どこにかかるのかも自信がない。当該部分の原文は'especially in times where the kernel log buffer is configured ridiculously large out-of-the-box.'である。ここでは、これが前にある'a very useful replacement for syslog'にかかるものと解釈して訳した。
*11:Ubuntuを後援するCanonicalの社員であったScott James Remnantらが開発した、従来のSysV initの代替実装。2006年にリリースされた。Upstartの特徴(とその限界)についてはレナートが本文で語っているため、省略するが、「Upstartは[...]特に動的なシステムに向けて設計されて」おり、「イベントを発行することで非同期性を取り扱う」「革新的な」ものとして当時、登場した。Ubuntuが6.10で採用したのみならず、REHL6でもinitとして採用されていた。だが、アップストリームにあたるDebianが標準のinitをsystemdに移行したことで、Ubuntuは15.10でinitをsystemdに移行した。なお、Ubuntu 17.10でUpstartはレポジトリから削除された(参考)。UpstartのマニュアルはUpstart Cookbookを参照。このマニュアルを日本語化するプロジェクトの残骸はこちらを参照。
*12:ドミノ倒しをイメージするとよいか。
*13:再度になるが、リンクは張り直している。
*14:https://linuxjm.osdn.jp/html/LDP_man-pages/man7/time.7.html
*15:https://linuxjm.osdn.jp/html/LDP_man-pages/man7/capabilities.7.html
*16:毒にも薬にもならないので、実験段階では好都合位の意で解釈した
*17:原文では'introspectable': introspect「内省する」 + able「〜可能な」ということから、文脈を意識して、このように訳した。
*18:自信がない。原文は'already, you can run systemd just fine as a normal user, and it will detect that is run that way and support for this mode has been available since the very beginning, and is in the very core.'
*19:このリンクはまだ生きているようだ
*20:一応リンクは張ったが、torrentファイルなのでダウンロードは無理だった
*21:原文では、リンクが貼られているがリンク切れ。相当するページはここか。
*22:リンクがあったが切れている
*23:今はRed Hatのようだ。 kaysievers (Kay Sievers) · GitHub
*24:今はOracleのようだ。 Dhaval Giani / Linux Plumbers Conference: Developing the Kernel, Libraries and Utilities
*25:UpstartがCanonical社主導なのに対し、LennartがRed Hatの人間であることを当てこすって
#きょうのsystemd : レナートにブログ翻訳の許可を取り付けた
レナートのブログは面白くて情報の宝庫なので、ちょくちょくチェックしている。
だけど、有名なあの投稿を含め、誰も翻訳をしていないようなので、レナートに「翻訳していいか?」と許可を取ってみた。
"Go Ahead!"と返事が来た。もちろん他にもちょっとだけ書いてあったけど。
とりあえず、例の投稿をやりたいかなぁ。あと、"systemd for Administrators"シリーズを訳して、公式wikiにリンクを貼ってもらおう。
#きょうのsystemd : IPアカウンティングとアクセスリスト
およそ1ヶ月前にリリースされたsystemd 235ではサービスごとにIPトラフィックを計算したり、アクセスコントロールができるようになったらしい。 元ネタはレナートのブログ記事 IP Accounting and Access Lists with systemd 。
本稿では、この記事の詳細を紹介する。
IPアカウンティング
v235以前でも、systemdはすでにユニットごとのリソース管理のフックを様々な種類のリソースに対し提供してきた: 消費されたCPU時間、ディスクI/O、メモリ使用量やタスクの数などである。 v235では別の種類のリソースをsystemdでユニットごとに管理できるようになった: ネットワークトラフィック(特にIP)である。
これに伴い、ユニットに追加された設定項目は次の3つ
IPAccounting=
- booleanを取る。これが有効なユニットでは関連するプロセスによるIPトラフィックの送受信がバイト数およびパケット数でカウントされる。
IPAddressDeny=
,IPAddressAllow=
3つのオプションはLinux 4.11で導入されたカーネルの機能であるcontrol group eBPF フックを薄くラップするものである。 実際の仕事はカーネルにより実行され、systemdはこの機能を設定する新しい設定の多くを提供するだけである。
とのこと。
ブログでは次のようなservice unitを作成し、デモをおこなっている。
[Service] ExecStart=/usr/bin/ping 8.8.8.8 IPAccounting=yes
デモを見る限り、たしかにパケットのin/outがカウントされている。
デモ用サービスを停止するときに
表示された最後の行は興味深く、アカウンティングデータを表示している。 これは実際は構造化されたログメッセージであり、サービスのメタデータのフィールドにはより包括的な生のデータがある。
その行は
Received 49.5K IP traffic, sent 49.5K IP traffic
というもの。
journalctl -u $SERVICE_NAME -n 1 -o verbose
としたら、上記のメッセージが格納されているのが確認できるよ、
MESSAGE_ID
で引っ張れば、このメッセージが取り出せるよ、と書いているが、何が興味深い
のか正直よくわからない。
デモでは停止後にメッセージを取りに行っているが、リアルタイムでも取れるんだろうか。
個人的には、次の部分の方が興味深かった。
systemd-run -p IPAccounting=yes --wait wget https://cfp.all-systems-go.io/en/ASG2017/public/schedule/2.pdf
とすると、一時的なサービスとしてIPアカウンティングを有効にしながらコマンドが実行できるようである。なにそれ面白い。
あと、ちゃっかり、自分が登壇する The user-space Linux technologies conference のPDFを読者にダウンロードさせてるのが面白い。
(ところで、もうチケットは予約したかな?もうそろそろ売り切れちゃうから、急いで!)
じゃねーよw
これの応用編として
systemd-run -p IPAccounting=1 -t /bin/sh
で新しいシェルを起動させて、コマンドを打つとかをやっている。なにそれ面白い。
なお、すでに起動済みのサービスに対し、IPアカウンティングを有効にする方法もあるようで、systemctl
より設定する。
systemctl set-property foobar.service IPAccounting=yes
/etc/systemd/system.conf
にDefaultIPAccounting=
を設定すると、すべてのサービスでIPアカウンティングが有効になる。
IPアクセスリスト
設定されたユニットのパケットの通過の可否は次の順で判断される。
1. IPAddressAllow=
に設定されたIPアドレスとの通信は許可
2. IPAddressDeny=
に設定されたIPアドレスとの通信は拒否
3. いずれにもマッチしない通信は許可
後述するように、TCP wrapperに代わる使い方を想定しており、これに合わせているんだろうなという感想。
こちらもデモをおこなっている。
それぞれのユニットに個別にIPアクセスリストを設定できるだけでも、すでにかなりいい。 とはいえ、典型的にみんながやりたいことは、これを包括的に設定すること、つまり、別個のユニットだけじゃなく、いくつかのユニットをまとめて一気に、あるいは、それだけじゃなく、システムまるごと設定することだろう。 systemdでは
.slice
ユニットを使うことでこれが可能になる。 (systemdをあまり知らない人のために書いておくと、sliceユニットはリソース管理のためにサービスを階層的な木にまとめるための概念のことだ) IPアクセスリストはユニットにとって事実上、ユニット自身に設定された個別のIPアクセスリストの組み合わせであったり、それの含まれているすべてのsliceユニットのそれらであったりするのである*1。
続くデモでは、デフォルトでシステムのサービスが割り当てられるsystem.slice
に対し、IPアクセスリストを設定している。
使用例
レナートはこのIPアクセスリストのユースケースとして、次のような例を挙げている
TCP Wrapperの置換
TCP Wrapperに対するメリットとして、あるサービスのIPソケットすべてに無条件で適用できる点、サービス側のコードに何も入れなくて良い点を挙げている。
逆にデメリットはTCP Wrapperが提供する機能のうち、カバーできてないものがある点を挙げている。特に、DNS名で設定する方法がないことを挙げているが、レナート本人は「でも、正直に言ってしまうと、ネットワーキングを制限するためにネットワーキングをおこなうってやっぱりかなり疑わしい機能だ。少なくとも自分には」と書いている。
IPファイヤーウォールの置換
NetFilter/iptablesに対するメリットとして、サービスという概念が理解できる点、そのため、コンテキストを理解している点を挙げている。
ただし、単純に比較もできないと書いている。 systemdのIPアクセスリストはサービスのことはわかるがそれ以外はわからないが、典型的なファイヤーウォールはピュアなIPに注目している分、用途が広いと述べている。
ディストリ/ベンダー提供のシステムサービスをセキュアにする
ネットワークにつなぐ必要のないサービスはIPAccessDeny=any
(とIPAddressAllow=localhost
)を設定することで、ネットワークに繋がせないようにできる。
セキュアにコマンドを実行
あまり信用できない実行ファイルをsystemd-run
に-p
でIPAccessDeny=
とIPAccessAllow=
とを渡せば、ネットワーク的に安全な状態でコマンドを実行できる。
ノート
socket activationとの組み合わせ
ソケットアクティベーションとの組み合わせが可能である。 ただし、ソケット側でIPアカウンティングを設定した場合、(当たり前だが)ソケット側でカウントされるため、ソケットがサービスに渡されても、カウントがそちらで続いていることに注意が必要。 IPアクセスリストもソケット側とサービス側と別個にカーネルで管理される。そのため、例えば、socketユニットでは比較的オープンなアクセスができるアクセスリストを設定し、serviceユニット側で制限のかかったアクセスリストを設定することで、socketユニットで設定されたソケットをサービスを起動・停止の唯一の方法とすることができる*2。
他のアドレスファミリーとの組み合わせ
IPアカウンティングとアクセスリストはIPソケットのみに適用され、他のアドレスファミリーには適用されない。
つまり、AF_PACKET
*3ソケットはカバーされない。IPアクセスリストとRestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
と組み合わせるとより強固になる。
systemd-runの他のアカウンティング
CPUAccounting=
, IOAccounting=
をつけると、これらのアカウンティングも見れる
オーバーヘッド
ゼロというわけではないが、最新のカーネルではeBPFの実行はすでに最適化されている。今後も最適化が進んで、コストは無視できるようになるはずと述べている。
IPアカウンティングの再帰
現時点では再帰的ではない。つまり、sliceユニットに設定して、その配下のユニットのIPアカウンティングをまとめるといったことはできない。
将来的には実装したいが、カーネル側での実装がまず進む必要がある。
PrivateNetwork との関係
PrivateNetwork=
とIPAccessDeny=any
とは似たような機能で、ユニットがネットワークを使用できないようにすることができる。ただ、前者はLinuxネットワーク名前空間を使って実装されているため包括的である。
#きょうのsystemd : machinectlにブロックデバイスをイメージとして認識させられるように(なるはず)
LennartがnspawnについてMLに投稿していた件について、今朝調べていたら、動きがあったことがわかったので翻訳。 GPTイメージの話も興味深い。
[systemd-devel] systemd-nspawn/machinectl with LUKS/LVM
やぁ、
systemd-nspawnでLUKSで暗号化されたLVを使う正しい方法を探している。
"containername" と名付けたLVを用意していて、これはLUKSで暗号化されている。 で、次のコマンドを使って、コンテナを起動させた。
systemd-nspawn --boot --image=/dev/vg/containername
LUKSのパスフレーズの入力が求められて、コマンドライン上はうまく動いているように見える。
だけど、ちょっと質問がある。
1) パーティションを使わなかったり、ファイルシステムを使わない場合に比べて、単一パーティションのGPTを使うメリットってあるんだろうか?
2) machinectl list-imagesがLVにあるイメージを検出しないんだ。/var/lib/machinesにあるイメージは(auto)mountされるものだと思ってるんだけど?
3) これ(訳注: LVをnspawnのイメージとして起動させること)を起動時に有効にするのはどうするのがベストだろうか? "machinectl enable"は動かなかった。というのも、どのイメージを使うのか認識できないみたいなんだ。イメージから起動するnspawnコンテナのsystemd unitファイルの例ってあるの?
Thanks,
-- M
Lennartの回答。
[systemd-devel] systemd-nspawn/machinectl with LUKS/LVM
(引用省略) (訳注: 1について)
イメージの分析ロジックはどちらも取り扱えるよ。 GPTを使う方法が少しだけ良いと思うね。というのも、rootパーティションをそれとマークすることができるし、そのイメージに適したCPUアーキテクチャの情報も同梱することができる(し、nspawnがそこから--personality= オプションを引っ張ることができる)から。 つまり、この方法を使えば、より多くの情報を見つけることができ、nspawnに適したイメージは簡単にそういうものだと見分けが付くんだ。 そして、もちろん、同じイメージの中に複数のパーティションをもたせることだってできる。 例えば、単一のイメージの中に、読み出し専用のsquashfsな/usrに書き込み可能な/homeを組み合わせたりってことができる。 言い忘れてたけど、GPTを使うことで、KVM(や物理システム)とnspawnとでほとんどおんなじ方法でブートさせられるイメージを作ることができる。
もし、これらの特徴、つまり、見つけやすさやアーキテクチャのサポート、複数パーティション、KVMとの互換性、に関心がないのなら、GPTなしのほうがいいかもね。
(ところで、mkosiを使うと、GPTの特徴の恩恵を受けたイメージを簡単に作ることができるよ。)
(引用省略) (訳注: 2について)
/var/lib/machinesでイメージを利用できないなら、そこにマウントするか、シンボリックリンクを張れば、動くはずだよ。
Lennart
-- Lennart Poettering, Red Hat
でも、動かなかったようで、
[systemd-devel] systemd-nspawn/machinectl with LUKS/LVM
(訳注: 1について)
オーライ、納得したよ。
(引用省略) (訳注: 2について)
LVブロックデバイス(/dev/vg/containername - こいつはGPTを含んでる)へのシンボリックリンクを/var/lib/machinesに作ってみたよ。 "containername"や"containername.raw"って名前でシンボリックリンクを作ってみた。 でも、"machinectl list-images -a"はどうやら、このイメージをどの方法でも見つけられないみたいだ。 ところで、使ってるのはDebian stretchのsystemd 234だ。
/var/lib/machinesでイメージを利用できないなら、そこにマウントするか、シンボリックリンクを張れば、動くはずだよ。
できれば、イメージをマウントするのは避けたいな。GPTパーティションを手動で検出させたり、LUKSを解除したり、などなどは避けたいし、ホストにコンテナのデータを不必要に見せることもしたくないんだ。 でも、どうやら、自分が張ったシンボリックリンクがおかしいようだ。
-- M
Lennartの返答。どうやら、ブロックデバイスは認識できないらしい。
[systemd-devel] systemd-nspawn/machinectl with LUKS/LVM
(引用省略)
あー、ふーむ。目的のものはデバイスファイル?
あぁ、忘れてたけど、イメージはブロックデバイスだったんだ。 ブロックデバイスを使うにはサポートがいくつか足りないんだ。 /var/lib/machinesに入れられるのは、ディレクトリやサブボリューム、rawファイルに今のところ限られていて、ブロックデバイスはサポートしていないんだ。 でも、ブロックデバイスのサポートを追加するのは簡単でもある。 RFE*1バグとしてgithubにこのことを報告してもらえるかな?
Lennart
-- Lennart Poettering, Red Hat
というわけで、RFEがまとめられた。
RFE: allow symlinks to block devices in /var/lib/machines · Issue #6990 · systemd/systemd · GitHub
で、この件について機能強化したのが次のマージリクエスト。
昨日にマージされたみたい。ただ、xenial(amd64)の自動テストでコケてるのが気になるけど、次のリリースまでにはmachinectlがブロックデバイスをイメージとして取れるんじゃないかな。
*1:Request for enhancement: 機能強化要求
#きょうのsystemd : 受動態の target units は何のためにある?
勉強になったので翻訳
(私家訳。誤訳や誤解があれば指摘歓迎。)
[systemd-devel] Why do passive target units have to exist?
やぁ
systemdのドキュメントを読んでいるところなんだけど、受動態(passive)のunitがちょっとこんがらがった。
"network.target"を例に取ると、
"systemd-networkd.service" には "Wants=network.target" と "Before=network.target" が書かれている。これで、"systemd-networkd.service"は事実上、"systemd-networkd.service" と "network.target" との両方を起動させることができるし、 "network.target" を "systemd-networkd.service" がアクティブになろうとする後にアクティブにすることができる。シャットダウンの順序も正しいと思う。つまり、 "network.target" は"systemd-networkd.service" より先に停止させられる。ここまでは全部わかる。
もし、能動態(active)のtarget unitでこれを実現しようとしたらどうなるのだろう? "systemd-network.target" で "WantedBy=network.target" を指定することはできるのだろうか? そうすれば、 "systemd-network.service" を有効にすれば("network.target.wants" ディレクトリにシンボリックリンクがつくられて)、"network.target" を起動させれば、 "systemd-networkd.service" も起動させることができる。これでも、target unit デフォルトの依存関係で "network.target" は "systemd-networkd.service" の後にアクティブになる。シャットダウンの順序の正しいだろう。
自分にわかる唯一の違いは起動させる units の違いだ。つまり、受動態の "network.target" で "systemd-networkd.service" を起動させるか、能動態の "network.target" で "network.target" を起動させるかの違いかなんだ。
能動態の target units に対して、受動態の target units を使うメリットってあるんだろうか?
John
これに対する、Andrei Borzenkovの反応
[systemd-devel] Why do passive target units have to exist?
思うに、systemdがinitスクリプトに頼らざるを得なかった最初期の歴史的アーティファクトなんじゃないかな。networkingを実装しているinitスクリプトは network.target としても知られている $network を提供していた。だけど、もちろん、これはこれ自身をネイティブなsystemd unitに引っ掛けるものじゃなかった。ネイティブな units だけを使っている限りは、実用上の差はないよ。
[systemd-devel] Why do passive target units have to exist?
Johnがいろんな受動態のtargetをリストアップして、これら全部そうなん?と聞き始める。
ここで、Lennart参戦。
[systemd-devel] Why do passive target units have to exist?
違うよ。これら(訳注: Johnの挙げた受動態のtargetたち)はそれ(訳注: 初期のinitスクリプト)とはまったく関係ない。 これらは全部、同期のポイントなんだ。この同期のポイントはブートの遷移の中ではできればない方がいい。でも必要なら制御しないといけないんだ。
もともとの質問である「能動態の target units に対して、受動態の target units を使うメリットってあるんだろうか?」について Lennart。
[systemd-devel] Why do passive target units have to exist?
我々は基本的に同期ポイントは最小限にしようとしているんだ。 "network.target" もその一つで、そうする理由がなければ、そいつには活躍してほしくない。 同期ポイントが多ければ多いほど、ブートがよりシリアルなものになってしまうんだ。
ロジックはこうだ。もし networking スタックがなければ、サービスがこれの前後で同期する理由がなくなる。必要に応じて、 network.target を制御するのはまさに networking スタックであって、その利用者じゃないんだ。
納得できるかな?
Johnの追加の質問
[systemd-devel] Why do passive target units have to exist?
腑に落ちたよ。説明ありがとう。
だけど、まだ疑問がある。もし、基本的なゴールが同期ポイントの最小化にあるんなら、どうして、より多くの能動態の target を受動態のそれに変えないんだ? 例えばこうだ。全部のマシンにスワップがあるわけじゃない。それなら、swap.targetは受動態のtargetに変えて、不必要な同期ポイントを避けることができるんじゃないか? 何か理由があるに違いないと思うんだけど、それが何だかはわからなかった。
Lennartの回答
[systemd-devel] Why do passive target units have to exist?
あぁ、targetsはただの同期ポイントってわけじゃないんだ。こいつらは物事をグループ化するための方法でもあるんだ。 swap.targetはすべてのスワップデバイスを制御する方法であり、local-fs.targetはすべてのローカルファイルシステムを制御するなどなど。
読んでたらごっちゃになってきたが、理屈としてはおそらく、次の通り:
- network.target を必要としているのは systemd-networkd.service であって、その逆では決してない
- 理屈上、Johnの書くとおり、systemd-networkd.service に WantedBy=network.target と書いても動きはするだろう
- ただし、この場合、network.targetがsystemd-networkd.serviceを起動させようとしてしまう
- サービス起動をパラレルにすることを考えると、target unitの依存関係は少ないほうがいい
ふむ