🎃

(duplicated) Raspberry Pi + Yocto + docker + FastAPIでAPIサーバコンテナ

に公開

こちらの記事に統合しました。
https://zenn.dev/takumique/articles/975d70fada1d16

作りたいもの

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ファイル(uvicornfastapi)を用意します。

最後に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版はこちら

https://zenn.dev/takumique/articles/e2dcb85f32c1aa

Discussion