LoginSignup
3

More than 1 year has passed since last update.

posted at

updated at

AWS LambdaがContainer Image Support 言うから 疑似Cloud Run(Rails on Lambda)

はじめに

メリークリスマス!! :christmas_tree: :christmas_tree: :santa: :christmas_tree: :christmas_tree:

Ateam Lifestyle Advent Calendar 2020の25日目は、
株式会社エイチームライフスタイル 執行役員CTO @tsutorm が担当します!

Lambdaがコンテナイメージサポートと聞いて

今年のアドカレどうしよっかなー :thinking:

ポエム系になるけど 「ビジネスが間違えやすいエンジニアの"開発生産性"」 みたいなのを書こうかなー

せっかくのクリスマス :christmas_tree: だしなー。プレゼントになるようなコードがあるほうがいいよなー

と思っていた所...


image.png

【速報】Lambdaのパッケージフォーマットとしてコンテナイメージがサポートされるようになりました!! #reinvent


それなんてCloud Ru... とおもったけど、ちょっと違うか?

既にいくつかの触った系記事が出てますが(みなさん早いですねー)、思ったほどCloud Runじゃない模様。

  • Lambdaで API Gatewayとガッチャンコして Cloud Run的なやつだと思ったのになー
  • BaseImageはAmazonLinux2っぽいので、Dockerfileさえちゃんと作れば原理的にはRailsとかも動くよなぁー
  • Aurora ServerlessとかRDS Proxyとかあるし、WebAPIくらい作れそうなのになぁー

...

いっちょやってみるか!

Rails API on AWS Lambda Run(仮) 動くぞ

$ # データぶっこんだりなんかはRailsなんでRESTとかseedsとかでやってくれい
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/posts  # GET /posts
[{"id":1,"title":"test","body":"hoge","published":null,"created_at":"2020-12-23T11:31:39.626Z","updated_at":"2020-12-23T11:31:39.626Z"},{"id":2,"title":"test2","body":"hoge","published":null,"created_at":"2020-12-23T12:11:04.785Z","updated_at":"2020-12-23T12:11:04.785Z"},{"id":3,"title":"test3","body":"hoge","published":null,"created_at":"2020-12-23T13:29:04.307Z","updated_at":"2020-12-23T13:29:04.307Z"}]
START RequestId: 87bfb3ea-568d-477a-aa52-809fc14c3d40 Version: $LATEST
I, [2020-12-24T06:35:23.744874 #8]  INFO -- : [de7866af-0a31-4bd7-a7f1-9b15aeb864dc] Started GET "/posts" for xx.xx.xx.xx at 2020-12-24 06:35:23 +0000
I, [2020-12-24T06:35:23.746365 #8]  INFO -- : [de7866af-0a31-4bd7-a7f1-9b15aeb864dc] Processing by PostsController#index as */*
I, [2020-12-24T06:35:23.960053 #8]  INFO -- : [de7866af-0a31-4bd7-a7f1-9b15aeb864dc] Completed 200 OK in 214ms (Views: 212.8ms | ActiveRecord: 27.8ms | Allocations: 6188)
END RequestId: 87bfb3ea-568d-477a-aa52-809fc14c3d40
REPORT RequestId: 87bfb3ea-568d-477a-aa52-809fc14c3d40  Duration: 220.86 ms Billed Duration: 3193 ms    Memory Size: 1024 MB    Max Memory Used: 158 MB Init Duration: 2971.41 ms

全体感

構成全景

言うほど複雑じゃないけど、図にするとこんな感じ
undefined.png

そんだけ

コード

githubに上げました。

アプリケーションとしては、rails new --api なものに、有る有るのpostモデルを追加しただけの大変シンプルなやつなんで、既存のガッツリ作ったRailsアプリケーションをFitされるとどうなるか。というのは又別の問題になるかと思います。。

deploy周りはserverlessに全部投げました。大変に便利。

docker buildは一部シェルにしてありますが、公式のドキュメントみてればそれほど難しい感じではなかったです。

Aurora Serverlessの構築だけ手動でやりましたが、そんな難しい話じゃないのでここでの手順説明は割愛します。

build & deploy

完全にスクリプト化できてなくてごめんけど大体以下

  • buildしてイメージ作る ./build.sh
  • image hashを含む、deployに必要な情報を dev.yml に記載する
  • serverless deploy
  • 初回だけ rails db:migrate 相当のLambda functionをCLI経由でinvokeしておく
$ aws lambda invoke --function-name lambda-run-dev-migrate --payload '{}' response.json # migration

ハマった所、躓いた所

Dockerfile.lambda

AWS提供のBaseImage or Ruby runtime interface clientsを入れた自分のImageの二択 がわからん

  • 公式ドキュメント ちゃんと読めって話である
  • 最初AlpineベースでDockerfileを書いたけど、aws_lambda_ric入れなきゃいかんのがちょっとなんかGemfileに侵入されて嫌。公式BaseImageで作り直した
  • AWS提供のBaseImageはRuby 2.5/2.7しか無い
FROM public.ecr.aws/lambda/ruby:2.7.2020.12.18.21

BaseImageはentrypointに予めaws_lambda_ricが指定されてるので docker run -it public.ecr.aws/lambda/ruby:2.7 /bin/shすると入力受け付けなくて :thinking:

before

$ docker run --rm -it public.ecr.aws/lambda/ruby:2.7 /bin/sh
INFO[0000] exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)
# このまま 

after

$ docker run --rm --entrypoint="" -it public.ecr.aws/lambda/ruby:2.7 /bin/sh
sh-4.2#

私はこれで yum install したりアレヤコレヤ弄りながら Dockerfile 書いていくスタイル

serverless

基本はすごくわかりやすいエントリがあったのでそれを参考にしただけ

CMDENTRYPOINTの上書きにまだ対応してない

rails db:migrateする別のfunctionを別imageで用意して回避

# Dockerfile.lambda
# 略
FROM app as migrate

WORKDIR /var/task

#中略

CMD ["lambda.App::Handler.migrate"]
# lambda.rb
# 略
    def self.migrate(event:, context:)
      p `bin/rails db:create`
      p `bin/rails db:migrate`
      response = {
        'statusCode' => 204
      }
    end
end

Auroraと繋ぐのであればVPC Lambdaにしないといけない

すっかり忘れてて普通に作ってしまったので作り直した。
serverlessだったので serverless remove && serverless deployで簡単

# serverless.yml
  vpc:
    securityGroupIds:
      - ${self:custom.environment.${self:provider.stage}.MY_AWS_SECURITY_GROUP}
    subnetIds:
      - ${self:custom.environment.${self:provider.stage}.MY_AWS_SUBNET_ID_0}
      - ${self:custom.environment.${self:provider.stage}.MY_AWS_SUBNET_ID_1}
      - ${self:custom.environment.${self:provider.stage}.MY_AWS_SUBNET_ID_2}
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "ec2:CreateNetworkInterface"
        - "ec2:DescribeNetworkInterfaces"
        - "ec2:DeleteNetworkInterface"
      Resource:
        - "*"

Rails on Lambda

そもそも LambdaからRailsをどう動かすの??

Ruby on Rails on AWS Lambda and API Gateway by Serverless Frameworkを試してみた で先人が居たので助かった。
もうほぼこれ。大感謝。

aws_lambda_riclambda.rbconfig.ru で動く。

ハンドラからrackに渡すところのコードは以下をベースにconfig.ruの参照パスだけ変えた。

そのため、Railsではアプリケーションサーバとして一般的なpumaは起動しない :exclamation: :exclamation:

結果、invoke時に発生すると想定していた puma起動オーバーヘッドが削減されるという恩恵が有るような気がする。

目からウロコ。棚からぼたもち。

WORKDIRの/var/task/ は書き込み禁止なので Rails.root.join('tmp') に書き込む処理が全部エラー

主に今回ハマったのは bootsnap でのキャッシュファイルと tmp/log/*.log

bootsnapのキャッシュが tmp/cache に書けない

環境変数を使ってbootsnapキャッシュの出力先を /tmp に変更して回避 bootsnapドキュメントの通り

# Dockerfile.lambda
ENV BOOTSNAP_CACHE_DIR=/tmp/cache
tmp/log/development.log にログが書けないので STDOUT にログを出力するように変更

普通にcloudwatch logsに乗らなくて困るのでこれはやるだけだし、その時はRAILS_ENV=developmentだったという話もある。config/environments/development.rb にも書いておくとよい

# config/environments/development.rb
  if ENV["RAILS_LOG_TO_STDOUT"].present?
    logger           = ActiveSupport::Logger.new(STDOUT)
    logger.formatter = config.log_formatter
    config.logger    = ActiveSupport::TaggedLogging.new(logger)
  end
# Dockerfile.lambda
ENV RAILS_LOG_TO_STDOUT=1

負荷掛けるとどんな感じか

一発だけリクエスト投げた時のリソース状況

コールドスタート

$ hay -c 1 -n 1 https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/posts

Summary:
  Total:        3.7599 secs
  Slowest:      3.7597 secs
  Fastest:      3.7597 secs
  Average:      3.7597 secs
  Requests/sec: 0.2660

  Total data:   408 bytes
  Size/request: 408 bytes

Response time histogram:
  3.760 [1]     |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  3.760 [0]     |
  3.760 [0]     |
  3.760 [0]     |
  3.760 [0]     |
  3.760 [0]     |
  3.760 [0]     |
  3.760 [0]     |
  3.760 [0]     |
  3.760 [0]     |
  3.760 [0]     |


Latency distribution:
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs

Details (average, fastest, slowest):
  DNS+dialup:   0.1575 secs, 3.7597 secs, 3.7597 secs
  DNS-lookup:   0.1406 secs, 0.1406 secs, 0.1406 secs
  req write:    0.0001 secs, 0.0001 secs, 0.0001 secs
  resp wait:    3.6014 secs, 3.6014 secs, 3.6014 secs
  resp read:    0.0006 secs, 0.0006 secs, 0.0006 secs

Status code distribution:
  [200] 1 responses

ウォームスタート

$ hay -c 1 -n 1 https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/posts

Summary:
  Total:        0.0556 secs
  Slowest:      0.0556 secs
  Fastest:      0.0556 secs
  Average:      0.0556 secs
  Requests/sec: 17.9856

  Total data:   408 bytes
  Size/request: 408 bytes

Response time histogram:
  0.056 [1]     |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.056 [0]     |
  0.056 [0]     |
  0.056 [0]     |
  0.056 [0]     |
  0.056 [0]     |
  0.056 [0]     |
  0.056 [0]     |
  0.056 [0]     |
  0.056 [0]     |
  0.056 [0]     |


Latency distribution:
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs
  0% in 0.0000 secs

Details (average, fastest, slowest):
  DNS+dialup:   0.0159 secs, 0.0556 secs, 0.0556 secs
  DNS-lookup:   0.0052 secs, 0.0052 secs, 0.0052 secs
  req write:    0.0003 secs, 0.0003 secs, 0.0003 secs
  resp wait:    0.0386 secs, 0.0386 secs, 0.0386 secs
  resp read:    0.0005 secs, 0.0005 secs, 0.0005 secs

Status code distribution:
  [200] 1 responses

Auroraはキャパ1で動作する

image.png

負荷掛けた場合 (300並列 / 3000リクエスト)

$ hay -c 300 -n 3000 https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/posts

Summary:
  Total:        7.3692 secs
  Slowest:      5.1843 secs
  Fastest:      0.0223 secs
  Average:      0.4378 secs
  Requests/sec: 407.1016

  Total data:   370236 bytes
  Size/request: 123 bytes

Response time histogram:
  0.022 [1]     |
  0.538 [2645]  |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  1.055 [59]    |■
  1.571 [3]     |
  2.087 [0]     |
  2.603 [2]     |
  3.120 [5]     |
  3.636 [20]    |
  4.152 [255]   |■■■■
  4.668 [9]     |
  5.184 [1]     |


Latency distribution:
  10% in 0.0351 secs
  25% in 0.0404 secs
  50% in 0.0490 secs
  75% in 0.0728 secs
  90% in 0.8516 secs
  95% in 3.8275 secs
  99% in 4.0212 secs

Details (average, fastest, slowest):
  DNS+dialup:   0.0331 secs, 0.0223 secs, 5.1843 secs
  DNS-lookup:   0.0079 secs, 0.0000 secs, 0.1565 secs
  req write:    0.0009 secs, 0.0000 secs, 0.2680 secs
  resp wait:    0.3979 secs, 0.0222 secs, 4.7219 secs
  resp read:    0.0004 secs, 0.0000 secs, 0.1758 secs

Status code distribution:
  [200] 907 responses
  [500] 2093 responses

さばけてなーい。原因はRDS側のキャパが足りずなので、一定時間立つと...

$ hay -c 300 -n 3000 https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/posts

Summary:
  Total:        1.3416 secs
  Slowest:      0.7870 secs
  Fastest:      0.0215 secs
  Average:      0.1167 secs
  Requests/sec: 2236.2066

  Total data:   844968 bytes
  Size/request: 281 bytes

()

Status code distribution:
  [200] 2071 responses
  [500] 929 responses

image.png

Auroraもサーバレスなんで、勝手にキャパが増えて勝手にだいぶさばける!!!

一定時間経つと...

image.png

ゼロに向かってスケールインするよ

手がつけられなかった所

ActiveJob(sidekiq)もLambdaにしちゃう

aws-sdk-rails gemを使ってハンドラを Aws::Rails::SqsActiveJob.lambda_job_handler にするだけでできそう

ECS Fargate/EKSとのコスト比較

どう考えても有る一定以上のワークロードが有るような商用利用のケースではLambdaの分が悪いはず。とは思う。

まとめ

  • 面白かったのでよかった。
  • Cloud Runで良い気はする。Jetsとかも有るよねという話は十分承知している。悔いはない。
  • 巨大なRailsだとどうかはわからん。検証不足。

おわりに

Ateam Lifestyle Advent Calendar 2020もこれですべて終了です。いかがだったでしょうか?

運営に動いてくれた @engabesi, @asasigure ありがとうございました!

2020年は主にCovid-19もあり、激動の一年でしたね。

来年2021年はどんな年になるんでしょうか。楽しみです!

それではみなさん Happy Holidays!!! :tada:

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3