Cloud Native Buildpacks は Docker 上でアプリケーションを稼働させるにあたり非常に強力なソリューションです
Tagbangers では Spring Boot と Next.js で作成したアプリを AWS EKS / AWS ECR にデプロイして Docker Container 上で稼働させているプロダクトがいくつかあります
それらは Buildpacks を利用してほぼゼロコンフィグでイメージを作成しているものが多いです
過去にも Buildpacks に関連した記事をいくつか随筆しているのでよろしければご覧ください
今回は FFmpeg を利用するアプリを動作させるために必要な作業にピックアップします
Spring Boot アプリケーションを Docker Image 化
Spring Boot でイメージを作成する方法 はいくつかありますが、今回はビルド済みの executable jar から作成します
Builder は Paketo Buildpacks を利用します
事前に Docker と pack cli のインストールが必要です
Spring Boot アプリケーションをビルドした後 pack build
コマンドで jar ファイルを渡すだけです
./mvnw clean package
app_name=$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)
app_version=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
pack build ${app_name}:${app_version} \
--builder paketobuildpacks/builder \
--path target/${app_name}-${app_version}.jar
基本的にはこれだけでイメージの作成が完了します
内部的にはアプリのフレームワークや Java バージョンの検知を行い、適切なランタイムや環境変数の設定を行ってくれます
詳細は下記の Spring Fest の講演が参考になります
Fest2021 sf_1 Cloud Native Buildpacksで作る至高のコンテナイメージ for Spring Boot~ おそらくあなたの知らないBuildpack活用法 ~
Paketo Buildpacks の Builder と Stack
カスタマイズにあたり Builder と Stack の概念に関して軽く知っておくと良いです
Builder
Builder - Cloud Native Buildpacks
Builder はイメージ作成時のアプリの検知やビルド処理といった一連のプロセス、そしてそれを行う Docker Container などがまとまった集合体です
Buildpacks を利用する際に --builder
で指定しています
Builder はさまざまな企業が提供しており pack builder suggest
コマンドを叩くことで推奨 Builder の一覧が取得できます
$ pack builder suggest
Suggested builders:
Google: gcr.io/buildpacks/builder:v1 Ubuntu 18 base image with buildpacks for .NET, Go, Java, Node.js, and Python
Heroku: heroku/builder:22 Base builder for Heroku-22 stack, based on ubuntu:22.04 base image
Heroku: heroku/buildpacks:20 Base builder for Heroku-20 stack, based on ubuntu:20.04 base image
Paketo Buildpacks: paketobuildpacks/builder:base Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, Python, Ruby, Apache HTTPD, NGINX and Procfile
Paketo Buildpacks: paketobuildpacks/builder:full Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, Python, PHP, Ruby, Apache HTTPD, NGINX and Procfile
Paketo Buildpacks: paketobuildpacks/builder:tiny Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java, Java Native Image and Go
Tip: Learn more about a specific builder with:
pack builder inspect <builder-image>
Paketo Buildpacks では paketobuildpacks/builder をベースに幾つかの種類がイメージタグとして分かれています
tiny
Distroless をベースとしており、Go と Java のアプリに最適化されたビルダー- イメージサイズが小さくほとんどのライブラリ・コマンドラインが含まれていない
base
Java, Node.js, Go, .NET Core, Ruby, NGINX のアプリに最適化されたビルダーlatest
(= タグ未指定) と同じ
full
base
に加えて PHP, HTTPD のアプリに最適化されており native extensions(C ライブラリ)を含んでいるビルダー- イメージサイズが大きい
全て Ubuntu Bionic (18.04) がベースですが Ubuntu Jammy (22.04) 用の builder も用意されてます(Docker Hub の paketobuildpacks/builder-jammy-XXX
に該当)
Stack
Stack · Cloud Native Buildpacks
Stack は Builder で利用される Docker Container のイメージでビルド時に使用するものと実行時に使用されるもので分けて定義されます
ビルド時のイメージはは先ほど紹介した paketobuildpacks/builder
が該当します、実行時のイメージは paketobuildpacks/run
が利用され pack build
時の --run-image
オプションで明示的に指定しない限りは Builder に対応したイメージが自動的に選択されます
https://buildpacks.io/docs/tools/pack/cli/pack_build/
--run-image string Run image (defaults to default stack's run image)
FFmpeg を利用するアプリケーション用に Image をカスタマイズ
さて、本題です
今回は Spring Boot アプリケーションで画像・動画変換を行うために JavaCPP Presets Platform For FFmpeg を用います(詳細は省きます)
import org.bytedeco.ffmpeg.ffmpeg;
import org.bytedeco.javacpp.Loader;
...
Loader.load(ffmpeg.class);
初回読み込みが行われると $HOME/.javacpp/cache
に ffmpeg のバイナリと幾つかのライブラリが展開されます
ffmpeg を実行するために必要な依存は下記の通りです
$ ldd ffmpeg
.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/ffmpeg:
linux-vdso.so.1 (0x00007ffe303ef000)
libavdevice.so.59 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libavdevice.so.59 (0x00007f5abc742000)
libavfilter.so.8 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libavfilter.so.8 (0x00007f5abc060000)
libavformat.so.59 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libavformat.so.59 (0x00007f5abb899000)
libavcodec.so.59 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libavcodec.so.59 (0x00007f5ab9fde000)
libswresample.so.4 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libswresample.so.4 (0x00007f5abc723000)
libswscale.so.6 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libswscale.so.6 (0x00007f5abc68b000)
libavutil.so.57 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libavutil.so.57 (0x00007f5ab9e04000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f5ab9a66000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f5ab9847000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5ab9456000)
libxcb.so.1 => /usr/lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f5ab922e000)
libxcb-shm.so.0 => /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0 (0x00007f5ab902b000)
libxcb-shape.so.0 => /usr/lib/x86_64-linux-gnu/libxcb-shape.so.0 (0x00007f5ab8e27000)
libxcb-xfixes.so.0 => /usr/lib/x86_64-linux-gnu/libxcb-xfixes.so.0 (0x00007f5ab8c1f000)
libasound.so.2 => /usr/lib/x86_64-linux-gnu/libasound.so.2 (0x00007f5ab8918000)
libva.so.1 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libva.so.1 (0x00007f5ab86f8000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f5ab836f000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f5ab816b000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f5ab7f53000)
libva-drm.so.1 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libva-drm.so.1 (0x00007f5ab7d50000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5abc532000)
libXau.so.6 => /usr/lib/x86_64-linux-gnu/libXau.so.6 (0x00007f5ab7b4c000)
libXdmcp.so.6 => /usr/lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f5ab7946000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f5ab773e000)
libdrm.so.2 => /home/cnb/.javacpp/cache/ffmpeg-5.0-1.5.7-linux-x86_64.jar/org/bytedeco/ffmpeg/linux-x86_64/libdrm.so.2 (0x00007f5ab752d000)
libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f5ab7318000)
base
Image をベースに作成した場合下記の依存ライブラリが不足しているため ffmpeg が実行できません
libXau.so.6
libXdmcp.so.6
libasound.so.2
libxcb-shape.so.0
libxcb-shm.so.0
libxcb-xfixes.so.0
libxcb.so.1
これらライブラリは full
イメージにも含まれていないため別途含める必要があります
そこで先ほどの Stack のうち実行イメージをカスタマイズして追加の依存ライブラリをインストールさせます
カスタム Image を作成
Dockerfile を用意します
FROM paketobuildpacks/run:base
USER root
RUN apt-get -qq update && \
apt-get -qq --no-install-recommends install \
libxcb-shm0 \
libxcb-shape0 \
libxcb-xfixes0 \
libasound2 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
USER cnb
ベースイメージにて基本ユーザである cnb
が指定されているため root
に切り替えて必要な依存を apt-get
でインストールします(最後に cnb
に切り替えるのを忘れずに)
このイメージをビルドして pack build
時に実行イメージとして指定します
docker build -t paketobuildpacks-ffmpeg-runtime .
pack build ${app_name}:${app_version} \ --builder paketobuildpacks/builder \
--run-image paketobuildpacks-ffmpeg-runtime \
--path target/${app_name}-${app_version}.jar
注意点
実行イメージを作る際に必ず最新のベースイメージをプルする
Paketo Buildpacks のイメージは最新のセキュリティパッチの更新に合わせて日々イメージが更新されています
https://paketo.io/docs/concepts/stacks/#when-are-paketo-stacks-updated
pack build
で指定した際は作成時に最新のベースイメージをプルする処理がありますがカスタムイメージの場合はスキップされるため下記の対応が推奨されます
- カスタムイメージ作成時に
docker build --pull
オプションをつけて必ず最新のベースイメージをプルさせる pack build
前にカスタムイメージを作成させる
ビルドイメージと実行イメージのライブラリが一致している必要がある
--builder
で指定しているイメージと --rum-image
で指定しているイメージのそれぞれに含まれるライブラリが違うとイメージ作成時にエラーになるため要注意です
base
イメージ以上でないと apt-get
コマンドは使えない
tiny
イメージは Distroless がベースのため、apt-get
コマンドも用意されていません
雑記
やりたいこととしては実行時に足りないライブラリを追加したいだけだったのですが今回の方法に辿り着くまでにだいぶ時間がかかりました
理由としては Buildpacks は自動でイメージ作成処理が始まるため途中で処理を挟ませることができず、色々調べているうちにカスタムでスタックを作成したり、ビルドパックを作成する方法に辿り着いたりしていました
How to Create a Custom Stack - Paketo Buildpacks Create a builder - Cloud Native Buildpacks
ここまでやるとなるとやりたいことに対してコストが大きく、一時は諦めて自前で Dockerfile を用意してイメージを作っていました(最適化を自前で行う必要が発生していた)
pack build
の説明を眺めていた時に --run-image
のオプションを発見したのと、先ほどご紹介した Spring Fest での Buildpacks の解説動画にてカスタマイズ方法がご紹介されていたので今回のやり方に行き着くことができました