S3に格納した動画をffmpegで変換するバッチ処理をAWS Batchで実装してPHPから実行する
背景
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
大まかな手順
- shellを動かす用のffmpegとawscliが使えるDockerイメージを作成する
- Amazon ECR(Elastic Container Registry)に作成したDockerイメージをプッシュする
- AWS Batch上で動くシェルスクリプトを作成してS3に配置する
- AWS Batchで諸々の設定する
実行したいシェルスクリプト
S3からファイル取得→サムネイル作成→S3アップ→webmに変換→S3アップという流れ。
シェルはS3の任意の場所に格納しておく。
#!/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
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でログを確認できる。
参考
Discussion