(duplicated) Raspberry Pi + Yocto + docker + FastAPIでAPIサーバコンテナ
こちらの記事に統合しました。
作りたいもの
Raspberry Pi 4 (RasPi4)にYoctoを載せて、その上にDocker環境を構築し(手順はスクラップにあります)、その上にFastAPIを使ったAPIサーバコンテナを立ち上げます。
動作する全ソースコードをGitHubで公開しています。
Yoctoのコンテナイメージビルドについて
Yoctoのビルド内でコンテナイメージをビルドするには、Yoctoを使ってコンテナイメージをビルドすることになります。
コンテナイメージビルド
私の場合、fastapiを動かしたいので、gunicorn(WSGIサーバ) + uvicorn(gunicornのワーカーとして) + fastapi(APIサーバフレームワーク)を動かしたいです。このうちuvicornとfastapiはYocto 5.2にはマージされていないのでbbファイル(uvicorn、fastapi)を用意します。
最後にfastapi上のアプリケーション(API)としてsample-apiを用意します。レシピとソースのレポジトリを分けています。
最後にこれらをインストールしたコンテナイメージをビルドするレシピを作成します。
meta-virtualizationにお手本があるので、それをベースにします。
ここで注意が必要なのが、<build dir>/local.confでIMAGE_INSTALL:append
したものはコンテナイメージにもappendされてしまいます。
そこで<build dir>/local.confではIMAGE_INSTALL:append
の代わりにCORE_IMAGE_EXTRA_INSTALL:append
を使用しました。ドキュメントによると、これはlocal.confでしか使えないようなので、つまり他のbbファイル内からは参照されないのではないかと思います。実際にlocal.confのCORE_IMAGE_EXTRA_INSTALL:append
で追加したパッケージがコンテナイメージに追加されていないことを確認しました。
この時点でコンテナイメージのターゲットを指定するとコンテナイメージがビルドされます。github上のsampleの例では
bitbake sample-api-container-image
コンテナイメージビルドをターゲットイメージビルドに含め、イメージファイルをターゲットイメージにインストール
以下のようなbbファイルで実現しました。
do_install()にsample-api-container-imageのdo_image_complete()への依存を追加して、do_install時に${DEPLOY_DIR_IMAGE}/sample-api-container-image-${MACHINE}.rootfs.tar.bz2
が出来上がっていることを保証します。
そしてdo_install()でイメージファイル(ファイルシステムのtar.bz2)をターゲットイメージ上の/opt/sampleにインストールします。
SUMMARY = "bitbake-layers recipe"
DESCRIPTION = "Recipe created by bitbake-layers"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
do_install() {
install -d "${D}/opt/sample"
install -m 644 "${DEPLOY_DIR_IMAGE}/sample-api-container-image-${MACHINE}.rootfs.tar.bz2" "${D}/opt/sample"
}
FILES:${PN} = "/opt/sample/*"
DEPENDS = "sample-api-container-image"
do_install[depends] = "sample-api-container-image:do_image_complete"
このtar.bz2を実機の起動時にdocker import
し、docker run
でCMDを指定することで実行します。本当はビルド時にインポートまでしたかったのですが、うまくできませんでした。
参考
この記事を参考にしたのですが、yoctoビルド時にsudo使えず、またrootless-dockerも試しましたがyocto(bitbake)自身がユーザ名前空間を使用しているため、そのプロセスがさらにdockerdを起動してユーザ名前空間を使用できない(上限が1のため)ため諦めました。
ターゲット上での自動インポート・起動
以下のようなシェルスクリプトでインポート・コンテナの起動をしています。
docker imagesでイメージ一覧を取得し、規定の名前のイメージがまだ作成されていない場合はdocker import後にdocker runしています。
#!/bin/sh
TARGET_REPOSITORY=$1
TARGET_TAG=$2
IMAGE_FILE=$3
# trap subshell error, code 1 is SUCCESS_PUBLISH
set -E
trap '[ "$?" -ne 1 ] || exit 0' ERR
run_sample() {
docker run --rm --name $1 -p8080:8080 $1:$2 /usr/bin/run-sample-api.sh
}
docker images --format "table {{.Repository}} {{.Tag}}" |
while read row; do
repository=`echo ${row} | cut -d " " -f 1`
tag=`echo ${row} | cut -d " " -f 2`
if [[ "${repository}" == ${TARGET_REPOSITORY} ]]; then
if [[ "${tag}" == ${TARGET_TAG} ]]; then
run_sample ${TARGET_REPOSITORY} ${TARGET_TAG}
exit 1
fi
fi
done
docker import ${IMAGE_FILE} ${TARGET_REPOSITORY}:${TARGET_TAG}
run_sample ${TARGET_REPOSITORY} ${TARGET_TAG}
動作確認
dockerdが起動するまで少々時間がかかるようですが、無事に起動しました。以下のコマンドでAPIの動作が確認できます。
wget -O - http://<raspi addr>:8080/hello
...
{"response":"world!"}
docker-compose版はこちら
Discussion