みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

YoctoProject RAUCと実機でOTAアップデート

はじめに

IoTではよく見かける、遠隔から機器のファームウェア(ここではLinux)をアップデートする仕組みがある。 これらの仕組みはOver-The-Air Update(OTAアップデート)と呼ばれる。

組み込みLinux向けのOTA Updateのためのソフトウェアはいくつかある。YoctoProjectをサポートしているもののうち、 レシピの内容や使い方などをみて、一番とっつきやすそうだったRAUCを試してみる。

ターゲットはラズベリーパイ4。

OTAアップデートの実装

OTAアップデートは、ストレージ上にOSをどの様にレイアウトするか?というところから設計する必要がある。

運用するOSを1面だけ持って、それをOTAアップデート対象とする場合は、基本的にリカバリ用のOSをストレージ上のどこかに配置する必要がある。 このような運用はMain/aux imagesとか、Single imageとか呼ばれる。日本では「1面持ち」みたいに表現される。(※)

または運用する面を2面もって、片方をアクティブ、もう片方をインアクティブとして、OTAアップデートはインアクティブの方に対して行うという方法がある。こちらはA/B UpdateとかDouble imagesとか呼ばれる。日本では「2面持ち」みたいに表現される。(※)

※ 筆者の近所だけかもしれない

このあたりは次の記事が詳しいと思う。

RAUCではどちらにも対応できるが、サンプル実装であるmeta-rauc-raspberrypiではA/B Updateを実装している。

環境構築

作業環境

$ mkdir -p ~/yocto/rpi-kirkstone
$ cd ~/yocto/rpi-kirkstone

pokyの取得

$ git clone git://git.yoctoproject.org/poky.git -b kirkstone 

環境変数の設定

$ source poky/oe-init-build-env

meta-raspberrypiの取得

$ bitbake-layers layerindex-fetch meta-raspberrypi

meta-rauc関連レイヤの取得

$ bitbake-layers layerindex-fetch meta-rauc
$ pushd ../poky
$ git clone https://github.com/rauc/meta-rauc-community.git -b kirkstone 
$ popd
$ bitbake-layers add-layer ../poky/meta-rauc-community/meta-rauc-raspberrypi

証明書と鍵の作成

$ ../poky/meta-rauc-community/create-example-keys.sh

このスクリプトで鍵、証明書とsite.confが生成される。

local.confを編集

MACHINE ?= "raspberrypi4"
DL_DIR ?= "${TOPDIR}/../downloads"

# Generic raspberrypi settings
ENABLE_UART = "1"
RPI_USE_U_BOOT = "1"

# systemd
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# Settings for meta-rauc-raspberry-pi
IMAGE_INSTALL:append = " rauc"
IMAGE_FSTYPES:append = " ext4"
WKS_FILE = "sdimage-dual-raspberrypi.wks.in"

DISTRO_FEATURES:append = " rauc"

ビルド

$ bitbake core-image-minimal

動作確認

core-image-minimal-raspberrypi4.wicをSDに書き込み、ラズベリーパイを起動する。

ターゲット上で、下記のようにしてブート状態を確認する。

Poky (Yocto Project Reference Distro) 4.0.6 raspberrypi4 ttyS0

raspberrypi4 login: root
Last login: Sun Jan  8 14:35:19 +0000 2023 on /dev/ttyS0.
root@raspberrypi4:~# rauc status
=== System Info ===
Compatible:  RaspberryPi4
Variant:     
Booted from: rootfs.0 (A)

=== Bootloader ===
Activated: rootfs.0 (A)

=== Slot States ===
o [rootfs.1] (/dev/mmcblk0p3, ext4, inactive)
        bootname: B
        boot status: good

x [rootfs.0] (/dev/mmcblk0p2, ext4, booted)
        bootname: A
        mounted: /
        boot status: good

A面とB面があり、A面がアクティブ(booted)であることが確認できた。

バンドル

RAUCではOTAアップデートで配信されるイメージファイルをbundleと呼ぶらしい。 meta-raucではbundleを生成するためにbundle.bbclassを提供している。

meta-rauc-raspberrypiではupdate-bundle.bbで生成することができる。

この内容を見てみると、bundle.bbclassを継承していることがわかる。 bundleの元になるイメージはcore-image-minimalで有ることも読み取れる。

DESCRIPTION = "RAUC bundle generator"

inherit bundle

RAUC_BUNDLE_COMPATIBLE = "RaspberryPi4"
RAUC_BUNDLE_VERSION = "v20200703"
RAUC_BUNDLE_DESCRIPTION = "RAUC Demo Bundle"
RAUC_BUNDLE_SLOTS = "rootfs" 
RAUC_SLOT_rootfs = "core-image-minimal"
RAUC_SLOT_rootfs[fstype] = "ext4"

RAUC_KEY_FILE = "${THISDIR}/files/development-1.key.pem"
RAUC_CERT_FILE = "${THISDIR}/files/development-1.cert.pem"

鍵について

update-bundle.bbにもRAUC_KEY_FILERAUC_CERT_FILEがあり、こちらは=で代入されているため上書きができない。 このままビルドしたバンドルでOTAアップデートしようとすると、create-example-keys.shで生成された鍵と異なるため、 署名検証で失敗する。

OTAアップデートの更新イメージはきちんと検証されていることが確認できる。

create-example-keys.shで生成した鍵で署名されるようにするため、このレシピのRAUC_KEY_FILEとRAUC_CERT_FILEの行はコメントアウトしておく。

バンドルのビルド

update-bundleをビルドする。

$ bitbake update-bundle

tmp/deploy/images/raspberrypi4/update-bundle-raspberrypi4.raucbが生成される。

hawkBit

OTAアップデートするOSイメージを配信するための仕組みとして、hawkBitというものがある。

RAUCはhawkBitとの連携に対応している。

hawkBitクライアントの追加

meta-raucでは2つのhawkBitクライアントを提供している。

  • rauc-hawkbit (python実装)
  • rauc-hawkbit-updater (C実装)

今回は、C実装のrauc-hawkbit-updaterを使用する。そのためにはlocal.confに下記を追加する。

IMAGE_INSTALL:append = " rauc-hawkbit-service"

hawkBitの起動

$ docker run --rm -p 8080:8080 hawkbit/hawkbit-update-server:latest

ログイン

下記でログイン

username admin
password admin

最初にターゲットからのトークンによるアクセスを許可する。 [System Config] -> [Authentication Configuration] とクリックして、[Allow targets to authenticate directly with their target security token]にチェックをつける。

ターゲットトークンによるアクセスの許可

ターゲットの登録

次にデバイスを登録する。この時ターゲット上の/etc/rauc-hawkbit-updater/config.confの「target_name」と「Controller Id」を一致させておくこと。 ここでは、サンプルの設定が「test-target」となっているためそれに合わせる。

バイスの追加

バイスを追加するとトークンが発行される。これをconfig.confのauth_tokenに設定する。

トークンの発行

ターゲットの設定

アクセス設定

初期状態の設定ではターゲット(ラズベリーパイ)はhawkBitと通信できないため、/etc/rauc-hawkbit-updater/config.confにhawkBitサーバのIPアドレスと先ほど発行したターゲットトークンを設定する。

下記の内容で~/config.shを作成

#!/bin/sh

readonly CONFIG_FILE=/etc/rauc-hawkbit-updater/config.conf
readonly SERVER="$1"
readonly TOKEN="$2"

sed -i "s/hawkbit_server.*=.*/hawkbit_server            = ${SERVER}:8080/" ${CONFIG_FILE}
sed -i "s/auth_token.*=.*/auth_token                 = ${TOKEN}/" ${CONFIG_FILE}

systemctl restart rauc-hawkbit-updater.service

下記のようにして実行する。

# chmod +x ~/config.sh
# ~/config.sh <hawkBitのIPアドレス> <発行したSecurity token>

これで一定時間毎に更新イメージがないかサーバにチェックするようになる。

root@raspberrypi4:~# systemctl status rauc-hawkbit-updater
* rauc-hawkbit-updater.service - HawkBit client for Rauc
     Loaded: loaded (8;;file://raspberrypi4/lib/systemd/system/rauc-hawkbit-updater.service/lib/systemd/system/rauc-hawkbit-updater.service8;;; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2023-01-08 17:16:50 UTC; 51s ago
   Main PID: 273 (rauc-hawkbit-up)
     Status: "Init completed, start polling HawkBit for new software."
      Tasks: 1 (limit: 4915)
     CGroup: /system.slice/rauc-hawkbit-updater.service
             `- 273 /usr/bin/rauc-hawkbit-updater -s -c /etc/rauc-hawkbit-updater/config.conf

Jan 08 17:16:50 raspberrypi4 systemd[1]: Started HawkBit client for Rauc.
Jan 08 17:16:51 raspberrypi4 rauc-hawkbit-updater[273]: Checking for new software...
Jan 08 17:16:51 raspberrypi4 rauc-hawkbit-updater[273]: No new software.

更新イメージの配信

hawkBitのWeb UIを使用してバンドルを配信し、ターゲットのOSをOTAアップデートする。

全体の流れは次のようになる。

今回は、配信するための最小限の手順のため、Target Filtersの作成やRolloutの設定などは行わない。

Upload画面

画面左のメニューの「Upload」をクリックし、Upload画面に遷移する。

Software Moduleを作成

「Software Module」の枠の右上にある「+」マークをクリックし作成する。

Software Moduleの作成

Typeは「OS」か「Application」からどちらかを選択。 Name、Versionは適当な文字列。バージョンは任意の文字列、この組み合わせが完全に一致するものがすでにある場合はエラーとなる。

バンドルをアップロード

画面右下の「Drop Files to upload」の領域にバンドルをドラッグアンドドロップする。

Distributions画面

画面左のメニューの「Distribution」をクリックし、Distribution画面に遷移する。

この時点で画面右側の「Software Module」にはUpload画面で作成した、Software Moduleが1つ登録されている。

Distributionを作成

「Distribution」の枠の右上にある「+」マークをクリックし作成する。

Distributionの作成

Typeは「App(s) only」「OS only」、「OS with app(s)」の中から選択。 Name、Versionは適当な文字列。バージョンは任意の文字列、この組み合わせが完全に一致するものがすでにある場合はエラーとなる。

Software ModuleをDistributionに関連付け

画面右側の「Software Module」のリストにあるアイテムを、作成したDistributionにドラッグアンドドロップする。

これでDistributionが配信可能な状態となる。

Deployment画面

画面左のメニューの「Deployment」をクリックし、Deployment画面に遷移する。

DistributionをTargetに配信

画面真ん中くらいにある「Distributions」のリストから配信するアイテムを、「Targets」にあるターゲットにドラッグアンドドロップする。

イメージの配信

「Forced」にすると即時配信、インストールが実行される。 即時配信といっても、実際にはターゲットからの次回の更新確認で処理が実行されるため少しタイムラグがある。

配信が完了すると次のように履歴が追加される

配信完了

ターゲットの再起動

配信およびインストールが完了したら、ターゲット(ラズベリーパイ)をリブートし、状態を確認する。

root@raspberrypi4:~# rauc status
=== System Info ===
Compatible:  RaspberryPi4
Variant:
Booted from: rootfs.1 (B)

=== Bootloader ===
Activated: rootfs.1 (B)

=== Slot States ===
x [rootfs.1] (/dev/mmcblk0p3, ext4, booted)
        bootname: B
        mounted: /
        boot status: good

o [rootfs.0] (/dev/mmcblk0p2, ext4, inactive)
        bootname: A
        boot status: good

B面がboottedになっていることがわかる。

まとめ

YoctoProjectを使ってOTAアップデートを試した。OTAアップデートにはRAUCを使用した。 配信サーバとしてhawkBitを使い、遠隔からのOS更新の手順も確認した。

RAUCとhawkBitは連携はするが、別々のプロジェクトで開発されているため、必ずセットで使用する必要はない。 hawkBitはSWUpdateなどの他のOTAアップデートフレームワークとも連携可能となっている。

RAUCとしてはA/Bアップデートの仕組みはGRUBやu-bootなどの環境変数を使用して、 それなりに仕組みを合わせ込む作業が必要となる。ラズベリーパイ4の場合はmeta-rauc-raspberpiをベースにカスタムすれば、 割と簡単に環境が構築できると思う。

hawkBitとしては今回はターゲットトークンを使用して、各ボードそれぞれにトークンを発行してアクセス認証をしたが、 実際に製品で使うようなことを考えると、「gateway security token」などを使って複数のボードで共通のトークンを使うなども検討したほうが良いかもしれない。

RAUCもhawkBitも良くできているので、OTAアップデートに必要な機能は十分揃っているように見えた。