😾

S3に格納した動画をffmpegで変換するバッチ処理をAWS Batchで実装してPHPから実行する

2023/02/13に公開

背景

Laravelのwebアプリケーション上でS3にアップロードされた動画(mp4,movなど)をwebm形式にしてS3の同じディレクトリに保存したい。

概要

S3から動画ファイルを取得→ffmpegでwebmに変換→結果をS3に格納する処理を行うシェルスクリプトをAWS BatchとFargateというAWSの仕組み上で実行する。
S3とのやりとりはawscliのコマンドを用いて行う。

前提

  • PHP 8.0
  • Amazon Linux2(PHP実行環境)
  • bash 4.2.46
  • Docker 20.10.17
  • aws-cli/2.9.18

大まかな手順

  1. shellを動かす用のffmpegとawscliが使えるDockerイメージを作成する
  2. Amazon ECR(Elastic Container Registry)に作成したDockerイメージをプッシュする
  3. AWS Batch上で動くシェルスクリプトを作成してS3に配置する
  4. AWS Batchで諸々の設定する

実行したいシェルスクリプト

S3からファイル取得→サムネイル作成→S3アップ→webmに変換→S3アップという流れ。
シェルはS3の任意の場所に格納しておく。

exec-ffmpeg.sh
#!/bin/sh
# S3に配置してAWS BATCHから呼び出す用
# 動画ファイルをwebm形式に変換
export LANG='ja_JP.UTF-8'
echo "exec-ffmpeg.sh start"
if [ $# -ne 1 ]
  then
    echo "入力ファイルを指定してください"
    echo "exec-ffmpeg.sh end"
    exit 1
fi

INPUT_FILE=$1

INPUT_DIR=$(dirname "$INPUT_FILE")
INPUT_FILENAME_FULL=$(basename "$INPUT_FILE")
INPUT_FILENAME=$(echo $INPUT_FILENAME_FULL | sed 's/\.[^\.]*$//')
INPUT_EXTENSION=$(echo $INPUT_FILENAME_FULL | sed 's/^.*\.\([^\.]*\)$/\1/')

echo "S3からファイルをを取得します"
aws s3 cp "$INPUT_FILE" ./"$INPUT_FILENAME_FULL"
if [ $? -ne 0 ]
  then
   echo "S3からのファイル取得に失敗しました"
   echo "exec-ffmpeg.sh end"
   exit 1
fi

echo "サムネイルを取得します"
ffmpeg -i "$INPUT_FILENAME_FULL" -f image2 -ss 1 -vframes 1 -s 1280x720 "$INPUT_FILENAME.jpg"

if [ $? -ne 0 ]
 then
  echo "サムネイルの取得に失敗しました"
  echo "exec-ffmpeg.sh end"
  exit 1
fi

 echo "サムネイルをS3にアップロードします"
 echo /usr/local/bin/aws s3 "$INPUT_FILENAME.jpg" "$INPUT_DIR/$INPUT_FILENAME.jpg"

aws s3 mv "$INPUT_FILENAME.jpg" "$INPUT_DIR/$INPUT_FILENAME.jpg"
 if [ $? -ne 0 ]
   then
   echo "サムネイルのS3へのアップロードに失敗しました"
   echo "exec-ffmpeg.sh end"
   exit 1
 fi

echo "変換処理を開始します"
ffmpeg -i "$INPUT_FILENAME_FULL" -threads 4 -strict -2 "$INPUT_FILENAME"".webm"
if [ $? -ne 0 ]
 then
  echo "動画の変換に失敗しました"
  echo "exec-ffmpeg.sh end"
  exit 1
fi

echo "変換結果をS3にアップロードします"
aws s3 cp "$INPUT_FILENAME.webm" "$INPUT_DIR/$INPUT_FILENAME.webm" --acl public-read
if [ $? -ne 0 ]
 then
  echo "変換結果のS3へのアップロードに失敗しました"
  echo "exec-ffmpeg.sh end"
  exit 1
fi

rm $INPUT_FILENAME_FULL
rm "$INPUT_FILENAME.webm"

echo "exec-ffmpeg.sh succeeded"

Dockerイメージをつくる

手元の環境でアップロード用のDockerイメージを作成する。

mkdir {任意の名前}
touch DockerFile
vi DockerFile
DockerFile
FROM    jrottenberg/ffmpeg
RUN     apt-get update
RUN     apt-get install awscli unzip language-pack-ja -y
ADD	fetch_and_run.sh /usr/local/bin/fetch_and_run.sh
RUN	chmod +x /usr/local/bin/fetch_and_run.sh
ENTRYPOINT ["/usr/local/bin/fetch_and_run.sh"]

ffmpeg用のコンテナイメージを使用。
fetch_and_run.shというのはawslabsが提供している、任意のシェルスクリプトを実行するためのラッパーで、S3上に格納したシェルスクリプトを取得して、Dockerコンテナ内で実行してくれるやつ。

Dockerイメージ作成

docker build -t awsbatch/fetch_and_run .

DockerイメージをECRにプッシュする

まずはECRのリポジトリを作成する。
マネジメントコンソールでECRを開いて「リポジトリを作成」。

任意の名前を入力して「リポジトリを作成」。

作成したリポジトリを選んで、「プッシュコマンドを表示」でイメージをプッシュするためのコマンドを教えてくれるので、Dockerイメージを指定しているパラメータ置き換えて実行するだけ。


AWS Batch ジョブ定義の作成

予めバッチ実行用のIAMロールを作っておく。権限はこんな感じ。

マネジメントコンソールでAWS Batchを開いて、「ジョブ定義」→「ジョブ作成」

オーケストレーションタイプはFargete。

タイムアウトは任意の時間、現状他に実行している処理もないので優先度は1。

実行ロールは先ほど作ったロールを指定して次のページへ。

コンテナ設定。イメージは先ほどの手順でECRに登録したDockerイメージのarnを入力。
コマンドは実行時に上書くので空欄でOK。

vCPUとメモリはあくまでデフォルト値で、これも実行時に上書き指定可能。
環境変数のBATCH_FILE_S3_URLは、S3に配置してあるシェルスクリプトのパスを指定。
BATCH_FILE_S3_URLは「script」。

実行ユーザーはroot。ログドライバーはawslogsを指定。
これでジョブ定義を作成。

AWS Batch ジョブキューの作成

ジョブ定義を作成したら、最後にジョブキューを作成する。
ジョブキューの設定に必要なスケジューリングポリシーを作成する。
任意の名前だけ入力して作成。

コンピューティング環境を作成。←詳細準備中
ジョブキューを作成。←詳細準備中

PHPからAWS Batchを呼び出す

実行時に渡すパラメータのcontainerOverridesでジョブ定義の設定を上書きできるので、
ここでリソース(vCPU,メモリ)と実行コマンドを指定する。

use Aws\Batch\BatchClient;
use Aws\Batch\Exception\BatchException;

$config = [
    'version' => '2016-08-10',
    'region' => 'ap-northeast-1',
    'credentials' => [
	'key' => {AWS_ACCESS_KEY_ID},
	'secret' => {AWS_SECRET_ACCESS_KEY}
    ]
 ];

$args = [
'jobDefinition' => '{ジョブ定義のarn}',
'jobName' => 'exec-ffmpeg',
'jobQueue' => '{ジョブキューのarn}',
'shareIdentifier' => 'execffmpeg',
"containerOverrides"=> [
    "command"=> [ "sh exec-ffmpeg.sh", "s3://{バケット名}{S3オブジェクトのキー}" ],
    "resourceRequirements" => [
	[
	    "value"=> "4.0",
	    "type"=> "VCPU",
	],
	[
	    "value"=> "30720",
	    "type"=> "MEMORY",
	],
    ],
],
'timeout' => [
    "attemptDurationSeconds"=> "3600" //1時間
],
];

$batch = new BatchClient($config);

try {
    $result = $batch->submitJob($args);
} catch (BatchException $e) {
    logger($e->getMessage());
}

実行したらマネジメントコンソールのAWS Batch→ジョブで確認する。
初期ステータスはSUBMITTED。
うまくいってればSTARTING→RUNNNIG→SUCCESSと遷移していく。

ジョブが終了したらCloudWatchでログを確認できる。

参考

https://hub.docker.com/r/jrottenberg/ffmpeg/
https://trac.ffmpeg.org/wiki/CompilationGuide
https://dev.classmethod.jp/articles/ecr-ffmpeg/
https://dev.classmethod.jp/articles/aws-batch-design/
https://dev.classmethod.jp/articles/aws-eaws-ecs-fetch-run-shell/
https://n-s.tokyo/2019/03/awsbatch-submitjob/

Discussion