SinatraをAWS Lambdaで動かす (Lambda Web Adapter 0.8.1 使用)

AWS Lambda 上で、任意の Web アプリケーションフレームワークを動かせる「Lambda Web Adapter」が、バージョン 0.8.1 で [HTTP 以外のイベントも受けられるように](https://github.com/awslabs/aws-lambda-web-adapter?tab=readme-ov-file#non-http-event-triggers)なりました。

https://twitter.com/sunhua/status/1751971473831649478

ここでは、Ruby製Webアプリケーションフレームワーク「[Sinatra](https://sinatrarb.com/)」を、AWS Lambda + Lambda Web Adapter で動かす最小構成を紹介します。

## 1. 動作確認用の最小構成Sinatraアプリケーション

いたって普通のSinatraアプリですね。これが AWS Lambda でそのまま動作します。

```ruby:app.rb (Sinatraアプリ)
require 'sinatra'

get '/' do
  logger.info params # QueryString
  'Hello! I am <b>Sinatra</b>.'
end

post '/' do
  logger.info params # QueryString and/or x-www-form-urlencoded
  logger.info request.body.read # e.g.) application/json
  'Post data recived.'
end

post '/events' do # for Non-HTTP Event Trigger
  logger.info request.body.read # Logging body
  'success' # w/ HTTP 200
end
```

```bash:Sinatra環境構築
mkdir -p sinatra_app1
cd sinatra_app1
bundle init
bundle add sinatra
bundle add rackup
bundle add puma
ls
#=> Gemfile  Gemfile.lock  app.rb
```

```bash:動作確認
ls
#=> Gemfile  Gemfile.lock  app.rb
bundle exec ruby app.rb -p 8080
# CTRL+C で終了

# 別ターミナルから
curl localhost:8080/
#=> Hello! I am <b>Sinatra</b>.
```

## Dockerfile 作成とビルド

先ほどの Sinatra アプリに Runtime である Ruby 本体を同梱してコンテナイメージを作成します。

```dockerfile:Dockerfile
FROM public.ecr.aws/docker/library/ruby:3.4
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
WORKDIR /var/task
COPY ./ ./
RUN bundle install
CMD ["bundle", "exec", "ruby", "app.rb", "-o", "0.0.0.0", "-p", "8080"]
```

```bash:コンテナイメージのビルド
ls
#=> Dockerfile  Gemfile  Gemfile.lock  app.rb
docker build -t sinatra_app1 .
```

```bash:動作確認
docker images sinatra_app1
#=> REPOSITORY     TAG       IMAGE ID       CREATED          SIZE
#=> sinatra_app1   latest    7d50536b75db   25 minutes ago   1.01GB
docker run -it --rm -p 8080:8080 sinatra_app1
# CTRL+C で終了

# 別ターミナルから
curl localhost:8080/
#=> Hello! I am <b>Sinatra</b>.
```


## この後の作業

AWS Lambda で Amazon Elastic Container Registry(ECR) 上のコンテナを利用する方法と同じ手順です。

1. Amazon ECR にレジストリを作成
2. コンテナイメージに tag を付与
3. Amazon ECR レジストリへコンテナイメージを push

`${ACCOUNTID}` や `${REGION}` は読み替えてください。

```bash:1～3の手順
aws ecr create-repository --repository-name sinatra_app1_lambda #1
docker tag sinatra_app1:latest ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/sinatra_app1_lambda:latest #2
aws ecr get-login-password | docker login --username AWS --password-stdin ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com #3a
docker push ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/sinatra_app1_lambda:latest #3b
```

```bash:コンテナイメージの確認
aws --output text ecr list-images --repository-name sinatra_app1_lambda
#=> IMAGEIDS        sha256:3bc6833093c4adaefddba0737501226fa02e9ac0e7ebe83c82b5111300f12639 latest
```

あとは、AWS Lambda の操作です。関数作成時に「コンテナイメージ」を参照するように設定します。関数のアーキテクチャは `x86_64`、メモリーは `512MB` を割り当てましょう。

Lambda 関数の動作ログは CloudWatch Logs で確認できます。

#### トラブルシュート:

* `/usr/local/lib/ruby/3.3.0/rubygems.rb:165: [BUG] Segmentation fault at 0xffffffffffffffff` が発生したら:
    * メモリー不足です。Lambda 関数へのメモリー割り当てを増やしてください。
* `INIT_REPORT Init Duration: xx.xx ms	Phase: init	Status: error	Error Type: Extension.LaunchError` が発生したら:
    * アーキテクチャーの不一致です。コンテナイメージ内のアーキテクチャーと、Lambda 関数のアーキテクチャーが一致しているか確認して、必要であれば Lambda 関数を再作成してください。

## コンテナイメージの更新 = 関数の更新

```bash
docker build -t sinatra_app1 .
docker tag sinatra_app1:latest ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/sinatra_app1_lambda:latest
docker push ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/sinatra_app1_lambda:latest
```

あとは AWS Lambda 関数側で「新しいイメージをデプロイ」から、更新後のコンテナイメージを選んでデプロイすれば、更新されます。

## あとがき

Non-HTTP トリガーは [ここを見る](https://github.com/awslabs/aws-lambda-web-adapter/tree/main/examples/sqs-expressjs#local-test)と、 `/events` というパスへ Content-Type=application/json で HTTP POST される仕様ですね。Web アプリの要領で実装できます。
※ 個人的には `lambda_handler()` でいい気もしてますが。

### コード例が examples にもあります

これと同じ内容が https://github.com/awslabs/aws-lambda-web-adapter/tree/main/examples/sinatra にあります。若干のディレクトリ構成の違いはありますが同じものです。

### 参考

* [コンテナ利用者に捧げる AWS Lambda の新しい開発方式 !](https://aws.amazon.com/jp/builders-flash/202103/new-lambda-container-development/?awsf.filter-name=*all#:~:text=%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E5%BD%A2%E5%BC%8F%E3%81%AE%20Lambda%20%E9%96%A2%E6%95%B0%E4%BD%9C%E6%88%90%E6%96%B9%E6%B3%95%E3%82%92%E3%81%9F%E3%82%81%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86%EF%BC%81)
* [AWS::Serverless::Function](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html)
* [Amazon ECR Public Gallery > search > docker > library/ruby "How to use this image"](https://gallery.ecr.aws/docker/library/ruby#how-to-use-this-image)
* [awslabs/aws-lambda-web-adapter | GitHub](https://github.com/awslabs/aws-lambda-web-adapter)
* [AWS SAM で AWS Lambda の 関数 URL を利用してみました](https://engineers.weddingpark.co.jp/lambda-function-urls/)
* [SAMを利用したLambda(Function URLs)デプロイ](https://qiita.com/Nikkely/items/0b1328970c7e12804397)

[EoT]