32blogby StudioMitsu
yocto11 min read

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

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

yoctoembedded-linuxsystemdscarthgap
目次

Yoctoで自作アプリケーションをターゲット上で自動起動させたい。組み込みLinuxでは、それはsystemdサービスを作ることを意味する。

だが、Yoctoでsystemdサービスを正しく動かすには、レシピの書き方・ディストロ設定・サービスファイルの3つが噛み合う必要がある。どれか1つが欠けると、サービスは静かに起動しない。

この記事では、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/ ディレクトリを作成するレシピを用意する。

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

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

まとめ

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のレシピ開発をさらに深く学びたい人には、以下の書籍が参考になる。

PRMastering Embedded Linux Development 4th Ed (2024)Amazonで見る
PREmbedded Linux Development Using Yocto Project 3rd Ed (2023)Amazonで見る