32blogby Studio Mitsu

Yocto systemd自動起動の設定と落とし穴7選

Yoctoでsystemdサービスを追加・自動起動する方法を、落とし穴7選とともにScarthgap 5.0 LTSベースで解説。

by omitsu14 min read

当記事にはアフィリエイト広告が含まれています

目次

Yoctoでサービスを自動起動するには、ディストロ設定で INIT_MANAGER = "systemd" を指定し、systemd bbclassを継承したレシピを書き、サービスファイルの[Install]セクションに WantedBy=multi-user.target を含める。この3つのどれかが欠けると、サービスは静かに起動しない。

だが正しく動かすには、YoctoのINIT_MANAGER変数とDISTRO_FEATURESの関係、組み込みターゲット向けのサービスファイルの書き方、そして経験者でもハマる落とし穴を理解する必要がある。

この記事では、Yocto Scarthgap 5.0 LTS(systemd 255)をベースに、systemdサービスの追加方法から落とし穴7つまでを解説する。

INIT_MANAGERでsystemdを有効にする

Yoctoのデフォルトのinitシステムはsysvinitだ。systemdを使うには、明示的に切り替える必要がある。

INIT_MANAGER方式(Zeus 3.0以降)

conf/local.conf または conf/distro/mydistro.conf に1行追加するだけでいい。

bash
# conf/local.conf
INIT_MANAGER = "systemd"

この1行で init-manager-systemd.inc が読み込まれ、以下が自動設定される。

bash
# init-manager-systemd.inc の内容(Scarthgapブランチ)
DISTRO_FEATURES:append = " systemd usrmerge"
DISTRO_FEATURES_BACKFILL_CONSIDERED:append = " sysvinit"
VIRTUAL-RUNTIME_init_manager ??= "systemd"
VIRTUAL-RUNTIME_initscripts ??= "systemd-compat-units"
VIRTUAL-RUNTIME_login_manager ??= "shadow-base"
VIRTUAL-RUNTIME_dev_manager ??= "systemd"
ROOT_HOME ?= "/root"

旧方式(Zeus以前)

Zeus以前は、DISTRO_FEATURESVIRTUAL-RUNTIME_* を手動で local.conf に書く必要があった。今でも動くが、INIT_MANAGER 1行で済む方式が簡潔だ。

設定の確認

bash
# systemdがDISTRO_FEATURESに含まれているか確認
bitbake-getvar DISTRO_FEATURES | grep systemd

systemdサービスレシピの書き方

systemdサービスをYoctoで追加するには、systemd bbclassを継承したレシピを書く。

完全なレシピ例

bash
# meta-mylayer/recipes-apps/myapp/myapp_1.0.bb
SUMMARY = "My custom application"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://myapp.sh \
           file://myapp.service"

S = "${WORKDIR}"

inherit systemd

SYSTEMD_SERVICE:${PN} = "myapp.service"
SYSTEMD_AUTO_ENABLE:${PN} = "enable"

do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/myapp.sh ${D}${bindir}/myapp

    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
}

FILES:${PN} += "${systemd_system_unitdir}/myapp.service"

主要変数の解説

変数役割デフォルト
SYSTEMD_SERVICE:${PN}管理するサービスファイル名${PN}.service
SYSTEMD_AUTO_ENABLE:${PN}自動有効化するかenable
SYSTEMD_PACKAGESsystemd管理下に置くパッケージ${PN}

inherit systemd が自動的に行うこと:

  • systemctl enable を実行する postinst スクリプトの生成
  • multi-user.target.wants/ へのシンボリックリンク作成
  • パッケージ削除時の prerm スクリプト生成

レシピをsystemd対応にガードする

systemdが無効なディストロでビルドエラーを防ぐには、REQUIRED_DISTRO_FEATURES を使う。

bash
REQUIRED_DISTRO_FEATURES = "systemd"

この1行を追加すると、DISTRO_FEATURESsystemd が含まれていない環境ではレシピがスキップされる。他のレシピがこのレシピに依存している場合はビルドエラーになるので、原因の特定が容易になる。

bbappendで既存レシピに追加する場合

既存のレシピにsystemdサービスを追加するなら、bbappendを使う。具体的な書き方はbbappend実践ガイドのパターン3で解説している。ただし、inherit systemd.bbappend では使えない点に注意。元の .bb レシピが inherit systemd を含んでいる必要がある。

サービスファイルの設計ポイント

サービスファイルの書き方で、起動の安定性が大きく変わる。組み込みLinux向けのポイントを押さえよう。

基本構造

ini
# myapp.service
[Unit]
Description=My Application Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/myapp
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

[Unit] セクション

After= は起動順序を制御する。ネットワークが必要なサービスでよくある間違いが、After=network.target を使うことだ。

  • network.target — ネットワークデバイスが「設定された」ことを示す。IPアドレスが割り当てられたとは限らない
  • network-online.target — ネットワークが「使える状態」であることを示す。API呼び出し等が必要なサービスにはこちらを使う

[Service] セクション

Type= はプロセスの起動方式を指定する。

Type用途
simpleフォアグラウンドで動くプロセス(デフォルト)
forkingデーモンとしてforkするプロセス
oneshot一度実行して終了するスクリプト

組み込みでは Restart=on-failureRestartSec=5(5秒後にリトライ)を設定しておくと、クラッシュ時に自動復旧する。

パーミッション

サービスファイルは 0644 でインストールする。実行権限(0755)を付けると、systemdが警告を出す場合がある。

bash
# 正しい
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

# 間違い(実行権限は不要)
install -m 0755 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

timerとsocket activationのレシピ

systemdの強みはサービスだけではない。timerで定期実行、socket activationでオンデマンド起動ができる。

timer unit

cronの代わりにsystemd timerを使えば、依存関係やログがsystemdに統合される。

ini
# myapp.timer
[Unit]
Description=Run myapp periodically

[Timer]
OnBootSec=1min
OnUnitActiveSec=1h
Persistent=true

[Install]
WantedBy=timers.target
ini
# myapp.service(timerから呼ばれる)
[Unit]
Description=My Application Task

[Service]
Type=oneshot
ExecStart=/usr/bin/myapp --run-task

レシピでは、SYSTEMD_SERVICE に両方のファイルを指定する。

bash
SRC_URI = "file://myapp.sh \
           file://myapp.service \
           file://myapp.timer"

SYSTEMD_SERVICE:${PN} = "myapp.service myapp.timer"

do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/myapp.sh ${D}${bindir}/myapp

    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
    install -m 0644 ${WORKDIR}/myapp.timer ${D}${systemd_system_unitdir}/
}

FILES:${PN} += "${systemd_system_unitdir}/myapp.service \
                ${systemd_system_unitdir}/myapp.timer"

socket activation

socket activationを使うと、実際に接続が来るまでサービスを起動しない。リソースが限られた組み込みデバイスで有効だ。

ini
# myapp.socket
[Unit]
Description=My Application Socket

[Socket]
ListenStream=8080
Accept=no

[Install]
WantedBy=sockets.target
ini
# myapp.service(socketから起動される)
[Unit]
Description=My Application
Requires=myapp.socket

[Service]
Type=simple
ExecStart=/usr/bin/myapp

レシピの書き方はtimerと同じ。SYSTEMD_SERVICE にサービスとソケットの両方を指定する。

bash
SYSTEMD_SERVICE:${PN} = "myapp.service myapp.socket"

落とし穴7選と対処法

Yoctoのメーリングリストやフォーラムで頻出する問題をまとめた。

1. DISTRO_FEATURESにsystemdがない

最も多いミス。inherit systemd を書いても、ディストロ設定でsystemdが有効でなければサービスは動かない。

bash
# 確認方法
bitbake-getvar DISTRO_FEATURES | grep systemd

# 有効にする(conf/local.conf に追加)
INIT_MANAGER = "systemd"

2. [Install]セクションのWantedByが抜けている

サービスファイルに [Install] セクションがない、または WantedBy= が抜けていると、systemctl enable が何もしない。symlinkが作られず、自動起動しない。

ini
# 必須
[Install]
WantedBy=multi-user.target

3. SYSTEMD_SERVICEに:$を付け忘れる

bash
# 間違い(パッケージ修飾子なし)
SYSTEMD_SERVICE = "myapp.service"

# 正しい
SYSTEMD_SERVICE:${PN} = "myapp.service"

SYSTEMD_SERVICE はパッケージごとの変数だ。:${PN} なしだと、systemd bbclassが正しくpostinstスクリプトを生成できない。

4. インストール先が/etc/systemd/system/

bash
# 間違い(/etc/はユーザーのオーバーライド用)
install -d ${D}/etc/systemd/system/

# 正しい
install -d ${D}${systemd_system_unitdir}
# → /usr/lib/systemd/system/

/etc/systemd/system/ はユーザーがローカルでunitをオーバーライドするための場所だ。パッケージからインストールするunitは ${systemd_system_unitdir}/usr/lib/systemd/system/)に置く。

5. After=network.targetでネットワーク到達を期待する

ini
# network.target: ネットワークデバイスが設定されたことを示す
# ≠ ネットワークが使える状態

# ネットワーク到達が必要な場合
[Unit]
After=network-online.target
Wants=network-online.target

network.target はインターフェースの設定完了を意味するだけで、IPアドレスの取得やDNS解決ができる状態を保証しない。

6. サービスファイルに実行権限を付けている

bash
# 間違い(systemdが警告を出す場合がある)
install -m 0755 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

# 正しい
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

サービスファイルは設定ファイルであり、実行ファイルではない。0644 が正しいパーミッションだ。

7. journaldログがリブートで消える

デフォルトでは、journaldはログをRAM(/run/log/journal/)に保存する。リブートするとログは消える。

永続化するには /var/log/journal/ ディレクトリを作成するレシピを用意する。保存量は journald.conf で制御できる。

bash
do_install:append() {
    install -d ${D}${localstatedir}/log/journal
}
FILES:${PN} += "${localstatedir}/log/journal"

ただし、組み込みデバイスではFlashへの書き込み頻度を考慮する必要がある。頻繁なログ書き込みはFlashの寿命を縮める。journald.confSystemMaxUse=MaxRetentionSec= を設定して上限を管理しよう。

FAQ

systemdとsysvinitはYoctoで共存できる?

できる。Scarthgapでは sysvinit が DISTRO_FEATURES_BACKFILL_CONSIDERED として扱われるため、共存は可能だ。ただし INIT_MANAGER = "systemd" を設定するとsysvinitのinitscriptsはデフォルトで無効になる。特定のsysvinitスクリプトが必要なら DISTRO_FEATURES:append = " sysvinit" を明示的に追加する。

ターゲット上でサービスが有効か確認するには?

systemctl is-enabled myapp.service を実行する。enabled なら multi-user.target.wants/ 以下にsymlinkが存在する。static なら [Install] セクションがなく、enableできない状態だ。

systemd_system_unitdir/etc/systemd/system/ の違いは?

${systemd_system_unitdir}/usr/lib/systemd/system/ に展開される。パッケージがunitファイルをインストールする場所だ。/etc/systemd/system/ はローカル管理者がunitをオーバーライドする場所で、/etc/ のunitが優先される。Yoctoレシピでは常に ${systemd_system_unitdir} を使う。

bbappendで inherit systemd は使える?

使えない。inherit.bb ファイルでのみ有効だ。元のレシピが既に inherit systemd していれば、bbappendで SYSTEMD_SERVICE:${PN} を設定してサービスファイルを追加できる。元レシピがsystemdを継承していない場合は別のアプローチが必要だ。詳しくはbbappendガイドを参照。

サービスが起動しないときのデバッグ方法は?

ターゲット上で systemctl status myapp.servicejournalctl -u myapp.service を実行してログを確認する。ビルド時は bitbake -e myapp | grep postinst でpostinstスクリプトが生成されているか確認する。デバッグの詳細はビルドエラーデバッグガイドで解説している。

SYSTEMD_AUTO_ENABLEのデフォルト値は?

デフォルトは enable だ。SYSTEMD_AUTO_ENABLE:${PN}を設定しなければ、サービスは自動的に有効化される。インストールだけして自動起動させたくない場合は disable に設定する。

読み取り専用rootfsでsystemdサービスを追加するには?

読み取り専用rootfsでは、postinst時の systemctl enable/etc/ に書き込めず失敗する。代わりに do_install() 内でsymlinkを事前作成し、${D}${sysconfdir}/systemd/system/multi-user.target.wants/ にリンクを張る。OTAアップデート対応のイメージでよく使うパターンだ。

まとめ

Yoctoでsystemdサービスを動かすために必要なステップは3つだ。

  1. ディストロ設定: INIT_MANAGER = "systemd" で有効化
  2. レシピ: inherit systemd して SYSTEMD_SERVICE:${PN} を設定
  3. サービスファイル: [Install] セクションに WantedBy=multi-user.target を忘れない

timerとsocket activationを使えば、定期実行やオンデマンド起動もsystemdに統合できる。

落とし穴の多くは「設定の欠落」が原因だ。困ったら bitbake-getvar でDISTRO_FEATURESを確認し、ターゲット上で systemctl status myapp を実行するところから始めよう。デバッグツールの使い方はビルドエラーデバッグガイドも参考になる。

systemdレシピの書き方やYoctoのレシピ開発をさらに深く学びたい人には、以下の書籍が参考になる。

関連記事: