aptpod Tech Blog

株式会社アプトポッドのテクノロジーブログです

通信量を劇的に節約!Terminal System 2のデルタアップデート機能

aptpod Advent Calendar 2025 12月19日の記事です。

intdashグループの野本です。組込Linuxソフトウェア開発を担当しています。

本記事では、車載などのエッジ環境で利用可能なゲートウェイアプライアンス intdash Terminal System 2 向けに開発している デルタアップデート機能 について紹介します。

デルタアップデートとは

Terminal System 2はMenderによるOTAをサポートしています。 Menderを使用したOSアップデートは、フルアップデートデルタアップデートの2つの方法があります。

フルアップデートとデルタアップデート

現状、Terminal System 2のOSアップデートは、ルートファイルシステム全体を一括更新するフルアップデートのみサポートしています。 ルートファイルシステム全体のサイズは数GB程度あるため、帯域幅が限られるモバイル回線ではインストールに時間がかかったり、データ通信量が多くかかってしまうという課題があります。

デルタアップデートは、アップデート前後のルートファイルシステムの差分のみをエッジコンピューターに送信することで、OSアップデートのインストール時間を短縮し、データ通信量を大幅に節約することができます。

参考:Mender docs - デルタアップデート

デルタアップデートの仕組み

デルタアップデートでは、アップデート前後のルートファイルシステムのバイナリ差分(Binary delta)を作成し、アップデートします(現在のMenderバージョンでは、xdelta3を使用してバイナリ差分を作成しています)。バイナリ差分を使用する以外は、通常のOSアップデートと仕組みは変わりません。

バイナリ差分は、デルタアップデートに対応したOSアップデートアーティファクトを2つ用意し、CLIツール(mender-binary-delta-generator)を使って生成します。

$ mender-binary-delta-generator -o delta-v1-v2.mender rootfs-v1.mender rootfs-v2.mender

生成されたバイナリ差分には、アップデート前後のルートファイルシステムのチェックサム値が定義されています。

+-------------------------------+
|Type:       rootfs-image       |
|Version:    v1                 |
|Checksum:   5bb84175           |
|                               |
|Provides                       |
|rootfs-image.checksum: 5bb84175|
+-------------------------------+

+--------------------------------+    +--------------------------------+
|Type:       mender-binary-delta |    |Type:       rootfs-image        |
|Version:    v2                  |    |Version:    v2                  |
|Checksum:   ff532419            |    |Checksum:   b9147deb5           |
|                                |    |                                |
|Provides                        |    |Provides                        |
|rootfs-image.checksum: b9147deb5|    |rootfs-image.checksum: b9147deb5|
|                                |    +--------------------------------+
|Depends:                        |  ↑ 提供(Provides)は、フル/差分共にv2で共通
|rootfs-image.checksum: 5bb841755|  ← 差分はv1に依存(Depends)
+--------------------------------+

参考:How checksums look in a working case

この例からわかるように、バイナリ差分はデルタアップデートに対応したイメージが必要で(デルタアップデートに対応していないバージョンのバイナリ差分生成はできません)、かつアップデート前後のバージョンに依存します。現状では、アップデート前後のバージョンのバリエーション毎にバイナリ差分の生成が必要です。

なお、アップデート前後のバージョンに合わせて、Menderサーバー側でバイナリ差分を生成する機能もありますが、Hosted Menderではイメージサイズの制限や生成ジョブ実行時間に制限があり、現状Terminal System 2では使用できません。

また、差分を適用する仕組み上、ルートファイルシステムには変更を加えることはできません。そのため、デルタアップデートに対応したイメージはルートファイルシステムが読み込み専用になります。書き込みが必要なデータは、ルートファイルシステムとは別のデータ保持用のパーティション(Menderのdataパーティション)や外部ストレージなどを利用します。 もし、エッジコンピューターの稼働中にルートファイルシステムの内容を変更した場合、デルタアップデートを実行するとチェックサム検証でエラーが発生するので、注意しましょう(その場合でも、フルアップデートは可能です)。

実際にデルタアップデートを試してみる

まず、デルタアップデートに対応したイメージをYoctoを使用してビルドします。 Yoctoでの組み込み方は非常に簡単で、以下の3ステップでデルタアップデートに対応したイメージのビルドができます。(実際は後述のエラーがたくさん出ました)

  1. IMAGE_FEATURES += "read-only-rootfs" でルートファイルシステムを読み込み専用にする(参考:Read-only root filesystem
  2. meta-mender-commerical レイヤーを追加
  3. local.confにIMAGE_INSTALL、LICENSE_FLAGS_ACCEPTED、SRC_URIを追記

追記例:

# Customizations for Mender delta-update support
IMAGE_INSTALL:append = " mender-binary-delta"
LICENSE_FLAGS_ACCEPTED:append = " commercial_mender-yocto-layer-license"
SRC_URI:pn-mender-binary-delta = "file://${HOME}/mender-binary-delta-1.5.1.tar.xz"

上記のYocto対応を行った、開発中のEDGEPLANT T1向けOSイメージを2つ用意します。今回はパッチリリースを想定し、主要モジュール(coredバイナリ、intdash-edge-agent2コンテナ、device-connector-intdashコンテナ)を更新したイメージを用意しました。CLIツールを使ってバイナリ差分を生成してMenderサーバーにアップロードすると、バイナリ差分であるmender-binary-deltaという種類のアーティファクトが追加されます。

アップロードされたバイナリ差分(mender-binary-delta

フルイメージは2.3GBありますが、今回のバイナリ差分では541MBとなり、約1.8GB(76%)削減されていることがわかります。

準備ができたので、デルタアップデートを試します。 デルタアップデートをする場合は、デプロイメントオプションで Generate and deploy Delta Artifacts where available のチェックボックスを有効化して、デプロイメントを作成する必要があります(チェックがない場合はフルアップデートされます)。

デルタアップデートのデプロイオプションを有効化する

あとは通常のOSアップデートと同じようにデプロイメントを作成すると、バイナリ差分を利用したデルタアップデートができます。

デルタアップデート成功

デルタアップデートを使用することで、フルアップデートより少ないサイズでOSアップデートをすることができました。

開発の裏側:ReadOnly Rootfs化の壁

従来、ルートファイルシステムが読み込み専用であることを想定していなかったこともあり、開発中に以下のような問題が発生しました。

読み取り専用なはずなのに差分が発生する

問題

Yoctoで IMAGE_FEATURES += "read-only-rootfs" を指定して、ルートファイルシステムを読み込み専用にしているのに、EDGEPLANT T1にてなぜかルートファイルシステムが変更され、チェックサム不一致によりデルタアップデートに失敗するという現象が発生しました。

調査の結果、/etc/machine-id というファイルが、初回起動時に書き換えられていることがわかりました。 起動時に読み取り専用になっていないタイミングがあり、書き換えられてしまっているようでした。

対処

Yoctoで IMAGE_FEATURES += "read-only-rootfs" を指定すると、rootfs-postcommands.bbclassでカーネル起動引数にroが指定され、これにより読み取り専用になります。

APPEND:append = '${@bb.utils.contains("IMAGE_FEATURES", "read-only-rootfs", " ro", "", d)}'

ただし、EDGEPLANT T1などのNVIDIA Jetsonデバイスの場合、上記のYoctoデフォルトのAPPEND変数をカーネル起動引数として使用しておらず、meta-tegraで独自に定義されたカーネル起動引数変数 KERNEL_ARGS を使用していたため、上記変更が反映されていなかったことが原因でした。GitHubにも同様のissueがありますが、今回は以下のように local.conf にカーネル起動引数設定を追加することで対処しました。

KERNEL_ARGS:append = " ro"

systemdの設定をアップデート間で保持できない

問題

従来、プロキシーなどの一部設定はsystemdサービス全体に反映させるため、ルートファイルシステムの /lib/systemd/system.conf.d/ に設定を入れて、OSアップデート時にマイグレーションするという対応を行っていました。デルタアップデートではルートファイルシステムが読み取り専用となったため、この方法が利用できなくなり、別の方法が必要となりました。

Terminal System 2では /etc がOverlayfsとなっているため、設定ファイルの置き場を /etc/systemd/system.conf.d/ に変更する案がありましたが、systemdの起動よりOverlayfsのマウント処理のほうが遅く、/etc の設定変更が反映されないという課題があり、実現できていませんでした。

対処

Yoctoには overlayfs-etc.bbclass というクラスがあることが判明し、これを使用することでsystemdが起動するより前に /etc をOverlayfsとしてマウントすることができました。このクラスでは、/sbin/init をラッパースクリプトで置き換えることにより、systemdより先にマウント処理を実行しているようです。

有効化は IMAGE_FEATURESoverlayfs-etcを追加するだけです。

IMAGE_FEATURES += " overlayfs-etc "

レシピの標準スクリプトでは作成されるOverlayfsのディレクトリ構造が従来と異なり、OSアップデート時に複雑なマイグレーションが必要になることから、以下のようにイメージレシピでカスタムテンプレート OVERLAYFS_ETC_INIT_TEMPLATE を指定して対応しました。

# overlayfs-etc settings
# Mount /etc before systemd starts using preinit script
# This ensures /etc/systemd/system/ configurations are recognized at boot
OVERLAYFS_ETC_DEVICE = "${MENDER_DATA_PART}"
OVERLAYFS_ETC_FSTYPE = "${@d.getVar('MENDER_DATA_PART_FSTYPE_TO_GEN') if d.getVar('MENDER_DATA_PART_FSTYPE') == 'auto' else d.getVar('MENDER_DATA_PART_FSTYPE')}"
OVERLAYFS_ETC_MOUNT_POINT = "/data"
OVERLAYFS_ETC_MOUNT_OPTIONS = "defaults"
OVERLAYFS_ETC_USE_ORIG_INIT_NAME = "1"
OVERLAYFS_ETC_CREATE_MOUNT_DIRS = "0"
# Use custom preinit template to maintain existing directory structure (/data/overlay/etc)
OVERLAYFS_ETC_INIT_TEMPLATE = "${THISDIR}/files/overlayfs-etc-preinit-ts2.sh.in"

これにより、systemdの /etc 配下の設定変更が反映され、OSアップデートで保持されるようになりました。

その他

以下のようなルートファイルシステムに書き込む処理はすべてエラーするので、tmpfsに作成するようにしたり、

-  TEMP_MOUNT_DIR_CANDIDATE=$(mktemp -d /mnt-XXXXXX)
+  TEMP_MOUNT_DIR_CANDIDATE=$(mktemp -d /tmp/mnt-XXXXXX)

非アクティブなパーティションでext4をマウント処理をする際に、ジャーナルの再生による書き込みを防ぐため ro,noloadオプションを追加してチェックサムの不一致が発生しないようにしたり、

-  mount "$INSTALL_PART" "$TEMP_MOUNT_DIR_CANDIDATE"
+  mount -o ro,noload "$INSTALL_PART" "$TEMP_MOUNT_DIR_CANDIDATE"

などなど、細かい問題を見つけては修正を繰り返して、デルタアップデートに対応することができました。

まとめ

本記事では、intdash Terminal System 2 向けに開発中のデルタアップデート機能についてご紹介しました。

ルートファイルシステムを読み込み専用化し、差分のみを配信することで、アップデート時のデータ通信量を大幅に削減できることが確認できました。特に帯域が限られるモバイル回線環境においては、通信コストの削減だけでなく、アップデート時間の短縮による可用性の向上も期待できます。

現在、次リリースでの正式サポートに向けて、引き続き検証と開発を進めています。より使いやすく、効率的なエッジ運用を実現する機能にご期待ください。