Tagbangers Blog

Paketo Buildpacks で作成する Spring Boot App on Docker で FFmpeg を扱えるようにする

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

Builders - Paketo 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 Hubpaketobuildpacks/builder-jammy-XXX に該当)

Stack

Stack · Cloud Native Buildpacks

Stacks - Paketo 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 の解説動画にてカスタマイズ方法がご紹介されていたので今回のやり方に行き着くことができました