Lambda Layer で ImageMagick をインストールする
西田@大阪です
Lambda の Node.js 8.x
イメージには ImageMagick とそれを Node.js から使うライブラリがなぜかプレインストールされていました。ですがNode.js 10.x
系以降から削除されてしまったので、Lambda Layer でImage Magick をインストールする方法と後学のために実行ファイルをビルドし Lambda に配置する方法をご紹介します
結論から
serverlesspub/imagemagick-aws-lambda-2: ImageMagick for AWS Lambda 2 runtimes
これを使えばOKです
後学のために、これにたどり着くまでにいろいろ試行錯誤した結果を残しておきたいと思います
やりたいこと
Node.js 8.x
のイメージに入っていたnpm rsms/node-imagemagick こちらを使ってS3にアップロードされたファイルをリサイズできるようにしてみたいと思います
まずはソースを読んで必要なファイルを確認します。(よくみるとしばらく更新されていないライブラリですね。。。)
リサイズする時に使用するexports
されたresize
メソッドを確認します
exports.resize = function(options, callback) { var t = exports.resizeArgs(options); return resizeCall(t, callback) }
ここからたどっていくと convert
というメソッドが呼ばれている事がわかります。convert
メソッドの中では"convert"
という文字列とそのパラメーターをexec2
という関数に渡して呼び出していることがわかります
exports.convert = function(args, timeout, callback) { // 〜〜〜省略 return exec2(exports.convert.path, args, procopt, callback); } exports.convert.path = 'convert';
exec2
では child_process.spawn
を使って引数で渡された文字列(今回は convert
)をコマンドとして実行していることがわかります
var childproc = require('child_process') // 〜〜〜省略 function exec2(file, args /*, options, callback */) { // 〜〜〜省略 var child = childproc.spawn(file, args); // 〜〜〜省略 }
そのため convert
という実行ファイルを PATH
が通っているところにおいてあげれば動きそうです。※ convert
はImageMagickをインストールすれば一緒にインストールされるコマンドの一つです
convert
実行ファイルをLambda環境に配置し、Node.jsから使えるようにするには以下の作業が必要です
convert
実行ファイルをLinux環境でビルドするconvert
実行ファイルをPATH
配下のディレクトリに配置するLambda Layer を作成し、デプロイするLambdaから参照するimagemagick
npmをLambda環境にインストールするimagemagick
npmを利用しリサイズする Lambda をデプロイする
1 は筆者は MacOS を利用しているので、EC2もしくはDockerを使う方法がありますが、後者のDockerを使う方法でビルドしたいと思います
2〜4 は Serverless Framework(以下 sls)を使って行いたいとおもいます
Lambda環境のPATHが通ってるとこに実行ファイルをおくには
Linuxでは実行ファイルを途中のパスを省略しファイル名だけで実行するにはPATH
環境で指定されているパス以下に実行ファイルを配置する必要があります
また、実行ファイルから共有ライブラリが動的にリンクされている場合にはLD_LIBRARY_PATH
環境変数で指定されているパス以下にso
ファイルを配置する必要があります
LambdaのPATH
環境変数や、LD_LIBRARY_PATH
環境変数を確認するには以下をご参考ください
参考: Lambda 関数で使用できる環境変数 - AWS Lambda
今回は Lambda Layer がファイルを /opt
以下に配置するので、/opt
以下で環境変数が通ってるところ /opt/bin
(PATH)、/opt/lib
(LD_LIBRARY_PATH)にそれぞれのファイルを配置していきたいと思います
前提条件
すでにS3のバケットが作成されている前提での説明とさせていただきます
Serverless Frameworkでプロジェクト作成
sls create --template aws-nodejs --path imagemagicksample
作成された serverless.yml
provider: name: aws runtime: nodejs12.x
執筆時点で上記コマンド作成されたプロジェクトの Runtime が nodejs12.x
だったので、このままこのプロジェクト上に ImageMagickに必要なファイルを作成していきます
作業用ディレクトリを作成します
mkdir -p layer/imagemagick/lib mkdir -p layer/imagemagick/bin mkdir -p volume/work
ここまででディレクトリは以下のようになっています
├── handler.js ├── serverless.yml ├── layer # Lambda Layer に含めるファイルを配置するディレクトリ │ └── imagemagick │ ├── bin │ └── lib └── volume # Dockerにマウントする作業用ディレクトリ
Docker を使って必要なファイルをビルド
Lambdaのほぼ同様の構成の Docker Image である lambci/docker-lambda を使って必要なファイルをビルドしていきます
必要なファイルをダウンロード
volume/work
以下にビルドに必要なファイルをダウンロードしていきます
host> cd volume/work
ImageMagickに必要なファイルをここからダウンロードしてきます。以下は執筆時点の最新のバージョンの tar.gz
をダウンロードするコマンドです
host> wget https://imagemagick.org/download/ImageMagick-7.0.9-13.tar.gz
今回は JPEG に対応するのでここからJPEGの拡張jpegsr*
もダウンロードします
host> wget http://www.imagemagick.org/download/delegates/jpegsrc.v9b.tar.gz
JPEG拡張の共有ライブラリをビルドする
volume
ディレクトリを/opt
にマウントしDockerを起動します。※ ローカルにDockerイメージのキャッシュがなければダウンロードが始まることにご注意ください
host> cd ../.. host> docker run -it --rm -v $PWD:/opt lambci/lambda-base-2:build /bin/bash
docker内の /opt/work
ディレクトリに移動し、必要なファイルを解凍します
docker> cd /opt/work docker> tar xfz jpegsrc.v9b.tar.gz
JPEG拡張をコンパイルします。configure
の--prefix
パラメーターに/opt/
を指定することで、/opt/lib
以下に共有ライブラリが配置されるようにしています
docker> cd jpeg-9b docker> ./configure \ CPPFLAGS=-I/opt/include \ LDFLAGS=-L/opt/lib \ --prefix=/opt/ \ --disable-dependency-tracking \ --enable-shared docker> make && make install
ヘッダファイルがインストールされていることを確認します
docker> ls /opt/include/ jconfig.h jerror.h jmorecfg.h jpeglib.h
共有ライブラリがインストールされていることを確認します
docker> ls /opt/lib/ libjpeg.a libjpeg.la libjpeg.so libjpeg.so.9 libjpeg.so.9.2.0
ImageMagick本体をビルドする
docker内の /opt/work
ディレクトリに移動し、必要なファイルを解凍します
docker> cd /opt/work docker> tar xfz ImageMagick-7.0.9-13.tar.gz
ImageMagickをコンパイルします。configure
の--prefix
に/opt
パラメーターを指定しています。
CPPFLAGS
、LDFLAGS
パラメーターに/opt
以下のパスを渡しJPEG拡張のヘッダファイル、共有ライブラリをインストールした場所を指定しています。
また、今回使用しない機能を無効化しています
docker> cd ImageMagick-7.0.9-13 docker> ./configure \ CPPFLAGS=-I/opt/include \ LDFLAGS=-L/opt/lib \ --prefix=/opt/ \ --disable-docs \ --without-modules \ --enable-delegate-build \ --disable-dependency-tracking \ --without-magick-plus-plus \ --without-perl \ --without-x \ --disable-openmp \ --disable-dependency-tracking docker> make && make install
コマンドの動作を確認します。※ sample.jpeg は適当な画像ファイルをダウンロードしてくる想定です
/opt/bin/convert -resize 100x100 sample.jpeg resized.jpeg
ImageMagickコマンドに必要なファイルを確認する
必要なファイルを確認します
docker> ls -l /opt/bin/convert lrwxrwxrwx 1 root root 6 Dec 31 20:15 /opt/bin/convert -> magick
lsコマンドで確認するとconvert
実行ファイルは同ディレクトリにあるmagick
実行ファイルのシンボリックリンクのようなので、この2つのファイルは必要と思われます
magick
コマンドの共有ライブラリの依存関係を確認します
docker> ldd /opt/bin/magick linux-vdso.so.1 (0x00007ffc0aff0000) libMagickCore-7.Q16HDRI.so.7 => /opt/lib/libMagickCore-7.Q16HDRI.so.7 (0x00007f0acfcff000) libMagickWand-7.Q16HDRI.so.7 => /opt/lib/libMagickWand-7.Q16HDRI.so.7 (0x00007f0acf9fa000) libjpeg.so.9 => /opt/lib/libjpeg.so.9 (0x00007f0acf7c1000) libz.so.1 => /lib64/libz.so.1 (0x00007f0acf5ab000) libm.so.6 => /lib64/libm.so.6 (0x00007f0acf26b000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f0acf04d000) libc.so.6 => /lib64/libc.so.6 (0x00007f0aceca2000) /lib64/ld-linux-x86-64.so.2 (0x00007f0ad0239000)
依存関係の中で /opt
より始まるパスに置かれているのは今回ビルドしたライブラリです。libMagickCore
、libMagickWand
、libjpeg
は必要と思われます
ImageMagickコマンドに必要なファイルをコピーする
実行ファイルをコピーします
host> cp -R volume/bin/magick \ volume/bin/convert \ layer/imagemagick/bin/
共有ライブラリをコピーします
host> cp -R volume/lib/libMagickCore-7.Q16HDRI.so.7* \ volume/lib/libMagickWand-7.Q16HDRI.so.7* \ volume/lib/libjpeg.so.9* \ layer/imagemagick/lib/
※ ファイルをそのままコピーするため -R オプションを指定しています
Lambda Layer の設定
serverless.ymlに以下のLambda Layerの設定を追加するだけです
layers: imageMagick: path: layer/imagemagick
参考: Serverless Framework - AWS Lambda Guide - Layers
npmをインストール
今回使用するnpmをyarnを使ってインストールします。※ aws-sdk
はIntellijで補完させるためにインストールしていますが、Lambdaにプレインストールされているものを使えば必要ありません
yarn init yarn add imagemagick yarn add aws-sdk
Lambdaのパッケージ設定
serverless.ymlに不要なnpmファイルや作業用ファイルをパッケージされないようpackage
の設定をします
package: exclude: - volume/** - nod_modules/** - '!node_modules/imagemagick/**'
Lambda の handler を作成
Lambdaのhadlerに設定する関数を作成します。以下は処理の概要です
- バケットにアップロードされたS3のキーをソースのキーとし、ソースのキーに
output
というプレフィックスを付与し出力先のS3のキーを生成します - ソースのキーをつかって S3 のオブジェクトをダウンロードします
- ダウンロードしたS3のオブジェクトを
imagemagick
npmのresize
メソッドをつかって、画像のリサイズをおこないます - リサイズされた画像のストリームを stdout(標準出力) より取得し、出力先のS3のキーにアップロードします
handler.js
'use strict'; const im = require("imagemagick") const aws = require('aws-sdk') const s3 = new aws.S3({ apiVersion: '2006-03-01', signatureVersion: 'v4' }) module.exports.resize_handler = async event => { const bucket = event.Records[0].s3.bucket.name const srcKey = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' ')) const destKey = "output/" + srcKey const srcParams = { Bucket: bucket, Key: srcKey } const src = await s3.getObject(srcParams).promise() const contentType = src.ContentType const ext = contentType.split('/').pop() const res = await (() => new Promise(resolve => { im.resize({ srcData: src.Body, format: ext, width: 100, height: 100, }, (err, stdout, stderr) => { s3.putObject({ Bucket: bucket, Key: destKey, Body: Buffer.from(stdout, 'binary'), ContentType: contentType }, (err, res) => { resolve(res) }) }) }) )() };
※ 説明の簡単のため、必要なエラー処理等を行っていません。実際にプロジェクトで使用する場合はエラー処理を忘れずに行ってください
Lambdaのhandlerの設定
serverless.ymlにhandlerの設定をしていきます
リサイズされたファイルをS3にアプロードするためのIAMの設定をします
provider: # 省略 iamRoleStatements: - Effect: "Allow" Action: - "S3:*" Resource: バケットのARN
つづいて以下2つの設定を行います
- Lambda Layer を指定します。
layers
で指定した Lambda Layer は Layer名をキャメルケースにしサフィックスにLambdaLayer
とつけた名前で参照できます。 今回の例だとimageMagick
=>ImageMagick
=>ImageMagickLambdaLayer
と変形されます - S3に
upload/
で始まるキーでアップロードしたときに発生するイベントに作成したhandlerが起動されるよう設定します
functions: resize: handler: handler.resize_handler layers: - { Ref: ImageMagickaLambdaLayer } events: - s3: bucket: "バケット名" events: - "s3:ObjectCreated" rules: - prefix: "upload/" existing: true
デプロイ
slsを使ってAWS環境にデプロイします
sls deploy
S3に upload/
プレフィックス以下にJPEGのファイルを置き、リサイズされた画像がoutput/
プレフィックス以下にできれば完成です
さいごに
最初にご紹介した serverlesspub/imagemagick-aws-lambda-2: ImageMagick for AWS Lambda 2 runtimes ではmagick
実行ファイルを静的リンクで作成し、共有ライブラリを個別に配置しなくてもよい方法で行っています。ですが、今回は勉強のため、あえて手順の多い共有ライブラリを使う方法で作成してみました
誰かのお役に立てれば幸いです