[SkiffOS] 軽量で高信頼の組込 Linux 上で Docker コンテナを動かす

(更新

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 さんによるヘルプ)

今日はここまで。次回は、何か Docker イメージを用意して、SkiffOS のビルド手順に組み込む勉強をしてみたいと想っています。

お問い合わせはお気軽に!

お問い合わせを頂いた後、継続して営業活動をしたり、ニュースレター等をお送りしたりすることはございません。
御返答は 24時間以内(営業時間中)とさせて頂いております。必ず返信致しますが、時々アドレス誤りと思われる返信エラーがございます。返答が届かない場合、大変お手数ではございますが別のメールアドレスで督促頂けますと幸いです。