はじめに
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面持ち」みたいに表現される。(※)
※ 筆者の近所だけかもしれない
このあたりは次の記事が詳しいと思う。
- Implementing Over-The-Air (OTA) updates for embedded Linux and Android systems
- SWUpdate OTA I.MX8MM EVK
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_FILE
とRAUC_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」の枠の右上にある「+」マークをクリックし作成する。
Typeは「OS」か「Application」からどちらかを選択。 Name、Versionは適当な文字列。バージョンは任意の文字列、この組み合わせが完全に一致するものがすでにある場合はエラーとなる。
バンドルをアップロード
画面右下の「Drop Files to upload」の領域にバンドルをドラッグアンドドロップする。
Distributions画面
画面左のメニューの「Distribution」をクリックし、Distribution画面に遷移する。
この時点で画面右側の「Software Module」にはUpload画面で作成した、Software Moduleが1つ登録されている。
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アップデートに必要な機能は十分揃っているように見えた。