Menderで始める組み込みOTA 第7回 : Quickstart Part6 : コンテナアップデートを実行
MenderはオープンソースベースのLinux向けOTA(Over the Air Update)ソリューションです。
「Menderで始める組み込みOTA」記事インデックス
- 第0回 : 組み込み製品、IoTシステムに適したOTAとは?
- 第1回 : Menderのご紹介
- 第2回 : Quickstart Part1 : mender.ioの試用アカウントを取得
- 第3回 : Quickstart Part2 : Raspberry Pi 3/4を接続
- 第4回 : Quickstart Part3 : アプリケーションを更新(その1)
- 第5回 : Quickstart Part4 : アプリケーションを更新(その2)
- 第6回 : Quickstart Part5 : OSアップデートを実行
- 第7回 : Quickstart Part6 : コンテナアップデートを実行 (本記事)
- 第8回 : Quickstart Part7 : リモートターミナル機能のご紹介
- 第9回 : 接続編1 : QEMUエミュレータを接続
- 第10回 : 接続編2 : Armadillo IoT G3を接続
- 第11回 : 接続編3 : Jetson AGX Orinを接続
はじめに
今回はDocker用コンテナイメージ更新情報を含むアップデートファイル (ARTIFACT)を自分で作成し、SaaSの mender.io に接続中のRaspberry Pi 3/4 デバイスに対してこれを配布します。基本的な流れは docs.mender.io にある「Deploy a container update」の内容と同じです。
今回も前提として、これまでの回で実行した mender.io との接続 (第2回と第3回)、アプリケーションアップデート (第4回と第5回) が完了していることが必要です。
作業用PCとしてLinuxのシェルが利用可能でかつDockerイメージの操作(docker pull等)が可能な環境が必要です。いわゆるLinux PCではもちろん実行可能ですが、Windows10/11 のWSL2も利用可能です。
Dockerについて
DockerはLinux環境で コンテナ仮想化 を実現するためのソフトウエア、またはエコシステムの名前です。Linux環境では仮想化を実現するためにcgroupsという機能がカーネルに実装されており、この機能を利用して仮想環境を作成する事ができます。Dockerはcgroupsを利用して実現されているコンテナ仮想化ソフトウエアの一つです。(このほかのコンテナ仮想化には LXC と呼ばれるものもあります)
Dockerの特徴は以下のようなものです。
- ホストOSとゲストOSが同じLinuxカーネルバイナリを利用する (つまり、カーネルに組み込まれているデバイスドライバは双方の環境で同じ)
- ユーザーランドはホストOSとおなじものではなく独自のもの(別のディストリビューションでもよい)を利用する。これが Dockerイメージ と呼ばれる。
- Dockerイメージは複数のレイヤーの集まりとして保存されている。同一のレイヤーが複数のイメージに含まれている場合、レイヤーは一回だけ保存される。
- Dockerイメージは通常、単一のビルドスクリプト Dockerfile で作成する。これにより、ディスクイメージではなくソースコードでイメージ設定を保存出来る。また、OSアップデート更新時にはDockerfileを利用して再度イメージをビルドするだけで反映される。
- ゲストOSからホストOSのファイルシステムのマウントが可能
- ゲストOSではホストOSとは独立したネットワークを利用可能、ホストOSや複数のコンテナ間を接続するローカルネットワークを構築できる
- 起動が速いため、(ビルドなど)単一コマンドのみを実行しすぐに終了するような使い方もされる。
インターネット上にはDockerイメージを保存するデータベース、Docker HUBが用意されており、公開・非公開のDockerイメージが多数保存されています。これにより一種のエコシステムが実現され、ユーザーは多数の公開Dockerイメージを自由に利用してシステムを構築することができます。
コンテナ仮想化はLinuxだけではなくWindowsやMacOSでも利用可能ですが、このDocker Hubの仕組みにより多数のDockerイメージ(これらはすべてLinuxが前提)が公開された結果、WindowsやMacOSでもそのままDocker HUBにあるLinux向けのイメージを利用するための仕組みが整備されました。
Linuxの場合、ディストリビューションごとにインストール方法が用意されています。
Generic installation steps
Windowsについては以下
WindowsにDocker Desktopをインストール
MacOSについては以下
MacにDocker Desktopをインストール
を参照ください。
実行環境のRaspberry Pi にDockerエンジンをインストール
以下の手順でターゲットのRaspberry Pi 3/4 に dockerエンジンをインストールします。インターネット接続が必要です。
- もし jq をインストールしていなければ、以下の手順で jq をインストールします。
sudo apt install jq
- Dockerをインストールするためのスクリプトをダウンロードします。
curl -fsSL https://get.docker.com -o get-docker.sh
- スクリプトを実行してインストールします。 (ダウンロード速度次第ですが、10分程度かかります)
sudo sh get-docker.sh
インストールが出来たら、いくつかのコマンドを実行してみます。
docker ps
docker image ls
など。
Windows10からsshでRaspberry Pi 3に接続してやってみると以下のような感じです。
Microsoft Windows [Version 10.0.19044.1766]
(c) Microsoft Corporation. All rights reserved.
C:\Users\hiron>ping raspberrypi.local
raspberrypi.local [2409:10:c860:1200:609e:bef7:a81b:6f4e]に ping を送信しています 32 バイトのデータ:
2409:10:c860:1200:609e:bef7:a81b:6f4e からの応答: 時間 =200ms
2409:10:c860:1200:609e:bef7:a81b:6f4e からの応答: 時間 =2ms
2409:10:c860:1200:609e:bef7:a81b:6f4e の ping 統計:
パケット数: 送信 = 2、受信 = 2、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
最小 = 2ms、最大 = 200ms、平均 = 101ms
Ctrl+C
^C
C:\Users\hiron>ping -4 raspberrypi.local
raspberrypi.local [192.168.5.18]に ping を送信しています 32 バイトのデータ:
192.168.5.18 からの応答: バイト数 =32 時間 =99ms TTL=64
192.168.5.18 からの応答: バイト数 =32 時間 =2ms TTL=64
192.168.5.18 からの応答: バイト数 =32 時間 =2ms TTL=64
192.168.5.18 からの応答: バイト数 =32 時間 =2ms TTL=64
192.168.5.18 の ping 統計:
パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
最小 = 2ms、最大 = 99ms、平均 = 26ms
C:\Users\hiron>ssh 192.168.5.18 -l pi
The authenticity of host '192.168.5.18 (192.168.5.18)' can't be established.
ECDSA key fingerprint is SHA256:tceZYhKKlvkodYJOgDWH4URYUZ+2K5IkKDpxpqZmR60.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes ← 「yes」を入力
Warning: Permanently added '192.168.5.18' (ECDSA) to the list of known hosts.
pi@192.168.5.18's password: ←piユーザーのパスワードを入力
Linux raspberrypi 5.10.63-v7+ #1459 SMP Wed Oct 6 16:41:10 BST 2021 armv7l
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Mon Jun 13 14:01:21 2022 from 192.168.5.20
SSH is enabled and the default password for the 'pi' user has not been changed.
This is a security risk - please login as the 'pi' user and type 'passwd' to set a new password.
Wi-Fi is currently blocked by rfkill.
Use raspi-config to set the country before use.
pi@raspberrypi:~ $ which jq
/usr/bin/jq ← 「jq」が既に存在しているので、そのまま実行
pi@raspberrypi:~ $ ls
mender-monitor-demo_latest-1_all.deb mender-monitor_latest-1_all.deb
pi@raspberrypi:~ $ curl -fsSL https://get.docker.com -o get-docker.sh
pi@raspberrypi:~ $ ls
get-docker.sh mender-monitor-demo_latest-1_all.deb mender-monitor_latest-1_all.deb
pi@raspberrypi:~ $ sudo sh get-docker.sh
# Executing docker install script, commit: b2e29ef7a9a89840d2333637f7d1900a83e7153f
+ sh -c apt-get update -qq >/dev/null
+ sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -qq apt-transport-https ca-certificates curl >/dev/null
+ sh -c mkdir -p /etc/apt/keyrings && chmod -R 0755 /etc/apt/keyrings
+ sh -c curl -fsSL "https://download.docker.com/linux/raspbian/gpg" | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
+ sh -c chmod a+r /etc/apt/keyrings/docker.gpg
+ sh -c echo "deb [arch=armhf signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/raspbian bullseye stable" > /etc/apt/sources.list.d/docker.list
+ sh -c apt-get update -qq >/dev/null
+ sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null
+ version_gte 20.10
+ [ -z ]
+ return 0
+ sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-ce-rootless-extras >/dev/null
+ sh -c docker version
Client: Docker Engine - Community
Version: 20.10.17
API version: 1.41
Go version: go1.17.11
Git commit: 100c701
Built: Mon Jun 6 23:03:29 2022
OS/Arch: linux/arm
Context: default
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 20.10.17
API version: 1.41 (minimum version 1.12)
Go version: go1.17.11
Git commit: a89b842
Built: Mon Jun 6 23:01:31 2022
OS/Arch: linux/arm
Experimental: false
containerd:
Version: 1.6.6
GitCommit: 10c12954828e7c7c9b6e0ea9b0c02b01407d3ae1
runc:
Version: 1.1.2
GitCommit: v1.1.2-0-ga916309
docker-init:
Version: 0.19.0
GitCommit: de40ad0
================================================================================
To run Docker as a non-privileged user, consider setting up the
Docker daemon in rootless mode for your user:
dockerd-rootless-setuptool.sh install
Visit https://docs.docker.com/go/rootless/ to learn about rootless mode.
To run the Docker daemon as a fully privileged service, but granting non-root
users access, refer to https://docs.docker.com/go/daemon-access/
WARNING: Access to the remote API on a privileged Docker daemon is equivalent
to root access on the host. Refer to the 'Docker daemon attack surface'
documentation for details: https://docs.docker.com/go/attack-surface/
================================================================================
pi@raspberrypi:~ $ which docker
/usr/bin/docker
pi@raspberrypi:~ $ docker ps
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json": dial unix /var/run/docker.sock: connect: permission denied
pi@raspberrypi:~ $ sudo !!
sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
pi@raspberrypi:~ $ sudo docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
pi@raspberrypi:~ $ exit
logout
Connection to 192.168.5.18 closed.
C:\Users\hiron>
以上で raspberry pi に Dockerエンジンがインストールされました。
開発PCで前提条件を確認
- もし開発PCに jq をインストールしていなければ、以下のように jq をインストールします。
codegear@dell3070:~$ which jq ← 何も出ないのでjqはインストールされていない
codegear@dell3070:~$ sudo apt install jq
[sudo] codegear のパスワード:
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
gyp libc-ares2 libfprint-2-tod1 libfwupdplugin1 libjs-inherits libjs-is-typedarray libjs-psl
libjs-typedarray-to-buffer libllvm10 libnode-dev libnode64 libssl-dev libuv1-dev linux-headers-5.13.0-41-generic
linux-hwe-5.13-headers-5.13.0-41 linux-image-5.13.0-41-generic linux-modules-5.13.0-41-generic
linux-modules-extra-5.13.0-41-generic nodejs-doc python-pkg-resources
これを削除するには 'sudo apt autoremove' を利用してください。
以下の追加パッケージがインストールされます:
libjq1 libonig5
以下のパッケージが新たにインストールされます:
jq libjq1 libonig5
アップグレード: 0 個、新規インストール: 3 個、削除: 0 個、保留: 74 個。
313 kB のアーカイブを取得する必要があります。
この操作後に追加で 1,062 kB のディスク容量が消費されます。
続行しますか? [Y/n] y ← y (ENTER)を入力
(中略)
codegear@dell3070:~$ which jq
/usr/bin/jq
codegear@dell3070:~$
- もし開発環境に docker engine をインストールしていなければ、docker engine をインストールします。
codegear@dell3070:~$ which docker
/usr/bin/docker
codegear@dell3070:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
codegear@dell3070:~$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
codegear@dell3070:~$
docker engine がインストールされている場合、上記コマンドが正常に実行されます。
そうでない場合は Dockerについて の記述を参考にインストールしてください。
開発PCに mender-artifact をダウンロード・インストール
mender-artifact をまだダウンロード・インストールしていない場合は、第6回記事の「mender-artifact をダウンロード」の内容を参考に $HOME/bin に mender-artifactをインストールし、使用可能な状態にしておきます。
すでに使用可能かどうかは以下のように which コマンドで確認できます。
codegear@dell3070:~$ which mender-artifact
/home/codegear/bin/mender-artifact ← 既にインストールされている
codegear@dell3070:~$
開発PCに作業ディレクトリを作成しARTIFACTを作成
開発PCに一時的な作業ディレクトリを作成し、docker-artifact-gen スクリプトをダウンロード後、これを実行して dockerイメージ更新用の ARTIFACTを作成します。
以下では mender-docker という名前で一時ディレクトリを作成しています。
mkdir ~/mender-docker
cd ~/mender-docker
docker-artifact-gen スクリプトをダウンロード
wget https://raw.githubusercontent.com/mendersoftware/mender/3.3.0/support/modules-artifact-gen/docker-artifact-gen
スクリプトを実行可能に設定
chmod +x docker-artifact-gen
ARTIFACTに設定するターゲットの DEVICE_TYPE をシェル変数に設定します。
ここでは raspberrypi3 に設定してみました。
DEVICE_TYPE="raspberrypi3"
以下のように ARTIFACTを作成します。
./docker-artifact-gen \
-n "hello-world-container-update" \
-t "${DEVICE_TYPE}" \
-o "hello-world-container-update.mender" \
"hello-world"
この手順を実際にやってみると以下のようになります。
codegear@dell3070:~$ mkdir ~/mender-docker
codegear@dell3070:~$ cd !$
cd ~/mender-docker
codegear@dell3070:~/mender-docker$ wget https://raw.githubusercontent.com/mendersoftware/mender/3.3.0/support/modules-artifact-gen/docker-artifact-gen
--2022-06-22 18:10:14-- https://raw.githubusercontent.com/mendersoftware/mender/3.3.0/support/modules-artifact-gen/docker-artifact-gen
raw.githubusercontent.com (raw.githubusercontent.com) をDNSに問いあわせています... 2606:50c0:8000::154, 2606:50c0:8001::154, 2606:50c0:8002::154, ...
raw.githubusercontent.com (raw.githubusercontent.com)|2606:50c0:8000::154|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 3818 (3.7K) [text/plain]
`docker-artifact-gen' に保存中
docker-artifact-gen 100%[=================================================>] 3.73K --.-KB/s in 0s
2022-06-22 18:10:14 (22.0 MB/s) - `docker-artifact-gen' へ保存完了 [3818/3818]
codegear@dell3070:~/mender-docker$ ls -l
合計 4
-rw-rw-r-- 1 codegear codegear 3818 6月 22 18:10 docker-artifact-gen
codegear@dell3070:~/mender-docker$ chmod +x docker-artifact-gen
codegear@dell3070:~/mender-docker$ ls -l
合計 4
-rwxrwxr-x 1 codegear codegear 3818 6月 22 18:10 docker-artifact-gen
codegear@dell3070:~/mender-docker$ DEVICE_TYPE="raspberrypi3"
codegear@dell3070:~/mender-docker$ echo $DEVICE_TYPE
raspberrypi3
codegear@dell3070:~/mender-docker$ ./docker-artifact-gen \
> -n "hello-world-container-update" \
> -t "${DEVICE_TYPE}" \
> -o "hello-world-container-update.mender" \
> "hello-world"
Using default tag: latest
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:13e367d31ae85359f42d637adf6da428f76d75dc9afeb3c21faea0d976f5c651
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
Artifact hello-world-container-update.mender generated successfully:
Reading Artifact...
.............................................................. - 100 %
Mender artifact:
Name: hello-world-container-update
Format: mender
Version: 3
Signature: no signature
Compatible devices: '[raspberrypi3]'
Provides group:
Depends on one of artifact(s): []
Depends on one of group(s): []
State scripts:
Updates:
0:
Type: docker
Provides:
rootfs-image.docker.version: hello-world-container-update
Depends: Nothing
Clears Provides: ["rootfs-image.docker.*"]
Metadata:
{
"containers": [
"hello-world@sha256:13e367d31ae85359f42d637adf6da428f76d75dc9afeb3c21faea0d976f5c651"
],
"run_args": ""
}
Files: None
codegear@dell3070:~/mender-docker$ echo $?
0
codegear@dell3070:~/mender-docker$ ls -l
合計 12
-rwxrwxr-x 1 codegear codegear 3818 6月 22 18:10 docker-artifact-gen
-rw-rw-r-- 1 codegear codegear 5120 6月 22 18:27 hello-world-container-update.mender
codegear@dell3070:~/mender-docker$
5120 バイトの ARTIFACTファイル hello-world-container-update.mender
が作成されました。このARTIFACTには実際にはDocker HUBからダウンロードするDockerイメージの参照情報だけが含まれています。
ダウンロード対象の hello-world
イメージはDockerのインストール確認によく使われる以下のイメージです。
Docker HUBのイメージ
イメージビルドを行うためのGitHubは以下
docker-artifact-gen スクリプトの内部では以下のように mender-artifact を呼び出して ARTIFACTを作成します。
つまり docker
というPAYLOAD TYPEの ARTIFACT を作成しています。
mender-artifact write module-image \
-T docker \
$device_types \
-o $output_path \
-n $artifact_name \
-m $meta_data_file \
$passthrough_args
コンテナアップデートを実行
作成したアップデートファイル (ARTIFACT)を SaaSの mender.io にアップロードしOTAを実行します。
開発用PCからWebUI に ログイン し、DEVICES画面でターゲットのRaspberry Pi が接続中であることを確認後、RELEASES画面の下部にある UPLOAD ボタンを押します。すると以下のアップロードダイアログが表示されるので、Drag here or browse to upload an Artifact tile の部分をクリックするか、エクスプローラなどからドロップして先ほど作成した hello-world-container-update.mender
を登録します。
確認画面が出るので UPLOAD ボタンを押します。
アップロードが出来たら、この hello-world-container-update.mender
を選択して表示される CREATE DEPLOYMENT WITH THIS RELEASE ボタンを押して、DEPLOYMENT (OTAジョブ)を作成します。
前回同様にデフォルト設定で DEPLOYMENTを作成し、ダイアログ場面の最後で CREATE ボタンを押すとOTAジョブが登録され、数分以内にRaspberry Pi 3 デバイスで実行完了します。
View details ボタンを押すと、Raspberry pi 3 でOTA実行成功している事がわかります。
Raspberry Pi上で実行結果を確認
今回ダウンロードしたDockerイメージ hello-world
は実行するとすぐに終了する設定になっています。docker image ls
コマンドでイメージがダウンロードされていることを確認し、docker ps -a
コマンドで実行ログを確認します。
pi@raspberrypi:~ $ sudo docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world <none> 1ec996c686eb 8 months ago 4.85kB
pi@raspberrypi:~ $ sudo docker ps ← 常駐して実行を継続するモノではないので、現在は実行していない
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
pi@raspberrypi:~ $ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8c424da521a9 hello-world "/hello" About a minute ago Exited (0) About a minute ago condescending_curran
pi@raspberrypi:~ $
これでデバイスにイメージがダウンロードされ、(一度は)実行された事が確認されました。
以下のようにコマンドラインから再度実行することもできます。(TAGが指定されていないため、ここではイメージIDを指定して実行しています)
注: docker run
コマンドは、イメージがローカルに存在しないと勝手にダウンロードを試みるため、OTAでダウンロードしたイメージを正しく実行するにはちょっとした注意が必要です。
pi@raspberrypi:~ $ sudo docker run 1ec996c686eb
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm32v7)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
pi@raspberrypi:~ $
もしデバイスにすでに存在するDockerイメージのバージョンアップを行う場合は、タグ を利用して hello-world:latest
のようにイメージを指定すると、そのハッシュ値がARTIFACTに保存されます。このハッシュ値のイメージがデバイスに存在しない場合、OTA後にdocker pullによるダウンロードが行われますが、このときDockerイメージのレイヤー構造により、存在しないレイヤーだけがダウンロードされることが期待されます。
この記事のまとめ
Menderを利用して Docker用コンテナイメージをダウンロード&実行することが出来ました。Dockerイメージは Menderのサーバーからダウンロードされるのではなく、Docker HUB (やほかのイメージレジストリ)から直接ダウンロードされます。そのため、ダウンロードを指定するARTIFACTのサイズは非常に小さくなります。Dockerイメージをさらに更新する場合は、Dockerイメージのレイヤー構造を活かすことでダウンロード容量を減らすことができます。
Menderではdocker
PAYLOADタイプ以外にも以下のようなPAYLOADタイプを利用可能です。
- rootfs-image
- single-file (single-file-artifact-genを利用して生成)
- directory (実際にはtarファイル、directory-artifact-genを利用して生成)
- bootloader
- firmware
これ以外にも任意のPAYLOADタイプを独自に拡張して利用可能です。
MENDER HUB の Update Modules カテゴリではこのようなPAYLOADタイプの拡張について情報交換が行われています。
今後の予定
コードギアでは 「Menderで始める組み込みOTA」 のタイトルで以下のZenn記事を公開しました。
- 第0回 : 組み込み製品、IoTシステムに適したOTAとは?
- 第1回 : Menderのご紹介
- 第2回 : Quickstart Part1 : mender.ioの試用アカウントを取得
- 第3回 : Quickstart Part2 : Raspberry Pi 3/4を接続
- 第4回 : Quickstart Part3 : アプリケーションを更新(その1)
- 第5回 : Quickstart Part4 : アプリケーションを更新(その2)
- 第6回 : Quickstart Part5 : OSアップデートを実行
- 第7回 : Quickstart Part6 : コンテナアップデートを実行 (本記事)
- 第8回 : Quickstart Part7 : リモートターミナル機能のご紹介
- 第9回 : 接続編1 : QEMUエミュレータを接続
- 第10回 : 接続編2 : Armadillo IoT G3を接続
これ以降の記事も準備中です。ご期待ください。
Discussion