🐥

Menderで始める組み込みOTA 第9回 : 接続編1 : QEMUエミュレータを接続

2022/08/01に公開

MenderはオープンソースベースのLinux向けOTA(Over the Air Update)ソリューションです。

Mender is a secure, risk tolerant and efficient over-the-air update manager. Remotely manage and deploy software updates to your IoT devices at scale, worldwide.

https://mender.io/

筆者の会社コードギアではこのたびご縁あってMenderの技術サポートを提供する機会を得ました。
この一連の記事ではMender OTAソリューションの技術的な側面を紹介します。


Menderで始める組み込みOTA」記事インデックス

Quickstart編

第9回からは「接続編」として、いろいろなLinuxデバイスをMenderに接続します。

はじめに

今回は開発PCでQEMUベースのデバイスエミュレータを動かして、mender.io に接続しアプリを展開するまでをご紹介します。基本的な流れは docs.mender.io にある「Prepare a virtual device」の内容と同じです。

QEMUはオープンソースのCPUエミュレータです。あらゆる環境であらゆるCPUおよび周辺チップのエミュレーションをサポートしていますが、x86やARM上で同じ種類のゲストCPUをサポートする場合、ホストCPUの持つ仮想化サポート機能(Intel VTやARM VHE)を利用することで実用的な速度でのエミュレーションが可能になっています。ただ、このような仮想CPUを実際に利用するには周辺デバイスのサポートやディスクイメージのサポートも必要になるので結構大掛かりです。

Ubuntu等のLinuxディストリビューションでは、QEMUのエミュレータ本体は .debパッケージ 化され簡単にインストール可能ですが、問題はゲストOSを起動するためのブートローダーであったりLinux Kernelファイルであったりルートファイルシステムだったりします。これらをすぐに利用可能なdockerイメージという形であらかじめまとめてあるのが、今回使用するデバイスエミュレータイメージというわけです。

デバイスエミュレータイメージを動かすホストPCとしては、これまで開発PCとして利用してきた 64bit x86ベースのLinux、またはWindows10 WSL2 Linuxを利用可能です。これらのホストPC上でAlpine Linuxベースのdockerイメージを動かし、さらにdocker内でqemu-system-x86_64を動かしYoctoベースの仮想的なLinuxデバイスを起動します。

前提として、mender.io 試用アカウントの取得 (第2回 記事参照)が必要です。接続手順は 第3回 の後半と同じなので、この記事では一部省略している箇所があります。

Dockerエンジンのインストール

開発PCにDockerエンジンをインストールします。「第7回 : Quickstart Part6 : コンテナアップデートを実行」で既にインストール済みの場合は追加作業はありません。

そうでない場合は、まずは docker psdocker images lsをやってみて、正常に実行できるか確認します。もし実行できない場合は「第7回 : Quickstart Part6 : コンテナアップデートを実行」の場合と同様、Dockerについて の手順を参考にインストールします。

mender.io にログインし CONNECT A DEVICE を実行

まず、開発PCでLinuxシェルを開いておきます。(bashを想定。ssh接続のシェルでも可)

Webブラウザで mender.io のWebUIにログインし、DASHBOARD 画面で表示されている CONNECT A DEVICE ボタンを押します。

以下の画面で Try a virtual device ボタンを押します。

ダイアログに表示される接続文字列をコピーして、開発PCのLinuxシェルで実行します。
具体的には、以下の画面で COPY TO CLIPBOARD を押して文字列をコピーした後、あらかじめ開いておいた LinuxシェルウインドウにPasteし、ENTERを押します。

実際にやってみると以下のような感じ

codegear@dell3070:~$ TENANT_TOKEN='eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZW5kZXIudGVuYW50IjoiNjFhZDA3MzQzNjFhOTViMTRhMzc1YTE4IiwiaXNzIjoiTWVuZGVyIiwic3ViIjoiNjFhZDA3MzQzNjFhOTViMTRhMzc1YTE4In0.mcBW_FtR2xTO6njMbvDzbSfdvS36UIUF-xz3Af6Hp_n7T5teMIPW7cB3CveELbD9KZrf5mpmPtjKGwKLORRNxz_hcypu_nTwY-oYJlhrx6zI2D0_TqTfcciIqlCqpsYrrADAQhUy6rbToxPrZJ7SHH3obe09qzMAt7_Yi6s4ysTgJ1S34mIIDmjOWrPkoVocKh6HMGq8kdWjG1oHAcsuh63VtGzW7Lg8SH8Xdknvk62T5v8XM1b7ctQK7uWbJAETHul6WhLDkhUV9t_vztXMXXVJnbvIhxO6Gmr2BwB1BQ7pSJPSabl7EY209j2OaC9trUMzGrDqyh-WXIpIURbHsxXUtrGxVdZXLhLwT0ox8X0fuzJ9JhKNm8NYgCsh-kytYzgZS5e7spKsmFdvxvUQT988pg8phqWAOxFGKGOWI9PiSqL8QFK1PDPUPRY_ye7oaqON2mZ5ckrx_zaGXhFO-mZ-1VMBDdYyGcjHfEhOxsUPcqokyxEsrFN0FEo2XrMp'
codegear@dell3070:~$ docker run -it -p 85:85 -e SERVER_URL='https://hosted.mender.io' \
> -e TENANT_TOKEN=$TENANT_TOKEN --pull=always mendersoftware/mender-client-qemu
latest: Pulling from mendersoftware/mender-client-qemu
59bf1c3509f3: Already exists
a445a14663ce: Pull complete
ca1c43993d4c: Pull complete
47faa83b0f51: Pull complete
2d5414ab0c30: Pull complete
5b9a79b02093: Pull complete
eec11bbe5a77: Pull complete
f97229c8181b: Pull complete
fa96904e412e: Pull complete

(中略)

+ ./setup-mender-configuration.py --img=/core-image-full-cmdline-qemux86-64.uefiimg --server-url=https://hosted.mender.io --server-ip= --tenant-token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZW5kZXIudGVuYW50IjoiNjFhZDA3MzQzNjFhOTViMTRhMzc1YTE4IiwiaXNzIjoiTWVuZGVyIiwic3ViIjoiNjFhZDA3MzQzNjFhOTViMTRhMzc1YTE4In0.mcBW_FtR2xTO6njMbvDzbSfdvS36UIUF-xz3Af6Hp_n7T5teMIPW7cB3CveELbD9KZrf5mpmPtjKGwKLORRNxz_hcypu_nTwY-oYJlhrx6zI2D0_TqTfcciIqlCqpsYrrADAQhUy6rbToxPrZJ7SHH3obe09qzMAt7_Yi6s4ysTgJ1S34mIIDmjOWrPkoVocKh6HMGq8kdWjG1oHAcsuh63VtGzW7Lg8SH8Xdknvk62T5v8XM1b7ctQK7uWbJAETHul6WhLDkhUV9t_vztXMXXVJnbvIhxO6Gmr2BwB1BQ7pSJPSabl7EY209j2OaC9trUMzGrDqyh-WXIpIURbHsxXUtrGxVdZXLhLwT0ox8X0fuzJ9JhKNm8NYgCsh-kytYzgZS5e7spKsmFdvxvUQT988pg8phqWAOxFGKGOWI9PiSqL8QFK1PDPUPRY_ye7oaqON2mZ5ckrx_zaGXhFO-mZ-1VMBDdYyGcjHfEhOxsUPcqokyxEsrFN0FEo2XrMp --docker-ip=172.17.0.2/16 --mender-gateway-conffile ''
458752+0 records in
458752+0 records out
debugfs 1.46.4 (18-Aug-2021)
debugfs 1.46.4 (18-Aug-2021)
debugfs:  debugfs:
debugfs:  Allocated inode: 258
debugfs:  debugfs 1.46.4 (18-Aug-2021)
debugfs 1.46.4 (18-Aug-2021)
debugfs:  debugfs:
debugfs:  Allocated inode: 258
debugfs:  debugfs 1.46.4 (18-Aug-2021)
debugfs:  debugfs:  rm: File not found by ext2_lookup while trying to resolve filename
debugfs:  Allocated inode: 8795
debugfs:  458752+0 records in
458752+0 records out
+ touch /mender-setup-complete
+ export QEMU_NET_HOSTFWD=,hostfwd=tcp::80-:80,hostfwd=tcp::85-:85,hostfwd=tcp::443-:443,hostfwd=tcp::8080-:8080
+ QEMU_NET_HOSTFWD=,hostfwd=tcp::80-:80,hostfwd=tcp::85-:85,hostfwd=tcp::443-:443,hostfwd=tcp::8080-:8080
+ ./mender-qemu
+ mktemp -d /tmp/mender-qemu.XXXXXXXX
+ TMPDIR=/tmp/mender-qemu.XXcbGnmL
+ trap cleanup 1 15
+ '[' -z  ]
+ dirname ./mender-qemu
+ BUILDDIR=./../../build
+ IMAGE_NAME=
+ '[' -n  ]
+ IMAGE_NAME=core-image-full-cmdline
+ '[' -d ./../../build/tmp ]
+ BUILDTMP=./../../build/tmp-glibc
+ QEMU_ARGS=
+ cd ./../../build
./mender-qemu: cd: line 52: can't cd to ./../../build: No such file or directory
+ eval
+ MACHINE=qemux86-64
+ QEMU_SYSTEM=qemu-system-x86_64
+ echo
+ grep -q mender-image-bios
+ echo
+ grep -q mender-image-gpt
+ BOOTLOADER=/ovmf.qcow2
+ BOOTLOADER_DATA=/ovmf.vars.qcow2
+ BOOTLOADER_ARG='-drive file=/ovmf.qcow2,if=pflash,format=qcow2,unit=0,readonly=on -drive file=/ovmf.vars.qcow2,if=pflash,format=qcow2,unit=1'
+ DISK_IMG=/core-image-full-cmdline-qemux86-64.uefiimg
+ STORAGE_TYPE=ide
+ od -txC -An -N3 /dev/urandom
+ tr ' ' :
+ RANDOM_MAC=52:54:00:34:09:2c
+ QEMU_DRIVE=
+ QEMU_ARGS=' -drive file=/core-image-full-cmdline-qemux86-64.uefiimg,if=ide,format=raw '
+ echo '--- qemu version'
--- qemu version
+ qemu-system-x86_64 --version
QEMU emulator version 6.1.1
Copyright (c) 2003-2021 Fabrice Bellard and the QEMU Project developers
+ echo '--- starting qemu'
--- starting qemu
+ ret=0
+ QEMU_AUDIO_DRV=none qemu-system-x86_64 -m 256M -drive 'file=/ovmf.qcow2,if=pflash,format=qcow2,unit=0,readonly=on' -drive 'file=/ovmf.vars.qcow2,if=pflash,format=qcow2,unit=1' -net 'nic,macaddr=52:54:00:34:09:2c' -net 'user,hostfwd=tcp::8822-:22,hostfwd=tcp::80-:80,hostfwd=tcp::85-:85,hostfwd=tcp::443-:443,hostfwd=tcp::8080-:8080' -display 'vnc=:23' -nographic -enable-kvm -drive 'file=/core-image-full-cmdline-qemux86-64.uefiimg,if=ide,format=raw'
Could not access KVM kernel module: No such file or directory
qemu-system-x86_64: failed to initialize kvm: No such file or directory
+ ret=1
+ continue
+ QEMU_AUDIO_DRV=none qemu-system-x86_64 -m 256M -drive 'file=/ovmf.qcow2,if=pflash,format=qcow2,unit=0,readonly=on' -drive 'file=/ovmf.vars.qcow2,if=pflash,format=qcow2,unit=1' -net 'nic,macaddr=52:54:00:34:09:2c' -net 'user,hostfwd=tcp::8822-:22,hostfwd=tcp::80-:80,hostfwd=tcp::85-:85,hostfwd=tcp::443-:443,hostfwd=tcp::8080-:8080' -display 'vnc=:23' -nographic -drive 'file=/core-image-full-cmdline-qemux86-64.uefiimg,if=ide,format=raw'
BdsDxe: failed to load Boot0001 "UEFI QEMU DVD-ROM QM00003 " from PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Master,0x0): Not Found
BdsDxe: loading Boot0002 "UEFI QEMU HARDDISK QM00001 " from PciRoot(0x0)/Pci(0x1,0x1)/Ata(Primary,Master,0x0)
BdsDxe: starting Boot0002 "UEFI QEMU HARDDISK QM00001 " from PciRoot(0x0)/Pci(0x1,0x1)/Ata(Primary,Master,0x0)
Welcome to GRUB!

lock: OK
lock: OK

(ここからqemux86-64 の起動メッセージが始まる)

[    0.000000] Linux version 5.4.178-yocto-standard (oe-user@oe-host) (gcc version 9.3.0 (GCC)) #1 SMP PREEMPT Wed Feb 9 22:32:38 UTC 2022
[    0.000000] Command line: BOOT_IMAGE=(hd0,2)/boot/bzImage root=/dev/hda2 console=ttyS0,115200n8 rootwait
[    0.000000] x86/fpu: x87 FPU will use FXSAVE
[    0.000000] BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000007fffff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000800000-0x0000000000807fff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x0000000000808000-0x000000000080ffff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000810000-0x00000000008fffff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x0000000000900000-0x000000000f8eefff] usable
[    0.000000] BIOS-e820: [mem 0x000000000f8ef000-0x000000000f9eefff] reserved
[    0.000000] BIOS-e820: [mem 0x000000000f9ef000-0x000000000faeefff] type 20
[    0.000000] BIOS-e820: [mem 0x000000000faef000-0x000000000fb6efff] reserved
[    0.000000] BIOS-e820: [mem 0x000000000fb6f000-0x000000000fb7efff] ACPI data
[    0.000000] BIOS-e820: [mem 0x000000000fb7f000-0x000000000fbfefff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x000000000fbff000-0x000000000fef3fff] usable
[    0.000000] BIOS-e820: [mem 0x000000000fef4000-0x000000000ff77fff] reserved
[    0.000000] BIOS-e820: [mem 0x000000000ff78000-0x000000000fffffff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x00000000ffc00000-0x00000000ffffffff] reserved
[    0.000000] NX (Execute Disable) protection: active
[    0.000000] efi: EFI v2.70 by EDK II
[    0.000000] efi:  SMBIOS=0xf942000  ACPI=0xfb7e000  ACPI 2.0=0xfb7e014  MEMATTR=0xee68018
[    0.000000] SMBIOS 2.8 present.
[    0.000000] DMI: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 0.0.0 02/06/2015
[    0.000000] last_pfn = 0xfef4 max_arch_pfn = 0x400000000
[    0.000000] x86/PAT: Configuration [0-7]: WB  WC  UC- UC  WB  WP  UC- WT
[    0.000000] check: Scanning 1 areas for low memory corruption
[    0.000000] Secure boot could not be determined
[    0.000000] ACPI: Early table checksum verification disabled

(中略)

[  OK  ] Started Network Service.
         Starting Wait for Network to be Configured...
         Starting Network Name Resolution...

Poky (Yocto Project Reference Distro) 3.1.15 qemux86-64 ttyS0

qemux86-64 login:

という感じで、Docker HUBに保存されている以下のdockerイメージ mendersoftware/mender-client-qemu がダウンロード後起動されます。

https://hub.docker.com/r/mendersoftware/mender-client-qemu

これをビルドしているDockerfileは以下。別ディレクトリにあるスクリプトファイルも組み込まれます。(ビルドスクリプト build-docker を見ると x86_64版以外に 32bit ARM版もビルド出来そうな感じです...)

hhttps://github.com/mendersoftware/meta-mender/blob/master/meta-mender-qemu/docker/qemux86-64/Dockerfile

ユーザー root を利用するとパスワードなしでログイン出来ます。以下では mender-clientサービスmender-monitorサービス が動作していることを確認しています。Control-P Control-Qを入力し、開発用Linux PCのシェルに戻ります。

qemux86-64 login: root
root@qemux86-64:~# systemctl status mender-client
* mender-client.service - Mender OTA update service
     Loaded: loaded (/lib/systemd/system/mender-client.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2022-07-31 17:31:59 UTC; 22min ago
   Main PID: 265 (mender)
      Tasks: 14 (limit: 266)
     Memory: 26.7M
     CGroup: /system.slice/mender-client.service
             |-  265 /usr/bin/mender daemon
             |-21336 /bin/sh /usr/share/mender/identity/mender-device-identity
             `-21346 cat /sys/class/net/enp0s3/address

Jul 31 17:54:19 qemux86-64 mender[265]: time="2022-07-31T17:54:19Z" level=warning msg="Reauthorization failed with erro>
Jul 31 17:54:19 qemux86-64 mender[265]: time="2022-07-31T17:54:19Z" level=error msg="Failed to submit inventory data: t>
Jul 31 17:54:19 qemux86-64 mender[265]: time="2022-07-31T17:54:19Z" level=error msg="inventory submit failed: transient>
Jul 31 17:54:19 qemux86-64 mender[265]: time="2022-07-31T17:54:19Z" level=warning msg="Failed to refresh inventory: fai>
Jul 31 17:54:19 qemux86-64 mender[265]: time="2022-07-31T17:54:19Z" level=info msg="Handle update inventory retry state>
Jul 31 17:54:19 qemux86-64 mender[265]: time="2022-07-31T17:54:19Z" level=info msg="State transition: inventory-update->
Jul 31 17:54:19 qemux86-64 mender[265]: time="2022-07-31T17:54:19Z" level=info msg="State transition: check-wait [Idle]>
Jul 31 17:54:19 qemux86-64 mender[265]: time="2022-07-31T17:54:19Z" level=warning msg="Returning artifact name from /et>
Jul 31 17:54:19 qemux86-64 mender[265]: time="2022-07-31T17:54:19Z" level=info msg="Device unauthorized; attempting rea>
Jul 31 17:54:20 qemux86-64 mender[265]: time="2022-07-31T17:54:20Z" level=info msg="Output (stderr) from command \"/usr>
root@qemux86-64:~# systemctl status mender-connect
* mender-connect.service - Mender Connect service
     Loaded: loaded (/lib/systemd/system/mender-connect.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2022-07-31 17:31:59 UTC; 23min ago
   Main PID: 266 (mender-connect)
      Tasks: 10 (limit: 266)
     Memory: 11.6M
     CGroup: /system.slice/mender-connect.service
             `-266 /usr/bin/mender-connect daemon

Jul 31 17:31:59 qemux86-64 systemd[1]: Started Mender Connect service.
Jul 31 17:32:02 qemux86-64 mender-connect[266]: time="2022-07-31T17:32:02Z" level=info msg="Loaded configuration file: >
Jul 31 17:32:02 qemux86-64 mender-connect[266]: time="2022-07-31T17:32:02Z" level=warning msg="ShellArguments is empty,>
Jul 31 17:32:02 qemux86-64 mender-connect[266]: time="2022-07-31T17:32:02Z" level=warning msg="call to GetJWTToken on t>
root@qemux86-64:~# 

(ここで Control-P Control-Q を入力)

codegear@dell3070:~$
codegear@dell3070:~$ docker ps
CONTAINER ID   IMAGE                               COMMAND             CREATED          STATUS          PORTS                                         NAMES
2d5417fbd08f   mendersoftware/mender-client-qemu   "./entrypoint.sh"   25 minutes ago   Up 25 minutes   0.0.0.0:85->85/tcp, :::85->85/tcp, 8822/tcp   nostalgic_sinoussi
codegear@dell3070:~$

mender.io の WebUIでQEMUデバイスの接続を承認

dockerコンテナ実行中に Webブラウザからmender.ioの DASHBOARD を確認すると、新しくPending Devices が追加されているのがわかります。

第3回の記事と同様に画面上の「+」をクリックすると、DEVICES画面に移行し「pending」状態のデバイスが表示されます。

チェックボックスにチェックを入れると下のように右下に「+」ボタンが表示され、これにマウスオーバーすると「Accept Device」ボタンが現れるので、緑のチェックマーク部分を押します。

これでQEMUデバイスが mender.io と接続されました。上の方にある接続済デバイスの数のカウントが一つ増えました。

リモートターミナルを実行

先ほど確認したように、QEMUデバイスでは mender-monitorサービスが動作しています。ということは、DEVICES画面からこのQEMUデバイスを選んでリモートターミナルを実行可能なはずです。

DEVICES画面で Status: フィルタを accepted に変更すると、これまでに接続承認したデバイスが一覧表示されます。ここからDevice Typeが qemux86-64 のデバイス行を見つけてクリックします。

表示されるデバイス詳細画面の一番下にある Launch a new Remote Terminal session をクリックすると、リモートターミナルがブラウザ内で実行開始されます。実行終了するときは CLOSE ボタンを押します。

アプリケーションアップデートを実行

第4回記事 でやっていたのと同様に、mender.ioにあらかじめ登録されている mender-demo-artifact をこのQEMUデバイスに配信することが可能です。

RELEASES 画面から mender-demo-artifact-3.1.0 をクリックし、表示される CREATE DEPLOYMENT WITH THIS RELEASE ボタンを押して新規DEPLOYMENTを作成します。 (もし実行途中のDEPLOYMENTSが残っている場合には、あらかじめキャンセルしておいた方がわかりやすいかもしれません)

特にオプション変更をせずにDEPLOYMENTを設定し CREATEボタンを押すと、すぐにQEMUデバイス上で更新が実行され、ポート85上でWebサーバーが動き始めます。このポートは開発PCのポート85にマップされているので、開発PC上のWebブラウザから

http://localhost:85/

とアクセスするか、別のPCのWebブラウザから開発PCのIPアドレスを (例えば 192.168.5.40 だった場合は以下のように)指定して

http://192.168.5.40:85/

のようにアクセスすると、第4回記事と同様なデバイス情報が表示されます。

デバイスエミュレータの実行を終了

開発PCのシェルから docker ps で表示される CONTAINER IDを指定してdocker stop を実行すると、docker コンテナおよびqemux86-64の実行を終了します。

上記の例ではコンテナIDは 2d5417fbd08f だったので、

docker stop 2d5417fbd08f

と実行します。(停止までにちょっとだけ時間がかかります)

dockerコンテナを終了した時点で (特に保存するように設定していない場合)コンテナ内の操作結果はすべて廃棄されます。

qemux86-64 のdockerコンテナを再度実行するには、テナントトークンがシェル変数 TENANT_TOKEN に設定された状態で、

docker run -it -p 85:85 -e SERVER_URL='https://hosted.mender.io' -e TENANT_TOKEN=$TENANT_TOKEN --pull=always mendersoftware/mender-client-qemu

を実行します。ただしこのように再度実行するとQEMUデバイスのMACアドレスが前回とは変化するので、Menderサーバーからは別の新しいデバイスが接続に来ていると認識され、WebUIから再度認証しないと利用できません。

無効になったMACアドレスのデバイスは、MenderのWebUI上の DEVICES 画面から該当するデバイスをチェックして、Dismiss Device を実行することで削除できます。

この記事のまとめ

Linux開発PC上でQEMUエミュレータデバイスを動かし、Menderサーバーと接続することで、Menderの動作を確認することができます。これは特にサーバー側の開発時などには有用と考えられます。
ただしエミュレータデバイスである関係上、例えば更新結果が結局は保存されないなど、実デバイスの動作とはいくつかの相違点があります。

今後の予定

コードギアでは Menderで始める組み込みOTA のタイトルで以下のZenn記事を公開しています。

Quickstart編

接続編

これ以降の記事も準備中です。ご期待ください。

Discussion