SSD Nodes Learn 🎉 VPS $5.50/月〜
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-13

systemdの歴史と選ばれた理由を解説

SysV initの依存関係とプロセス追跡の限界を、Upstartやlaunchdはどう補おうとしたのでしょうか。4年で各ディストリビューションがsystemdへ移行した経緯と、正しかった反対意見を解説します。

systemd が選ばれた理由

systemd の歴史は、SysV init が実現できなかった2つの機能から始まります。SysV init(System V init。AT&T UnixからLinuxが受け継いだ起動システム)には、サービスが依存する対象を記述する方法がありませんでした。また、サービスの起動後に、どのプロセスがそのサービスに属するかを把握する方法もありませんでした。systemd は、シェルスクリプトでは利用できないカーネル機能によって、この2つの問題に対応しました。プロセスの追跡にはcontrol groupsを、起動順序の制御にはあらかじめ開いておくlistening socketを使用します。そこから、これら2つの解決策がユーザーランドの他の部分へ広がっていきました。そして、反対意見が出始めます。実際、その中には正しい指摘もありました。

SysV init が実際に行っていたこと

SysV システムでは、PID 1(プロセス ID 1。カーネルが最初に起動するプロセス)が /etc/inittab を読み取り、runlevel を選択して、その runlevel 用のスクリプトを実行していました。スクリプトは /etc/init.d/ に置かれていました。/etc/rc3.d/ 内のシンボリックリンクによって、実行するスクリプトと実行順序が決まっていました。そのため、/etc/rc3.d/S20nginx/etc/init.d/nginx を参照し、引数 start を指定して呼び出されました。

#!/bin/sh
### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
### END INIT INFO

case "$1" in
  start)
    start-stop-daemon --start --quiet --pidfile /run/nginx.pid \
      --exec /usr/sbin/nginx
    ;;
  stop)
    start-stop-daemon --stop --quiet --retry TERM/30/KILL/5 \
      --pidfile /run/nginx.pid
    ;;
esac

S20nginx 内の 20 は依存関係ではなく、実行位置を示します。この指定は、このスクリプトが S19 の後、S21 の前に実行されることを示すだけです。実行する理由は示さないため、依存関係を検証できません。また、人が安全だと判断しない限り、関係のない 2 つのスクリプトを安全に同時実行することもできません。

rc プログラムは各スクリプトを順番に実行し、終了するまで待機していました。ネットワークアドレスを待っているスクリプトが 30 秒間ブロックすると、ネットワークを一切使用しないサービスも含め、システム全体の起動が 30 秒間停止しました。

スクリプトの先頭にある LSB(Linux Standard Base)ヘッダーは、この問題を既存の仕組みの内側から修正しようとしたものでした。2011 年の Debian 6.0 では insserv がデフォルトになりました。これは各スクリプトから Required-Start を読み取り、依存関係グラフを作成して、シンボリックリンクの番号を振り直します。これにより Debian は、独立したスクリプトを startpar で同時に実行できるようになりました。これは改善でしたが、より深い問題は解決しませんでした。依存関係は依然として、スクリプトが終了することに基づいていました。S20nginx が 0 を返すことは、シェル関数が戻ったことを意味します。nginx が接続を受け付けていることを意味するわけではありません。

init スクリプトでは解決できなかった5つの問題

  • 並列起動。ファイル名による順序付けは、マシン上のすべてのサービスに対する完全順序です。そのため、起動時間は各処理時間の合計になります。
  • 準備完了の判定。起動スクリプトは、デーモンがリクエストを処理できるようになった時点ではなく、デーモンを fork した時点で終了します。そのため、次のスクリプトが早すぎるタイミングで起動することがよくあります。
  • 監視。デーモンが2回 fork すると、親プロセスは終了します。これによりデーモンは端末から切り離され、PID 1 に再親化されます。init は子プロセスの終了を認識しますが、生き残ったプロセスとの信頼できる関連付けを持てません。
  • オンデマンド起動。inetd(インターネットスーパーサーバー)は、接続を受けたときにデーモンを起動できました。しかし、これは独立したシステムであり、専用の設定ファイルも必要でした。また、起動時に他の処理をどのような順序で実行するかは制御できませんでした。
  • リソース制御。init スクリプトには、サービスのメモリ使用量や CPU 使用率を制限する機能がありませんでした。ulimit は1つのプロセスに適用されるだけで、nice はスケジューラにしか作用しません。そのため、サービスの子プロセスが暴走すると、マシン上の他のプロセスと区別できませんでした。

監視機能の不足は、日常の運用に最も大きな影響を与えました。回避策は PID ファイルでした。デーモンがプロセス ID を /run/nginx.pid に書き込み、stop 関数がそのファイルを読み取っていました。デーモンが強制終了されると、ファイルは残ります。その後、カーネルがその番号を別のプロセスに再利用すると、start-stop-daemon --stop --pidfile はその番号を現在所有しているプロセスにシグナルを送信します。古い PID ファイルが残っていると、init スクリプトは誤ったプロセスを終了させます。

launchd は最初にソケットの問題を解決しました

Apple は 2005 年に Mac OS X 10.4 で launchd をリリースしました。Dave Zarzycki が開発したものです。1 つのプロセスが init、rc、xinetd、crond、watchdogd を置き換えました。

参考にできる考え方は、ソケットアクティベーションです。launchd は最初にすべての待ち受けソケットを作成し、その後でデーモンを起動します。まだ起動していないデーモンにクライアントが接続しても、接続拒否にはなりません。デーモンが accept() を呼び出すまで、カーネルがそのソケットのバックログキューに接続を保持するためです。2 つのデーモン間の起動順序を人が指定する必要もなくなります。ソケットが処理します。

launchd は Mach IPC(プロセス間通信)を基盤として構築されています。Mach IPC は Apple の XNU カーネルに属するもので、Linux に相当する仕組みはありません。そのため、コードを移植するのは現実的ではありませんでした。それでも、この考え方は広まりました。

Upstart はイベントを作業単位にしました

Canonical の Upstart は Scott James Remnant が開発し、2006 年 10 月に Ubuntu 6.10 でリリースされました。Fedora 9 から Fedora 14、RHEL 6、Chrome OS でも使用されました。runlevel をイベントに置き換え、ジョブで起動と停止の契機となるイベントを指定しました。

# /etc/init/example.conf
start on filesystem and net-device-up IFACE!=lo
stop on runlevel [!2345]
respawn
respawn limit 10 5
exec /usr/local/bin/exampled

ジョブの数が増えると、2 つの問題が明らかになりました。1 つ目は依存関係の向きです。ジョブは「このイベントが発生したら自分を起動する」と指定するため、何が何に依存するかという情報が適切でないファイルに置かれます。サービスは自分が必要とするものは把握できますが、翌年に何が自分を必要とするかは把握できません。サービスを追加するたびに、既存のジョブを編集して新しいイベントを発行させることが必要になる場合がありました。

2 つ目はプロセスの追跡です。Upstart は、fork() の呼び出しを ptrace で数えることで、fork するデーモンを追跡しました。これは expect fork または expect daemon として設定します。fork 数を誤ると、Upstart はすでに終了したプロセスを監視したり、すでに発生した fork を待ち続けたりします。エラーを表示せずに initctl start がハングするのが典型的な症状ですが、ジョブファイルからその理由を説明することはできません。

また、Upstart への貢献者には Canonical の contributor agreement への署名が必要でした。これは技術上の欠陥ではありませんが、開発に参加する人々に影響を与えました。

PID 1 の再考、2010 年 4 月

2010 年 4 月 30 日、Lennart Poettering は「PID 1 の再考」という記事を公開しました。Kay Sievers もこのプロジェクトに参加しました。その主張は 4 つの要点から成ります。

  • 起動するものを減らします。多くのサービスは、実際に要求されるまで待機できます。
  • ソケットで順序を示せる場合は、起動順序を個別に宣言しません。すべてのソケットを 1 回の処理で開き、その後ですべてを同時に起動します。
  • PID ファイルではなく、control group でプロセスを追跡します。
  • サービスを宣言的なファイルで記述します。これにより、1 つの記述をすべてのディストリビューションで使用できます。

最初のリリースは同年に公開されました。Fedora 14 は 2010 年 11 月に systemd を選択肢としてリリースに含め、Fedora 15 は 2011 年 5 月に systemd をデフォルトにしました。

監視を信頼できるものにした cgroups

cgroup(control group)はプロセスをグループ化するカーネル機能で、2008 年に Linux 2.6.24 に統合されました。systemd は各サービスを専用の cgroup に配置します。子プロセスは親プロセスの cgroup を継承し、権限のないプロセスは自分自身を cgroup の外へ移動できません。したがって、二重 fork を使ってもプロセスを隠せません。PID 1 は常に、unit に属するプロセスの正確な集合を保持します。サービスを停止するとは、その cgroup 内のすべてのプロセスを kill することです。これがデフォルトの KillMode=control-group の動作です。

systemctl status はそのグループを表示します。

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-08-11 09:14:22 UTC; 3min ago
   Main PID: 1042 (nginx)
      Tasks: 3 (limit: 4653)
     Memory: 6.1M (peak: 7.4M)
     CGroup: /system.slice/nginx.service
             ├─1042 "nginx: master process /usr/sbin/nginx"
             ├─1043 "nginx: worker process"
             └─1044 "nginx: worker process"

この出力だけで、古い PID ファイルの問題を完全に説明できます。リストはカーネルの状態であるため、古くなるファイル自体が存在しないからです。

同じツリーでリソース制限も管理できます。cgroups は、プロセス追跡に使われる前から、アカウンティング用に設計されていたためです。MemoryMax=CPUQuota=TasksMax= は、それぞれ 1 行で設定できます。サービスにメモリと CPU の上限を設定することは、現在では drop-in ファイルを 1 つ配置するだけで実現できます。2009 年には、誰も作成しなかった shell script への patch が必要でした。

2011 年から 2015 年にかけて、すべてのディストリビューションが移行した理由

  • Fedora 15、2011 年 5 月。
  • openSUSE 12.1、2011 年 11 月。
  • Mageia 2、2012 年 5 月。
  • Arch Linux、新規インストールのデフォルトが 2012 年 10 月から変更。
  • RHEL 7、2014 年 6 月。
  • SLES 12、2014 年 10 月。
  • Debian 8、2015 年 4 月。
  • Ubuntu 15.04、2015 年 4 月。

移行の理由は、ほとんどが単純なものでした。そのため、移行は速く進みました。

  • 1 つの unit file がすべてのディストリビューションで動作するため、upstream プロジェクトは .service file の配布を始めました。ディストリビューション側も、リリースごとにパッケージごとの shell script を保守する必要がなくなりました。
  • ConsoleKit の保守が 2012 年ごろに終了した後、デスクトップセッションの追跡は systemd-logind に移行しました。GNOME には logind が必要だったため、systemd を採用しないディストリビューションは代替手段を用意する必要がありました。その代替が elogind です。これは systemd の logind を分離し、個別に保守するものです。
  • デバイスマネージャーである udev は、2012 年 4 月に systemd の source tree へ統合されました。udev を配布するディストリビューションは、systemd の repository を追跡することになりました。これを受けて Gentoo は eudev を fork しました。
  • コンテナでは、プロセス追跡とサービスごとの制限がより重要になりました。どちらも cgroup の機能だからです。どの supervisor がコンテナのプロセスを管理するかという問題は、Docker Compose の stack を再起動後に復帰させる場合にも、現在まで残っています。

Debian の決定が最も大きな注目を集めました。Technical Committee は 2014 年 2 月に投票を行いましたが、票が同数になり、議長の Bdale Garbee が systemd を支持する決定票を投じました。数日後、Ubuntu は Upstart を継続せず、Debian に従うと発表しました。Debian の開発者グループは 2014 年 11 月にディストリビューションを fork して Devuan とし、2017 年 5 月に Devuan 1.0 をリリースしました。

公平に述べた異論

範囲。 1 つのプロジェクトが、PID 1、ログデーモン、ログインセッション管理、デバイスマネージャー、ネットワーク設定デーモン、DNS (domain name system) リゾルバー、NTP (network time protocol) クライアント、コンテナランナー、ブートローダーを提供するようになりました。これらは別々のバイナリであり、必ずしもインストールする必要はないという通常の反論は正しいものの、異論への答えにはなっていません。デスクトップ環境が logind を必要とし、logind が systemd のツリーから切り離されてリリースされると、選択の自由は失われます。議論でいう結合とはそのことであり、実際に起きたことです。

バイナリジャーナル。 journald はプレーンテキストではなく、インデックス付きのバイナリ形式に書き込みます。テキストでは得られなかった機能を利用できます。ユニット単位および優先度単位のフィルタリング、構造化フィールド、送信元プログラムが偽装できないメタデータです。journald 自身がユニットと cgroup を記録するためです。journalctl -u nginx -p err --since "-1h" は grep を日付の正規表現に置き換えます。代償もあります。起動しないマシンでは、レスキューシェルから less でログを読めません。代わりに、マウントしたディスクを journalctl に指定します。

sudo journalctl --directory /mnt/var/log/journal --boot -1 --priority err

ここには、1 度はまる別の落とし穴もあります。journald はログを /run/log/journal に保持します。これはメモリ上であり、/var/log/journal が存在する場合を除きます。存在しないマシンでは、再起動後に journalctl -b -1 で表示できるものがありません。まさにログが必要になる時点で、ログが失われます。確認して修正します。

journalctl --disk-usage
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald

journalctl --disk-usage は、/var/log/journal にアーカイブ済みジャーナルがあることを報告するはずです。プレーンテキストも必要な場合は、/etc/systemd/journald.confForwardToSyslog=yes を設定し、rsyslog をインストールしたままにします。

ブートのデバッグ性。 ユニットが停止すると、コンソールには 1 行だけが表示され、それ以上の情報はありません。

[  *** ] A start job is running for Wait for Network to be Configured (1min 32s / no limit)

さらに調査するためのツールはあります。停止中は systemctl list-jobs、停止後は systemd-analyze blamesystemd-analyze critical-chain、カーネルコマンドラインでは systemd.log_level=debug を使えます。この異論を公平に述べると、sh を知っている人なら init スクリプトを先頭から末尾まで読めました。一方、停止したユニットを調べるには、数多くあるコマンドのどれを使うべきかを知る必要があります。これは現実の負担です。管理者ごとに 1 度発生し、多数の管理者が同じ時期にその負担を負いました。

全員に適用されるデフォルトの変更。 2016 年の systemd 230 で、ログアウト時に残ったユーザープロセスを kill するよう logind のデフォルトが変更されました。切り離された tmuxscreen のセッションは、それらを開始したセッションの終了時に停止しました。ディストリビューションは /etc/systemd/logind.confKillUserProcesses=no を出荷し、サポートされる回答は loginctl enable-linger <user> です。1 つのプロジェクトにある 1 つのデフォルトが、数百万人の依存していた習慣を変えました。これが、「ユーザーランドの機能を 1 か所に集めすぎる」ことの実際の意味です。

デフォルトの依存関係はセキュリティ面になる。 2024 年 3 月、xz-utils のバックドアは Debian と Ubuntu の sshd を標的にしました。upstream の OpenSSH は libsystemd にリンクしていません。これらのディストリビューションは、sshd が systemd に準備完了を通知できるようパッチを適用し、その結果 libsystemd が、バックドアの存在した liblzma を引き込みました。準備完了プロトコル自体は、$NOTIFY_SOCKET に指定されたソケットへ送信する単一のデータグラムです。そのため、ライブラリはもともと必要ありませんでした。systemd は dlopen で圧縮ライブラリを読み込むように変更し、デフォルトでリンクされないようにしました。関連する種類のバグにも同じ構図があります。2017 年には、数字で始まる User= の値が無効として扱われ、失敗する代わりにユニットが root として実行されました。そのため、タイプミスが権限昇格になりました。後のバージョンでは、ユニットの起動を拒否します。

自分の systemctl プロンプトで歴史を確認する

上記の各問題は、今では読めるファイル内の 1 つのディレクティブになっています。

  • 直列起動は After=Wants= になり、systemd-analyze critical-chain でブートを実際に待たせた対象を確認できます。
  • 準備完了状態は Type=notify になり、サービスは要求を処理できる状態になると READY=1$NOTIFY_SOCKET に書き込みます。古いデーモン向けに Type=forkingPIDFile= も残っており、PID ファイルが現れない場合は start operation timed out. Terminating. で失敗するタイプです。
  • 監視は cgroup が担うため、RestartSec= を指定した Restart=on-failure でラッパースクリプトを置き換えられ、StartLimitBurst= によりクラッシュループが無限に実行されるのを防げます。
  • inetd は .service unit の隣にある .socket unit になりました。
  • ulimitMemoryMax=CPUQuota=TasksMax= になりました。
  • init スクリプトの su - appuser -c 行は User=NoNewPrivileges=yesProtectSystem=strict になりました。そのため、権限のないユーザーでサービスを実行することが unit の基本形であり、追加作業ではありません。
[Unit]
Description=Example API
Wants=network-online.target
After=network-online.target postgresql.service
Requires=postgresql.service

[Service]
Type=notify
ExecStart=/usr/local/bin/exampled
Restart=on-failure
RestartSec=2
MemoryMax=512M
CPUQuota=50%
TasksMax=128
User=exampled
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
StateDirectory=exampled

[Install]
WantedBy=multi-user.target

このファイルの 1 行には、誰もが 1 度は犯す間違いがあります。Requires=postgresql.service は要件であり、起動順序ではありません。これは Postgres が失敗すると unit も失敗することを示しますが、Postgres を先に起動する指定ではありません。After=postgresql.service がなければ両方が同時に起動し、サービスはまだ何も待ち受けていないポートへ接続しようとします。この 2 つが分かれているのは意図的です。片方だけが必要になる場合があるためです。ProtectSystem=strict はこのサービスから見えるファイルシステムを読み取り専用でマウントします。そのため StateDirectory= が指定されています。/var/lib 以下に、サービスが書き込めるパスを 1 つ与えるためです。

2026 年のサーバーで 2005 年の仕組みを最も分かりやすく確認できるのは、Ubuntu 24.04 の SSH です。2026 年 8 月時点で、Ubuntu 24.04 には systemd 255 が含まれています。ssh.service はデフォルトで socket activation を使用します。ssh.socket が待ち受けソケットを保持し、接続が到着すると sshd が起動します。そのため、/etc/ssh/sshd_configPort 2222 は効果がありません。ポートを開いたプロセスは sshd ではないためです。変更する場所は socket unit です。

sudo systemctl edit ssh.socket
[Socket]
ListenStream=
ListenStream=2222

空の ListenStream= は、パッケージが提供する unit から継承した値を消去します。これを省略すると両方のポートが有効になります。systemd はリストを置き換えず、値を追加するためです。その後、2 つ目の SSH セッションを開いたまま、適用して確認します。

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -lntp | grep 2222

ss には、sshd ではなく systemd が所有する port 2222 のソケットが 1 つだけ表示されるはずです。これは 20 年後の VPS における launchd の設計です。以前の動作に戻す場合は、sudo systemctl disable --now ssh.socket に続けて sudo systemctl enable --now ssh.service を実行します。これにより、長時間実行される sshd が起動し、独自の設定から Port を再び読み込みます。

これらの詳細のどれに遭遇するかは、実行している release によって異なります。そのため、アップグレードを計画する前に、LTS と interim Ubuntu release の違いを理解しておく価値があります。複数のマシンで運用する場合、どこでも unit file が同一であることが、複数のサーバーを 1 か所から管理することがシェルスクリプトの問題ではなく、設定の問題になった理由です。また、自分で unit を作成するときは、service と timer の組み合わせによって、2009 年に init スクリプトと cron の 1 行に分けていた処理を実現できます。

FAQ

Linux ディストリビューションはなぜ SysV init を systemd に置き換えたのですか?

技術上の理由が 2 つと、保守上の理由が 1 つあります。SysV init はファイル名の順序でサービスを起動していましたが、これは依存関係ではなく位置に基づく順序です。また、親プロセスから fork して離れた daemon を追跡できなかったため、古い PID ファイルによって誤ったプロセスを終了させることがありました。systemd は socket activation と依存関係ディレクティブで起動順序を解決し、control group でプロセスの追跡を解決しました。保守上の理由が移行の速度を決めました。1 つの unit ファイルがすべてのディストリビューションで機能するため、上流プロジェクトは .service ファイルを提供するようになり、ディストリビューションのメンテナーはパッケージごとに shell script を作成しなくなりました。Fedora 15 は 2011 年 5 月に移行し、Ubuntu 15.04 は 2015 年 4 月まで残った最後の主要ディストリビューションでした。

systemd は 1 つの巨大なバイナリですか?

いいえ。ソースツリーからは、多数の独立したプログラムがビルドされます。PID 1 は /usr/lib/systemd/systemd です。一方、journald、logind、udevd はそれぞれ独自のバイナリを持つ独立したプロセスです。自分のシステムで ls /usr/lib/systemd/ を実行すると確認できます。現在も残る批判は、バイナリのサイズではなくリリースの結合に関するものです。これらのプログラムは一緒にリリースされ、非公開のインターフェースを共有しています。そのため、ディストリビューションは一式として取り込む傾向があります。また、GNOME などのソフトウェアは、logind を明示的に要求するようになりました。

systemd なしで Linux を実行することはできますか?

はい。Devuan は sysvinit を提供し、Gentoo は OpenRC をデフォルトにし、Void は runit を使用し、Alpine は OpenRC と組み合わせた busybox init を使用しています。Slackware は BSD 形式の script を維持しています。代わりに、互換性を確保する作業が必要です。logind を必要とするデスクトップソフトウェアには elogind が必要です。これは systemd の logind を standalone package として保守するものです。また、現在は .service ファイルだけを提供するサーバーソフトウェアも増えています。その場合は、起動 script を自分で作成して保守します。

journal はなぜ通常のテキストファイルではなくバイナリなのですか?

journald は index 付きの構造化フィールドを保存するためです。これにより、unit 単位のフィルタリング、priority によるフィルタリング、送信元プログラムが偽装できないメタデータを利用できます。journald はログ行を信頼するのではなく、unit、cgroup、実際の UID を自分で記録します。その代わり、救出用システムから読み取る場合も含め、読み取りには journalctl が必要です。救出用システムでは、マウントしたディスクを journalctl --directory /mnt/var/log/journal で指定します。テキストも必要な場合は、/etc/systemd/journald.confForwardToSyslog=yes を設定します。

/etc/init.d の script を直接編集する方法に代わるものは何ですか?

Drop-in ファイルです。/usr/lib/systemd/system/ にある unit を直接編集しないでください。パッケージの更新によって上書きされるためです。sudo systemctl edit nginx.service を実行すると、systemd は /etc/systemd/system/nginx.service.d/override.conf を作成し、パッケージが提供する unit に重ねて適用します。systemctl cat nginx.service で統合後の結果を表示し、systemd-delta でシステム上のすべての override を一覧表示します。手動で編集した後は必ず sudo systemctl daemon-reload を実行してください。実行しないと、次のコマンドで Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk. と表示されます。

#systemd#linux#init#sysvinit#history