Running Docker containers on minimal and reliable embedded Linux.
メーカーなどで組込ソフトウェアを設計なさる技術者の方にとって、いつも頭を悩ませる、次のような問題があるかと想います。
- お客様に納品済みの製品に対して、製品出荷後のソフトウェアの修正を、どのように配布および適用するか
- フィールドでソフトウェアを更新中に失敗して、現場に技術者を派遣しなくてはならなくなったり、あるいは多くの製品が返送されてきたりしないか
- ソフトウェアのレグレッション(デグレ)により、その後のソフトウェア更新ができなくなったりしないか
マイコンでリアルタイム OS などを動かす場合
組込リアルタイム OS などが動作するマイコン製品では、マイコン製品メーカー自身で、これらの問題に対する解法(OTA)を提供しているところが増えています。例えば、ESP32 マイコンで有名な Espressif 社は、次のようなページで情報を展開しています。
そこで紹介されている技術を利用すると、遠隔からのソフトウェア更新の実現だけでなく、複数のバージョンのソフトウェアをフラッシュメモリ上に 2面用意しておき、ソフトウェアの更新中に電源が切れるようなことがあってもソフトウェアが起動しなくなることがないようなことを、比較的容易に実現できます。また、仮に無事に更新できたとしても、何か不具合(既存の設定内容と新しいバージョンのソフトウェアの間に不整合があるなど)で新しいバージョンのソフトウェアがクラッシュしてしまうような場合に、古いバージョンのソフトウェアに戻す(ロールバック)についても、解決法が提供されています。
アプリケーションプロセッサ(SoC)などで Linux OS を動かす場合
最近は組込システムなどでも、Linux を必要とするような比較的規模の大きな製品が増えてきています。特に、近年広まりを見せている機械学習(ML)では、TensorFlow や PyTorch といった規模の大きなフレームワークを必要とする場合もあるでしょう。さらに、Docker などのコンテナ化技術の普及にともなって、組込システムでもコンテナ化技術の導入を検討している企業が増えているのではないかと思います。
しかしながら、大企業であれば半導体メーカーの手厚いサポートを元に、Linux システムの安全な OTA などについて、設計支援を受けることができるかも知れませんが(例えばスマートフォンの設計など)、われわれのような中小規模の設計技術者にとっては、Linux の OTA、またコンテナ化技術の導入は、まだまだハードルが高いところだと思います。
SkiffOS の登場
そのような中、まだまだ日本国内では知名度は低いものの、SkiffOS という実験的なプロジェクトに注目が集まっています。
SkiffOS は、米国の Christian Stewart(paralin)さんという方が 2016年頃より始められたプロジェクトで、以下のような特徴を持っています。
- 組込 Linux のカーネルイメージ、ユーザーランド、各種アプリケーションを(比較的)簡単にビルドすることのできる Buildroot プロジェクトをベースにしてます。(ファームロジックスの個人的な経験では、これまた有名な OpenEmbedded Yocto Project よりも Buildroot のほうが取っつきやすい印象です。)
- さらに、Docker のコンテナ化技術を採用してます。動作させるハードウェアアーキテクチャとは独立に、Docker コンテナとして、Debian、Gentoo、Fedora、Alpine など、各種の Linux ディストリビューションを動作させることができます。もちろん、自前のコンテナを実行することもできます。
- ハードウェアアーキテクチャ、シングルボードコンピュータとしては、Raspberry Pi、NVIDIA Jetron、PinePhone、Intel Desktop など多数に対応しているほか、QEMU などのエミュレーション上でも動作させることができます。また、RISC-V への対応も始まっています。さらに驚くことに、(詳細は後述しますが)ウェブブラウザでもエミュレーション動作させることができます。ブラウザ上で Docker が動いているのを見るのは、私にとって極めて新鮮で驚きの体験でした。
- ベースとなる Linux OS は、最小限のユーザーランドを、リードオンリーのメモリから RAM 上に展開して動作させることで、高信頼化を実現しています。ベースシステムのアップデートや、ロギング、Docker イメージの展開、アプリケーション自身による要求(データベースアプリなど)などがない限り、フラッシュ ROM に書き込むことなく動作できるので、フラッシュ ROM の劣化(wear)を最小限に抑えられます。
- フラッシュ ROM などに書き込むことになるイメージ(ベース Linux、Docker の設定ファイルなどを含む)の生成(ビルド)を、make コマンドによるシステマティックな方法で実現しているため、ヒューマンエラーによる、ファイルの展開忘れ、設定ミスなどを防ぐことができます。
詳しくは、こちらの GitHub サイトや、論文(PDF)を御参考ください。
なお、現状では信頼性の高い OTA、ロールバック等にはまだ対応できていないようです。一つは、これらの技術は、ベースとなるアプリケーションプロセッサ(SoC)等の設計、ブートの仕組などに大きく依存するため、まだ明確な対応方針が決められていない、ということがあるのではないかと思います。ただし、paralin さんも OTA については、いろいろ検討なさっているようです。(参考: investigate OTA and un-interrupted updates · Issue #12 · skiffos/SkiffOS)
実際に動かしてみよう!
まずは実際に動かして、雰囲気を見てみましょう。
余談(tl; dr)ですが、私は 2022年上旬頃に一度、Raspberry Pi 上で評価をしたことがあります。しかしながら、Raspberry Pi を用意して LAN に接続したり、イメージファイルを SD カードに書き込んだりという作業が面倒で、特に、実験で設定ファイルを壊してしまったりしたことから、しばらく SkiffOS の評価から離れていました。しかしながら、組込システムにおけるコンテナ化技術がこれから普及するであろうことを考えると、SkiffOS の技術をフォローしておくことが重要ではないかと思うようになり、再評価することにした次第です。
最も手軽に動かす方法(ウェブブラウザ上で実行)
前述しましたが、驚くべきことに SkiffOS はウェブブラウザでも動作します。これを試してみましょう。
ブラウザ上のエミュレーションには、Fabian さんという方を中心に開発されているv86 というソフトウェアを利用しています。ウェブブラウザとしては、最近のバージョンの Google Chrome や Mozilla Firefox などが利用できると思います。(私は Google Chrome で試しました。)
SkiffOS の GitHub 上でも紹介されている、こちらのリンクをクリックしましょう。起動には少々時間がかかりますが、最終的に次のような画面が表示されるかと思います。信じられない方もあるかと想いますが、この SkiffOS(Linux)は、どこか遠隔のクラウドで動作しているのでなく、目の前にあるパソコン上のウェブブラウザで、WebAssembly という技術を使って動作しています。
ログイン操作には、下半分に表示されるテキストボックスを使用します。早速 root でログインしてみましょう。
少しコマンドを実行した結果が、次の画面です。
それでは、Docker イメージとして実現されている skiff/core を動かしてみましょう。SkiffOS では、搭載された Docker イメージに、専用のユーザーアカウントでアクセスするという興味深い方法を採用しています。例えば、soff/core コンテナには、ユーザー core にログインすることで接続できます。やってみましょう。
最初の実行では、Docker イメージを pull してくるので、ネットワークの速度によっては少々時間がかかります。
この画面を見ると、一つ前の画面で表示しているベース OS と同じように見えますが、こちらは Docker コンテナの中で動いている Alpine OS です。Docker の中なので、docker コマンドは使えません。
本当に Alpine なの? ということで、もう少しコマンドを実行してみましょう。
Alpine の apk コマンドが動くことが分かりますね!
ベース OS に戻るには、exit するだけです。exit しても、skiff/core(Alpine コンテナ)はバックグランドで動いています。
素晴らしい! 下はオマケの neofetch です。(ベース OS 上で実行)
その次に手軽な方法(QEMU 上で実行)
上記の v86 上での実行は簡単に評価できて面白いですが、やはり、保存結果がファイルシステム上に残るようにしてみたいですね。また、SkiffOS のビルド方法も勉強してみたいところです。
いろいろ(virt/virtualbox、VMware 上での intel/desktop など)試してみたのですが、私がちゃんと動かせたのは QEMU(virt/qemu)のみでした。それも、paralin さんに何度もヘルプを頂きながら、です。以下では、その顛末を紹介させて頂きます。御笑納ください。
ビルド用に Ubuntu OS の用意
私はふだん macOS を利用していますが、SkiffOS や、そこで利用している Buildroot、ひいては Linux カーネルのビルドにはホスト Linux OS が必要なので用意します。私は使い慣れている Ubuntu(Debian でも良いと想いますが)を使いました。たまたま、手持ちの VMware Fusion 上に Ubuntu 18.04.6 LTS の仮想マシン(VM)があったので、それを利用することにしました。
余談ですが、実は Ubuntu 18 でインストールされている標準の GCC はバージョン 7 と古く、後でこれが問題となるのですが、そのときは全く気付いていませんでした。
Buildroot に必要なツールのインストール
SkiffOS は Buildroot というソフトウェアを利用しているので、Buildroot が動くように準備します。こちらを参考にして、必要なコマンドがインストールされていることを確認し、なければパッケージマネージャ(apt など)でインストールしましょう。
さて。Buildroot の説明では GCC の 4.8 以降が必要であると記載されていますが、実は、QEMU 上で動作する SkiffOS をビルドするにはこれではダメで、最低でも GCC version 8 と、G++ version 8 が必要です。gcc --version を実行してみて、もしバージョンが古ければ、(Debian、あるいは Ubuntu を利用の方は)以下の情報を参考に GCC と G++ の version 8 を利用できるようにしてください。
なお、私が paralin さんに助けて貰った後に、paralin さんが GitHub 上の QEMU ビルド情報に「GCC バージョン 8 が必要だよ」と書き加えてくださったのですが、G++ バージョン 8 も必要ですので、忘れずにインストールしてください。
また、SkiffOS をビルドしていくと、いくつか追加のツールが必要になることが分かります。そのときは慌てずに検索エンジンで調べて、必要なツールをインストールしてください。その後、再度 make compile すればビルドを継続できます。
SkiffOS ソースツリーの展開
さて、ホスト Linux の準備ができたら、SkiffOS を git clone します。もちろん、こちらのページからですね。
次に、ビルドするバージョンの選択です。私は当初、タグ 2022.11.1 を使ったのですが、そのバージョンでは QEMU 用のビルド(virt/qemu)はうまくいきませんでした。最終的には、paralin さんのアドバイスに従ってコミット 35e6671 を利用しました。実は、そこまでにはいろいろ経緯があったのですが、皆さんはとりあえずこれをお試ししてください。paralin さんいわく、次の stable リリースを準備中だそうですので、リリースのあかつきには、皆さんはそちらを利用なさっても良いかと想います。
コンフィグレーションとコンパイル
まず最初に、普段使っている PC の SSH の公開鍵をコピーしておきます。そうすることで、最終的に外部から ssh コマンドで SkiffOS や SkiffOS 上の Docker コンテナにアクセスできるようになります。
$ cp ~/.ssh/*.pub ./overrides/root_overlay/etc/skiff/authorized_keys/
コンフィグレーションとコンパイルは、SkiffOS の virt/qemu の指示に従います。(なお、ここで skiff/core を追加しておくと、Docker や Alpine Linux を利用できるようになるのでそうします。)
$ export SKIFF_CONFIG=intel/x64,virt/qemu,skiff/core
$ make configure compile
コンパイルにはかなりの時間がかかります。私の PC(Intel i7-3770 @ 3.40GHz)上の VMware ゲスト OS(2コア割り当て、メモリ 8GB)にて、4〜5時間といったところでしょうか。そのほとんどが、Buildroot による Linux カーネルとユーザーランドのビルドに費やされているものと想います。寝る前、外出時などの時間を使うと良いでしょう。(老婆心)
QEMU 上での実行
ビルドが終わったら、以下を実行します。
$ make cmd/virt/qemu/run
無事に動作しましたでしょうか。root のパスワードは設定されていないので、パスワード無しでログインできます。
Welcome to SkiffOS 2022.11.1-110-g35e66718! skiffos-dabaa628 login: root Last login: Tue Mar 7 03:52:50 on ttyS0 root@skiffos-dabaa628:~#
カーネルからいくつかエラー(警告?)が出るかも知れませんが、なんとか無事に動いているようです。次回の stable 版リリースまでに解決したら嬉しいのですが。(なお、私は少しばかり独自の git commit をしているので、上のバージョン番号はオリジナルと異なります。)
あと、先ほど v86 上で動作させたのと同様のことが実行できますので、お試しください。
ローカル PC 上の QEMU で動かす
ここまでで SkiffOS を勉強するための環境は構築できましたが、もう少し改善しましょう。一つは、この SkiffOS を、VMware の外で動かそうというものです。現在は、VMware 上の QEMU で SkiffOS を動かしてますが、Windows 上や macOS 上の QEMU でも動作するはずだと想うので、やってみます。
まず最初に、ホスト OS に QEMU をインストールしましょう。macOS でしたら、Homebrew を使うのが簡単でしょう。
続いて、ファイル configs/virt/qemu/scripts/execute_qemu.sh を参考に、次のファイルをローカル PC の作業ディレクトリに(ディレクトリ構造を無視して)コピーします。
- workspaces/default/images/bzImage
- workspaces/qemu/images/rootfs.cpio.lz4
- workspaces/qemu/qemu-exec/qemu-persist.qcow2
コピー先が例えば $HOME/qemu_files/ だとしたら、次のようなシェルスクリプトを用意します。(configs/virt/qemu/scripts/execute_qemu.sh を参考にしてますが、ホスト OS から SSH ログインできるよう、hostfwd を指定しています。)
#!/bin/sh
DIR=$HOME/qemu_files
qemu-system-x86_64 \
-smp 1 \
-m "size=2g" \
-nographic -serial mon:stdio \
-device virtio-rng-pci \
-device virtio-net,netdev=vmnic \
-netdev user,id=vmnic,hostfwd=tcp::2022-:22 \
-kernel $DIR/bzImage \
-initrd $DIR/rootfs.cpio.lz4 \
-append "console=ttyS0 root=/dev/ram0" \
-drive file=$DIR/qemu-persist.qcow2,if=virtio \
-virtfs local,path=.,mount_tag=host0,security_model=mapped,id=host0
このシェルスクリプトを実行すると、macOS 上の QEMU でも動作することが確認できました。SSH ログインも試してみましょう。
root で SSH すると SkiffOS に、また、core で SSH すると、Docker コンテナの Debian にログインできることが分かります。
消費リソースについて
少しばかり、イメージサイズやメモリ消費量について見てみましょう。
まずは、QEMU に渡しているファイル群です。
macOS$ ls -lSrh total 5790288 -rw-r--r-- 1 yokoyama staff 13M 3 6 12:06 bzImage -rw-r--r-- 1 yokoyama staff 308M 3 6 12:11 rootfs.cpio.lz4 -rw-r--r-- 1 yokoyama staff 2.4G 3 7 13:15 qemu-persist.qcow2
次に、ベース OS 上で df してみましょう。
aroot@skiffos-dabaa628:~# df -m Filesystem 1M-blocks Used Available Use% Mounted on devtmpfs 835 0 835 0% /dev tmpfs 993 0 993 0% /dev/shm tmpfs 397 1 397 1% /run tmpfs 4 0 4 0% /sys/fs/cgroup /dev/vda1 32076 1855 28567 7% /mnt/persist tmpfs 993 0 993 0% /tmp overlay 32076 1855 28567 7% /etc/NetworkManager/system-connections host0 1069182 897396 148000 86% /mnt/shared overlay 32076 1855 28567 7% /mnt/persist/skiff/docker/overlay2/daf0a8558e8075834ded914b914b216c9217d9a99cab806b21d458f8c5567042/merged
persist パーティションの消費量は、1.8 G バイト程度のようです。(ちなみに、私のほうで少し apt でアプリをインストールする実験をしたので、インストール直後はもう少し小さいかも知れません。)
いずれにしても、現状の Linux ワンボードコンピュータ(Raspberry Pi など)用としては、十分に小さなサイズと言えるかと想います。
次にメモリ関連です。まずは、docker stats を見てみましょう。
root@skiffos-dabaa628:~# docker stats --no-stream CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS a69cc9889c80 core 0.09% 100.5MiB / 1.938GiB 5.06% 0B / 0B 0B / 0B 22
以下は top コマンドの結果です。%MEM 順にソートしています。
top - 04:38:06 up 46 min, 0 users, load average: 0.17, 0.08, 0.08 Tasks: 81 total, 1 running, 80 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.6/3.2 4[ ] KiB Mem : 2031664 total, 913912 free, 165384 used, 952368 buff/cache KiB Swap: 2097148 total, 2097148 free, 0 used. 1073940 avail Mem PID USER PR NI VIRT RES %CPU %MEM TIME+ S COMMAND 610 dbus 20 0 590.4m 103.3m 0.0 5.2 0:17.26 S /usr/sbin/ligh+ 580 root 20 0 339.6m 77.1m 0.0 3.9 0:05.29 S /usr/lib/xorg/+ 330 root 20 0 1273.9m 69.0m 0.0 3.5 0:08.61 S /usr/bin/docke+ 307 root 20 0 1320.3m 41.8m 0.0 2.1 0:08.14 S /usr/bin/conta+ 259 root 20 0 243.6m 17.0m 0.0 0.9 0:03.13 S /usr/sbin/Netw+ 1078 core 20 0 1140.3m 15.3m 0.0 0.8 0:00.58 S -skiff-core (snip)
最後に /proc/meminfo を見てみます。
root@skiffos-dabaa628:~# cat /proc/meminfo MemTotal: 2031664 kB MemFree: 913912 kB MemAvailable: 1073816 kB Buffers: 10588 kB Cached: 914020 kB SwapCached: 0 kB Active: 32136 kB Inactive: 245500 kB Active(anon): 744 kB Inactive(anon): 115524 kB Active(file): 31392 kB Inactive(file): 129976 kB Unevictable: 757148 kB Mlocked: 0 kB SwapTotal: 2097148 kB SwapFree: 2097148 kB Dirty: 8 kB Writeback: 0 kB AnonPages: 110228 kB Mapped: 244132 kB Shmem: 6084 kB KReclaimable: 27644 kB Slab: 42372 kB SReclaimable: 27644 kB SUnreclaim: 14728 kB KernelStack: 2028 kB PageTables: 3300 kB SecPageTables: 0 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 3112980 kB Committed_AS: 826036 kB VmallocTotal: 34359738367 kB VmallocUsed: 25076 kB VmallocChunk: 0 kB Percpu: 392 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB DirectMap4k: 51060 kB DirectMap2M: 2045952 kB
私は全ての表示フィールドを理解している訳ではありませんが、皆様の御参考になれば幸いです。
参考(paralin さんによるヘルプ)
- ${BUILDROOT_DIR}/host/bin/qemu-system is missing · Issue #274 · skiffos/SkiffOS
- virt/qemu: intel/x64: bash segfault when logging in with tty · Issue #275 · skiffos/SkiffOS
- SSH root login fails depending on environment variable LANG · Issue #276 · skiffos/SkiffOS
今日はここまで。次回は、何か Docker イメージを用意して、SkiffOS のビルド手順に組み込む勉強をしてみたいと想っています。