Dockerイメージから作成したLambdaで画像リサイズ処理を行う

# やりたいこと
Lambda関数でlibvipsで画像をリサイズする
Lambda関数はRubyで動かす
画像はS3から取得する
ローカル環境で動作確認もしたい

# Dockerfileの用意
libvipsを使う場合そもそもlibvips本体をインストールしないといけません。
今回は、libvipsをインストールしたDockerイメージからLambdaを作成することにしてみました。

```:Dockerfile
FROM amazon/aws-lambda-ruby:latest

# EPELリポジトリを追加
RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

# remiレポジトリを追加
RUN yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm

# libvipsとlibvipsを使うために必要なパッケージをインストール
RUN yum install -y install vips make gcc

COPY Gemfile Gemfile.lock ${LAMBDA_TASK_ROOT}

COPY lambda_function.rb ${LAMBDA_TASK_ROOT}

# /usr/local/bundleにGemをインストールする
ENV GEM_HOME=/usr/local/bundle

RUN bundle install

CMD [ "lambda_function.lambda_handler" ]
```

## 解説
DockerイメージからLambdaを作成する場合、AWSが提供している`amazon/aws-lambda-ruby`イメージを使います。

https://hub.docker.com/r/amazon/aws-lambda-ruby

次にlibvipsをインストールしたいのですが、CentOS 7やAmazon Linux 2の標準のリポジトリにはlibvipsが用意されていないため、remiリポジトリを使えるようにします。
remiレポジトリを使用するには、先にEPELリポジトリを追加する必要があり、Amazon Linux 2にEPELリポジトリを追加する方法は下記リンク先に記載があります。

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/add-repositories.html

> Amazon Linux 2 で EPEL レポジトリを有効にするには、次のコマンドを使用します。
> [ec2-user ~]$ sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 

# Gemfileの用意
image_processingをインストールすることで、Rubyからlibvipsを使うことができるのでGemfileに追記します。
```:Gemfile
source 'https://rubygems.org'
gem 'image_processing'
```

# docker-compose.ymlの用意
ローカル環境で動作確認するためにdocker-compose.ymlファイルも作成しておきます。
ローカルでS3の機能を実現するために今回はminioを使うことにしました。
```:docker-compose.yml
version: '3'
services:
  app:
    build: .
    image: lambda_libvips_demo
    environment:
      BUCKET_NAME: local-bucket
      AWS_REGION: 'ap-northeast-1'
    ports:
      - "8080:8080"
    tty: true
    stdin_open: true
    volumes:
      - .:/var/task
  minio:
    image: minio/minio:latest
    environment:
      MINIO_ROOT_USER: minio_user
      MINIO_ROOT_PASSWORD: minio_password
    ports:
      - 9000:9000
      - 9001:9001
    volumes:
      - minio:/storage
    command: ['server', '/storage', '--console-address', ':9001']
  mc:
    image: minio/mc:latest
    depends_on:
      - minio
    environment:
      MINIO_ROOT_USER: minio_user
      MINIO_ROOT_PASSWORD: minio_password
    entrypoint: >
      /bin/sh -c "
      mc alias set myminio http://minio:9000 minio_user minio_password;
      mc mb myminio/local-bucket;
      "
volumes:
  minio:

```

# lambda_function.rbの用意
実行する関数です。
実際やるならリサイズしたい画像の情報をeventで渡すのがいいと思いますが今回は簡単のため固定の画像 `image.jpg` をリサイズする処理を書いています。

```ruby:lambda_function.rb
require 'image_processing/vips'
require 'aws-sdk-s3'

Aws.config.update({
  credentials: Aws::Credentials.new('minio_user', 'minio_password'),
  endpoint: 'http://minio:9000',
  force_path_style: true
})

def lambda_handler(event:, context:)
  # 画像データ取得
  s3_client = Aws::S3::Client.new(:region => 'ap-northeast-1')
  key = 'image.jpg'
  uploaded_data = s3_client.get_object(:bucket => ENV['BUCKET_NAME'], :key => key).body.read

  # 画像リサイズ
  uploaded_file = Vips::Image.new_from_buffer(uploaded_data, '')
  resized_file_path = ImageProcessing::Vips.source(uploaded_file).resize_to_limit!(200, 200).path

  # アップロード実行
  s3_resource = Aws::S3::Resource.new()
  s3_resource.bucket(ENV['BUCKET_NAME']).object("resize_image.jpg").upload_file(resized_file_path,  content_type: "image/jpeg")
end
```

# ローカルでの動作確認
ビルド後docker-compose upコマンドで立ち上げます。
```
docker-compose build app
docker-compose up
```
ブラウザで`http://localhost:9001`にアクセスし、あらかじめ `image.jpg`をアップロードしておきます。
![スクリーンショット 2023-12-01 0.40.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2736970/10b7e297-d613-903b-e930-5e2e7e2a2941.png)

別のターミナルを開きcurlコマンドでPOSTすることでLambda関数が動きます。
```
curl -d '{"payload":"hello world!"}' "http://localhost:8080/2015-03-31/functions/function/invocations"
```
実行後再びminioを見るとリサイズされた画像がアップロードされていることが確認できると思います。
![スクリーンショット 2023-12-01 1.05.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2736970/104fefe1-0c32-d9f3-d37a-8daf90fa8beb.png)

# 本番用にlambda_function.rbを修正
AWSで実際Lambda関数を動かすときには `Aws.config.update` は不要なので削除しておきましょう。
```ruby:lambda_function.rb
require 'image_processing/vips'
require 'aws-sdk-s3'

def lambda_handler(event:, context:)
  # 画像データ取得
  s3_client = Aws::S3::Client.new(:region => 'ap-northeast-1')
  key = 'image.jpg'
  uploaded_data = s3_client.get_object(:bucket => ENV['BUCKET_NAME'], :key => key).body.read

  # 画像リサイズ
  uploaded_file = Vips::Image.new_from_buffer(uploaded_data, '')
  resized_file_path = ImageProcessing::Vips.source(uploaded_file).resize_to_limit!(200, 200).path

  # アップロード実行
  s3_resource = Aws::S3::Resource.new()
  s3_resource.bucket(ENV['BUCKET_NAME']).object("resize_image.jpg").upload_file(resized_file_path,  content_type: "image/jpeg")
end
````

# 再ビルド&ECRにpush
```
docker-compose build app
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin XXXXX.dkr.ecr.ap-northeast-1.amazonaws.com
docker tag lambda_libvips_demo:latest XXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/lambda_libvips_demo:latest
docker push XXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/lambda_libvips_demo:latest
```

ECRでlambda_libvips_demoリポジトリを作成した後に上記コマンドを実行してECRにプッシュします。

![スクリーンショット 2023-12-01 1.21.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2736970/36de3021-c973-34f3-b2c8-24b77d454711.png)

# S3の作成
S3バケットを作成し、image.jpgファイルをアップロードしておきます。
S3の設定は全てデフォルトでOKです。
![スクリーンショット 2023-12-01 1.29.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2736970/e6bcbed6-9c51-13f6-4347-c03150462077.png)

# Lambda関数を作成
Lambdaの関数の作成でコンテナイメージから作成で、先ほどECRにpushしたコンテナイメージから作成します。
環境変数に作成したS3のバケット名を追加し、Lambdaの実行ロールに `AmazonS3FullAccess` を追加します。

![スクリーンショット 2023-12-01 1.32.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2736970/f0abad87-41a2-537d-e1e9-2af1be8b7d78.png)
![スクリーンショット 2023-12-01 1.34.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2736970/74a119d0-1886-9b65-e3dd-cdfb65cd147a.png)

# Lambda関数を実行
テスト実行するとS3にリサイズされた画像がアップロードされていることが確認できると思います。
![スクリーンショット 2023-12-01 1.36.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2736970/81a51e7a-4053-e93f-a3dd-411e085ece6c.png)

![スクリーンショット 2023-12-01 1.38.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2736970/6e42196f-d403-cf37-035e-f784424635c1.png)