第4章 システム起動のカスタマイズ(systemd詳細)

LPIC-2 202.1 相当

中級の第1章ではsystemdターゲットの基礎を扱いました。ここでは、実際にサービスを定義する「ユニットファイル」の中身と、応用的な操作を学びます。systemdはサービス起動だけでなく、マウント・デバイス・タイマー・ソケットなど多様な対象を統一的な「ユニット」という概念で管理しており、LPIC-2の201試験でも重点的に問われる分野です。

ユニットファイルの種類

ユニットファイルは拡張子によって種類が区別されます。サービス管理でよく登場する主な種類は次のとおりです。

拡張子管理対象
.serviceデーモンなどのサービスプロセス
.mountファイルシステムのマウントポイント(/etc/fstabからも自動生成される)
.socketソケット(TCP/UDPポートやUnixドメインソケット)。アクセスがあった時点でサービスを起動するソケットアクティベーションに使う
.timercron的な定期実行のスケジュール定義
.target複数のユニットをまとめるグループ(旧来のランレベルに近い概念)

ユニットファイルの構造

systemdが管理する対象(サービス、マウント、タイマーなど)は「ユニット」という単位で定義されます。サービスの場合は .service ファイルとして /etc/systemd/system/ や /usr/lib/systemd/system/ に置かれます。

/etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target

[Service]
ExecStart=/usr/local/bin/myapp
Restart=on-failure
User=myapp

[Install]
WantedBy=multi-user.target
セクション役割
[Unit]説明や、起動順序の依存関係(After/Requiresなど)
[Service]実際の起動コマンド、再起動ポリシーなど
[Install]enable時にどのターゲットに紐づけるか

[Service]セクションでよく使われる項目もあわせて押さえておきましょう。Type=simple(既定値)はExecStartのプロセスがそのままメインプロセスになる単純な起動、Type=forkingは起動後に自分自身をデーモン化(フォーク)して親プロセスが終了するタイプの伝統的なデーモン向け、Type=oneshotは一度だけ実行して終了するスクリプトのようなユニット向けです。Restart=on-failureのように再起動ポリシーを指定しておくと、プロセスが異常終了した際にsystemdが自動的に再起動を試みます。

systemctl の応用操作

$ sudo systemctl daemon-reload         # ユニットファイル変更後に必須(正常時は無出力)
$ sudo systemctl enable --now myapp     # 有効化 + 即時起動を同時に行う
Created symlink /etc/systemd/system/multi-user.target.wants/myapp.service → /etc/systemd/system/myapp.service.
$ systemctl status myapp                # 状態を確認
● myapp.service - My Application
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
     Active: active (running) since Fri 2026-08-21 09:00:12 JST; 32min ago
   Main PID: 4021 (myapp)
      Tasks: 4 (limit: 4617)
     Memory: 18.4M
$ systemctl list-units --type=service --state=failed  # 失敗したサービスの一覧
UNIT               LOAD   ACTIVE SUB    DESCRIPTION
● backup.service    loaded failed failed Run backup daily
$ systemctl cat myapp                   # 適用中のユニットファイルの内容(ドロップインを含む)をそのまま表示
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target

[Service]
ExecStart=/usr/local/bin/myapp
$ systemctl show myapp -p Restart       # 特定のプロパティ値だけを取得
Restart=on-failure
$ systemctl is-active myapp; systemctl is-enabled myapp  # スクリプトから状態判定する際に便利
active
enabled

この行のこの値に注目: enable --now実行時のCreated symlink ...行は、自動起動用のシンボリックリンクが実際に作成されたことを示します。list-units --state=failedの一覧に何か表示された場合、先頭の●マークが赤色(環境によって色付き表示)で失敗を示しており、原因調査にはjournalctl -u サービス名が次の一手になります。is-active/is-enabledはシェルスクリプトの条件分岐で使いやすいよう、active/enabledなどの短い1語だけを返します。

enableとstartの違いを混同しないようにしましょう。enableは次回起動時に自動起動する設定(シンボリックリンクの作成)のみを行い、即座にサービスは起動しません。逆にstartは今すぐ起動するだけで、再起動後の自動起動は設定されません。両方を同時に行いたい場合に--nowオプションが便利です。

サービスが起動に失敗した場合、原因調査にはjournalctlが欠かせません。systemdはサービスの標準出力・標準エラー出力を自動的にjournalに取り込むため、専用のログファイルを個別に用意しなくてもログを追跡できます。

$ journalctl -u myapp                  # myappサービスのログのみを表示
Aug 21 09:00:12 web01 systemd[1]: Started myapp.service - My Application.
Aug 21 09:00:12 web01 myapp[4021]: listening on 0.0.0.0:8080
$ journalctl -u myapp -b                # 今回の起動(boot)以降のログに絞る
-- Boot 8f6a2e1c9e6a4b1c9c3d2e1f0a9b8c7d --
Aug 21 09:00:12 web01 systemd[1]: Started myapp.service - My Application.
$ journalctl -u myapp -f                # tail -fのようにログをリアルタイムで追跡
Aug 21 09:32:03 web01 myapp[4021]: request GET /health 200
(新着ログが記録されるたびに追記され続ける。Ctrl+Cで終了)
$ journalctl -u myapp -p err             # エラー以上の重要度のログだけを表示
Aug 20 22:14:01 web01 myapp[3987]: FATAL: cannot bind to port 8080: address already in use

この行のこの値に注目: -u myappだけだとそのユニットの全履歴が対象ですが、-bを付けると今回起動分だけに絞れ、再起動前後の切り分けがしやすくなります。-p errの出力に見覚えのあるエラー(この例ではaddress already in use)があれば、ポート競合が原因だと即座に特定できます。

ベンダー提供のユニットを直接編集しない: /usr/lib/systemd/system/ 以下のユニットファイルはパッケージ更新で上書きされます。設定を一部だけ変更したい場合は sudo systemctl edit myapp を使うと、/etc/systemd/system/myapp.service.d/override.conf というドロップインファイルが自動生成され、元のユニットを上書きせずに差分だけを適用できます。

systemdタイマー — cronの代替

中級の第13章ではcronによる定期実行を扱いましたが、systemdには、cronのように定期的にサービスを起動する.timerユニットという独自のタイマー機構もあります。cronの平文の設定に比べ、依存関係の指定や、前回実行に失敗した場合の扱いなど、より柔軟な制御が可能です。実行対象のサービスに対して、いつ起動するかを別ファイル(.timer)で定義する2ファイル構成が基本で、同名の.serviceユニット(下の例ではbackup.service)に実際の処理内容を記述し、タイマー側から呼び出します。

/etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true を指定すると、シャットダウンなどで実行タイミングを逃した場合に、次回起動時にすぐ実行してくれます。これはcronにはない利点で、ノートPCのように常時起動していない環境や、メンテナンスで頻繁に再起動するサーバーでは特に有用です。

$ sudo systemctl enable --now backup.timer
Created symlink /etc/systemd/system/timers.target.wants/backup.timer → /etc/systemd/system/backup.timer.
$ systemctl list-timers               # 登録されているタイマーと次回実行予定時刻を一覧表示
NEXT                        LEFT     LAST                         PASSED    UNIT           ACTIVATES
Sat 2026-08-22 02:00:00 JST 16h left Fri 2026-08-21 02:00:00 JST  7h ago    backup.timer   backup.service

この行のこの値に注目: list-timersのNEXT/LEFT列で次回実行までの残り時間が、LAST/PASSED列で前回実行からの経過時間が分かります。ACTIVATES列が、そのタイマーが実際に起動する対象の.serviceユニット名です。

OnCalendarは、cronのフィールド指定よりも柔軟な書式を持っています。*-*-* 02:00:00は「毎日2時」、Mon..Fri 09:00:00は「平日の9時」、*-*-01 00:00:00は「毎月1日の0時」を意味し、dailyのように「毎日0時」を表すショートハンドのマクロも用意されています。cronの5フィールド形式に慣れている場合は読み替えに戸惑うことがありますが、systemd-analyze calendarコマンドを使うと、指定した書式が実際にどう解釈され、次回いつ実行されるかを事前に確認できます。

$ systemd-analyze calendar "Mon..Fri 09:00:00"
  Original form: Mon..Fri 09:00:00
Normalized form: Mon..Fri *-*-* 09:00:00
    Next elapse: Mon 2026-08-24 09:00:00 JST
       (in UTC): Mon 2026-08-24 00:00:00 UTC
       From now: 2 days left

この行のこの値に注目: Next elapse行が、この書式で次に実行される実際の日時です。書き間違いに気づかずに本番のタイマーへ設定してしまう前に、このコマンドで意図した日時になっているかを必ず確認します。

ユニットの依存関係とマスク

あるサービスが正しく起動しない場合、依存する他のユニットに問題がないかを確認することが多くあります。

$ systemctl list-dependencies myapp     # 依存関係をツリー表示
myapp.service
● ├─network.target
● └─system.slice
$ sudo systemctl mask bluetooth        # /dev/nullへのシンボリックリンクとして完全に起動不能にする
Created symlink /etc/systemd/system/bluetooth.service → /dev/null.
$ sudo systemctl unmask bluetooth       # 正常時は無出力(マスク用シンボリックリンクが削除される)

この行のこの値に注目: mask実行時の→ /dev/nullという表記が、そのサービスへのあらゆる起動要求が実質的に無効化されたことを示します。disableとの違いはここにあり、他のユニットが依存関係経由で起動しようとしても、maskされたユニットは起動できません。

disable は自動起動の設定を外すだけで手動起動は可能ですが、mask は依存関係経由の起動も含めて完全にブロックする点が異なります。

起動時間の分析

systemd-analyze を使うと、起動プロセス全体や個々のサービスがどれだけ時間を要しているかを分析できます。

$ systemd-analyze              # 全体の起動時間
Startup finished in 3.912s (kernel) + 8.204s (userspace) = 12.116s
$ systemd-analyze blame        # サービスごとの起動時間を降順で表示
          4.021s NetworkManager-wait-online.service
          1.887s snapd.service
          0.532s systemd-udev-settle.service
$ systemd-analyze critical-chain  # 起動完了までのクリティカルパス(直列に依存している経路)を表示
graphical.target @8.204s
└─multi-user.target @8.202s
  └─myapp.service @4.180s +12ms
    └─network.target @4.175s
      └─NetworkManager-wait-online.service @158ms +4.021s

この行のこの値に注目: 各行の@以降が「そのユニットが開始された時刻」、+以降が「そのユニット自身の所要時間」です。この例ではNetworkManager-wait-online.serviceの+4.021sが直列パス上で最も大きく、これが起動時間全体を押し上げている実際のボトルネックだと分かります。

blameはあくまで各ユニットが個別にどれだけ時間を消費したかの一覧であり、必ずしも起動全体の遅延に直結するとは限りません。並列に起動しているユニットは互いの起動時間を隠す(オーバーラップする)ため、「起動を最も遅らせている本当のボトルネック」を特定するには、直列的な依存関係のみをたどるcritical-chainのほうが適しています。NetworkManager-wait-online.serviceのようにネットワーク到達を待つユニットは、起動時間全体を押し上げる典型的な要因としてよく知られています。

レスキューモードと緊急モード

ターゲット用途
rescue.targetルートファイルシステムをマウントした状態でのシングルユーザーモード。基本的なトラブルシューティング向け
emergency.target最小限のマウントのみで起動する、より深刻な障害対応向けのモード

これらのモードには、ブート時にGRUBのカーネル行末尾にsystemd.unit=rescue.target(または旧来の慣習に合わせて単に1やsingle)を追記して起動することで入ることができます。パスワードを忘れてログインできなくなった場合のroot権限復旧など、通常のログイン経路が使えない緊急時の代表的な対処法として、LPIC-2でもよく出題されます。

旧ランレベルとの対応: SysV initの時代のランレベル(0〜6)に慣れている場合は、runlevel3.targetがmulti-user.target、runlevel5.targetがgraphical.targetのシンボリックリンクとして対応付けられていることを覚えておくと理解しやすくなります。互換性のためにsystemctl isolate multi-user.targetのような操作は、旧来のinit 3相当の効果を持ちます。

ソケットアクティベーション

systemdには、サービスを常時起動しておくのではなく、実際に接続要求が来た瞬間に起動する「ソケットアクティベーション」という仕組みもあります。.socketユニットがあらかじめポートを監視しておき、最初の接続が来たタイミングで対応する.serviceユニットを起動します。これにより、めったにアクセスされないサービスのために常時メモリを消費させずに済み、システム全体の起動時間も短縮できます(多くのサービスを並行して起動を待たせる必要がなくなるため)。sshdなど一部のサービスは、環境によってはこの方式で運用されることがあります。

確認クイズ

Q1. systemdのユニットファイルで、実際の起動コマンドを指定するセクションはどれですか?

解説: [Service]セクションにExecStartなど、実際にどう起動するかを記述します。

Q2. ユニットファイルを編集した後、変更をsystemdに認識させるために実行する必要があるコマンドはどれですか?

解説: ユニットファイルを新規作成・編集した際は、systemctl daemon-reloadを実行してsystemdに変更を読み込ませる必要があります。

Q3. 各サービスの起動にかかった時間を降順で確認できるコマンドはどれですか?

解説: systemd-analyze blame は、起動時に時間がかかっているサービスを特定するのに役立ちます。

Q4. ルートファイルシステムをマウントした状態で、基本的なトラブルシューティングを行うためのターゲットはどれですか?

解説: rescue.targetは、ルートファイルシステムをマウントした状態でのシングルユーザーモードで、基本的なトラブルシューティングに使われます。より深刻な場合はemergency.targetを使います。