はじめに
メリークリスマス!!
Ateam Lifestyle Advent Calendar 2020の25日目は、
株式会社エイチームライフスタイル 執行役員CTO @tsutorm が担当します!
Lambdaがコンテナイメージサポートと聞いて
今年のアドカレどうしよっかなー
ポエム系になるけど 「ビジネスが間違えやすいエンジニアの"開発生産性"」 みたいなのを書こうかなー
せっかくのクリスマス だしなー。プレゼントになるようなコードがあるほうがいいよなー
と思っていた所...
【速報】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
全体感
構成全景
そんだけ
コード
アプリケーションとしては、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
すると入力受け付けなくて
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
基本はすごくわかりやすいエントリがあったのでそれを参考にしただけ
CMD
やENTRYPOINT
の上書きにまだ対応してない
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_ric
→ lambda.rb
→ config.ru
で動く。
ハンドラからrack
に渡すところのコードは以下をベースにconfig.ru
の参照パスだけ変えた。
そのため、Railsではアプリケーションサーバとして一般的なpuma
は起動しない
結果、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で動作する
負荷掛けた場合 (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
Auroraもサーバレスなんで、勝手にキャパが増えて勝手にだいぶさばける!!!
一定時間経つと...
ゼロに向かってスケールインするよ
手がつけられなかった所
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!!!