Raspberry Pi + Yocto + Docker + FastAPI + Celeryでエッジサービスの実装
作りたいものの全体像から参照しています
作りたいもの
Raspberry Pi 4 (RasPi4)にYoctoを載せて、その上にdocker/docker-compose環境を構築し、その上にAPI+コアロジックを立ち上げます。
2個の自作コンテナと1個のdocker-hubからダウンロードするコンテナをdocker-composeで実行します。
- 自作コンテナ
- sample-api: gunicorn + uvicorn + fastapiのAPIサーバ
- sample-core: Celeryのワーカーとなるコアロジックサーバ
- docker-hubコンテナ
- redis: Celeryのバックエンドとなるインメモリメッセージキュー
シングルマシン上でAPIとコアロジックを疎に分離する理由は、
- コードの保守性の向上
- コンピューテーションリソースが要求されるタスクの非同期・キューイング・同時実行を容易に制御
- クラウド環境への移植性(コアロジックの中にクラウド上でGPUベースのコンテナに載せたいロジックがあった場合など)
動作する全ソースコードをGitHubで公開しています。
Yoctoでのコンテナイメージビルドについて
Yoctoのビルド内でコンテナイメージをビルドするには、Yoctoを使ってコンテナイメージをビルドすることになります。
ここで注意が必要なのが、<build dir>/local.confでIMAGE_INSTALL:append
したものはコンテナイメージにもインストールされてしまいます。そこで<build dir>/local.confではIMAGE_INSTALL:append
の代わりにCORE_IMAGE_EXTRA_INSTALL:append
を使用しました。ドキュメントによると、これはcore-image-*
のターゲットのみにインストールされるとのことで、コンテナイメージにインストールしたくない場合に便利そうです。
実装
それでは作っていきます。meta-sampleレポジトリを作成します。構成は以下のようになっています。
meta-sample +- recipes-sample →自作コンテナのアプリのレシピ、自作コンテナイメージをビルドするレシピ
+- recipes-packages →自作コンテナが依存するPythonパッケージのレシピ
+- recipes-container →ターゲットイメージ上でコンテナを自動でインポート・ランするレシピ
なお、initマネージャはsystemdを前提にしています。
Yoctoへのdocker/docker-composeの組み込み
Yocto標準の仮想化関連のレシピはmeta-virtualizationに入っています。まずはこれをダウンロードします。
git clone -b walnascar git://git.yoctoproject.org/meta-virtualization
仮想化をビルド対象に含めるため、以下のように追加します。
${TOPDIR}/../meta-openembedded/meta-filesystems \
${TOPDIR}/../meta-virtualization \
DISTRO_FEATURES:append = " virtualization"
最後にdocker/docker-composeをインストールするよう追加します。
CORE_IMAGE_EXTRA_INSTALL:append = " docker"
CORE_IMAGE_EXTRA_INSTALL:append = " docker-compose"
これでターゲットデバイス上にdocker/docker-compose環境の準備ができました。
実行時に必要なディスクスペースの確保
コンテナイメージのインポートなどで実行時にディスクスペースを必要とします。Yoctoでビルドされるwicはパーティション構成も含んでおり、rootfsのパーティションサイズはrootfsのサイズ+余白(IMAGE_OVERHEAD_FACTOR)となり余裕がありません。
そこでドキュメントにあるようにスペースを確保します。
私は64GBのSDカードを使っているので40GiB確保しました。これでrootfsパーティションはトータル52GiBになりました。今後の機能追加を考えても余裕があります。
IMAGE_ROOTFS_EXTRA_SPACE = "41943040"
自作コンテナのアプリ
アプリ用に別のリポジトリを作成します。アプリ自身はホスト上で開発し、動作確認できたリビジョンをインストールするレシピを作成します。(recipes-sampleディレクトリを参照してください)
APIサーバ
アプリ用のレポジトリを作成して実装します。アプリケーション自身の動作確認はホスト上で行うのでDockerfile/requirements.txtも作成していますが、Yoctoビルドには使用しません。
コアロジックサーバ
こちらもアプリ用のレポジトリを作成して実装します。同様にアプリケーション自身の動作確認はホスト上で行うのでDockerfile/requirements.txtも作成していますが、Yoctoビルドには使用しません。
Pythonパッケージの依存関係の解決
自作コンテナをホスト上で実行しpip show <package>
で依存パッケージを取得しました。
必要なPythonパッケージのレシピがmeta-openembeddedにあればそれを使い、無ければ自分でレシピを作成することになります。PyPI(pip)でインストールできるパッケージであれば簡単にレシピを作成できます。(recipes-packagesディレクトリを参照してください)
コンテナイメージビルド
前述のアプリと依存パッケージをインストールしたコンテナイメージをビルドします。(recipes-sampleディレクトリを参照してください)
meta-virtualizationにお手本があるので、それをベースにします。
コンテナイメージのターゲットイメージへのデプロイ
以下のようなbbファイルで実現しました。(recipes-container/sample-services-preloadディレクトリを参照してください)
do_install()にsample-api-container-imageおよびsample-core-container-imageのdo_image_complete()への依存を追加して、do_install時に${DEPLOY_DIR_IMAGE}/sample-api-container-image-${MACHINE}.rootfs.tar.bz2
および${DEPLOY_DIR_IMAGE}/sample-core-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"
SRC_URI = "file://."
S = "${WORKDIR}/sources"
UNPACKDIR = "${S}"
do_install() {
install -d "${D}/opt/sample"
install -m 644 ${S}/compose.yaml ${D}/opt/sample/compose.yaml
install -m 644 "${DEPLOY_DIR_IMAGE}/sample-api-container-image-${MACHINE}.rootfs.tar.bz2" "${D}/opt/sample"
install -m 644 "${DEPLOY_DIR_IMAGE}/sample-core-container-image-${MACHINE}.rootfs.tar.bz2" "${D}/opt/sample"
}
FILES:${PN} = "/opt/sample/*"
DEPENDS = "sample-api-container-image sample-core-container-image"
do_install[depends] += "sample-api-container-image:do_image_complete"
do_install[depends] += "sample-core-container-image:do_image_complete"
実行時のコンテナイメージのDockerへのインポート・ラン自動化
このままでは、ただターゲットイメージ上にtar.gzがあるだけなので、起動時にdockerへインポートし、docker-composeでサービスを起動させます。(recipes-container/sample-services-autorunディレクトリを参照してください)
Yoctoターゲットのビルド対象への追加
最後にこれまでのレシピをYoctoターゲットのビルド対象に追加します。
${TOPDIR}/../meta-sample \
CORE_IMAGE_EXTRA_INSTALL:append = " docker"
CORE_IMAGE_EXTRA_INSTALL:append = " docker-compose"
CORE_IMAGE_EXTRA_INSTALL:append = " sample-services-autorun"
Discussion