こんにちは。てぃろです。
今回はAWS Lambdaの設定であるProvisionedConcurrencyをServerless Frameworkを使って設定してみた話をします。
どんな風に設定するのか?そのときの挙動がどのようになるのか?という点を書いていきます。おまけとしてProvisionedConcurrencyに関わって知るとうれしいTipsも解説します。
今回の設定を入れた最小限のサンプルコードは以下のリポジトリでご覧いただけます。
Provisioned Concurrencyは、AWSが公式に提供するLambdaのコールドスタート対策の設定
Lambdaは初回リクエスト時に起動のために時間がかかるという問題があります。これがいわゆるLambdaのコールドスタート問題です。
もともとLambda in VPCの初回起動時間が非常に長いことから有名だったと思いますが、Lambdaのアーキテクチャが刷新されたことでLambda in VPCの初回起動時間の長さはほとんど解消されました。以下が公式の発表記事です。Lambdaのアーキテクチャの何が問題でどんなふうに解決したかも詳しく載っています。
ただ、VPCに関係なくLambdaも起動が必要です。なのでVPC内にあるかどうかに関わらず初回リクエストのときには起動の時間がかかってしまうのは仕方がないことです。
Provisioned Concurrencyはこの起動時間の問題(コールドスタート問題)を解消できる設定です。
簡単に言うと指定の数だけLambdaを常時起動させることができる設定です。常時起動するので、常にリクエストに即応できるからコールドスタート問題を解消できる、というわけです。
Provisioned Concurrencyについて、もっと詳しく知りたい人は ↓ の記事を参照ください。
では、その設定をどこでするのか?ここからはServerless Frameworkでの設定を解説します。
Serverless FrameworkでのProvisioned Concurrencyの設定は、たった一行!
Serverless FrameworkでProvisioned Concurrencyの設定は以下のように一行だけfunctionsに追加するだけです。これでデプロイし直せばOK。
functions:
hello:
handler: handler.hello
events:
- http:
path: /hello
method: get
provisionedConcurrency: 5 // これだけ!
ここで設定している数字はLamdaの起動数ですので、Lambdaの同時実行数を消費してしまいます。
Lambdaはアカウントごとに同時実行数の上限が存在します。同時実行数の上限はProvisioned Concurrencyだけに関わっているのではなく、Lambda関数そのものの実行にも影響します。設定値の合計数が多すぎると最悪Lambdaが起動できなくてAPIの処理ができない、ということも出てきますのでサービスレベルや非機能要件をきちんと設計する必要があります。
以下でServerless Frameworkの人が設定方法を解説してくれてますので、詳しくはこちらを参照ください。
デプロイすると自動でバージョンが作られてエイリアスも作られる
Serverless Frameworkでは先に見た通り設定は一行ですが、マネジメントコンソールで見ると設定する時には同時実行数だけでなく、エイリアスまたはバージョンを指定する必要があることがわかります。
つまり、$LATESTは設定できず事前にエイリアスかバージョンを作って指定する必要があるのです。
しかし、Serverless Frameworkでデプロイすると、自動でバージョンを新たに作ってエイリアスの割り当てまでやってくれます。
これは便利!と思いきや実は落とし穴もあります。
バージョンが自動で作られるとストレージを圧迫してリリース障害を引き起こす危険がある
バージョンを勝手に作ってくれるのは便利ですが、いいことばかりではありません。
Serverless Frameworkで新たなコードをデプロイをすると、その度に新たなバージョンが自動で作られます。それは消さない限りどんどん溜まっていって、アカウントあたりに割り当てられているLambdaのソースのストレージを圧迫してしまいまいます。
上記のように、上限は75GBですので、なかなか溜まらないと思いがちですが、放っておくといつかは上限に達し、本番ではリリース障害になりかねません。
ならば、新たなバージョンを作らないような設定をすればいいのです!
実はServerless FrameworkではLambdaのバージョニングをしないよう設定が可能です。設定は以下の一行だけです。
versionFunctions: false
この設定をすると最初のバージョンと最新のバージョンの二つを残すようにしてくれます。
これで古いバージョンがどんどんたまっていくということはなくなりました!安心してデプロイを続けることができますね!
最後に
Provisioned Concurrencyの設定は、世界中のAWS Lambdaユーザが待ち望んだ機能だったと思うのですが、本番で使おうとすると気を付けるべき点がいくらかあるということが明らかになりました。
バージョンを作るという挙動は最初意外に感じましたが、常時起動させるという特性上自然なアーキテクチャだったのかな、とも思ってきました。
またProvisioned ConcurrencyはLambdaを常時起動させるという特性上、Lambdaの動いた時だけ課金という長所を奪ってしまいます。
そのためすべてのLambdaで全部有効化しておけばいいというものでもないです。課金のことも考えて賢くLambdaを使いこなしていきたいですね。
—
このあとはおまけです。記事から少し話がそれるので後回しにしましたが、ハマることがある人もあると思ったので残しておきます。
おまけ1:serverless-prune-pluginを使った古いバージョンの自動削除
実は本文にあったようにバージョニングをfalseに設定しても、既に作られた古いバージョンや最初のバージョンは削除されません。そのため開発中に実験でデプロイしたものが残ってストレージを圧迫し続けるという状況はありえます。
そんな時に使えるのがここで紹介するserverless-prune-pluginというプラグインです。
これはLambdaの古いバージョンを削除することができるプラグインです。
基本的な使い方は、以下の3つの手順を踏むだけです。
- npmでプラグインをインストール
npm install --save-dev serverless-prune-plugin
2. serverless.ymlにpluginの設定を書き込む
plugins:
- serverless-prune-plugin
3. コマンドで削除を実行
serverless prune -n 0
“-n”は必須のオプションです。これが意味するところは最新以外の残すバージョンの数です。つまり、Provisioned Concurrencyで必要なのは最新のバージョン1つだけなので、0に設定すればOKです。0だと最初のバージョンも消えてくれるので本当に使うバージョンだけが残ることになります。
自動化するのであれば、CI/CDスクリプトで`serverless deploy`した後に上記の実行コマンドを入れておくのがよいでしょう。
おまけ2:serverless-plugin-split-stacksを使った場合の問題
こちらはServerless Frameworkのブラグインであるserverless-plugin-split-stacksを使った場合の問題です。ちょっと特殊な気がしたので、おまけに回しました。
そこそこ大きなサービスの開発をしていると、CloudFormationのスタック分割が必要になることがあります。そこで便利なのがserverless-plugin-split-stacksです。
これをインストールして設定しておくと、CloudFormationのスタック分割を勝手にやってくれるという優れものです!
ですが、Provisioned Concurrencyと合わせて使うと、以下のようなエラーが出ることがあります。
Template format error: Outputs count 81 is greater than max allowed 60.
これはCloudFormationのスタック分割をしているときに起きることがあるようです。特にAPI Gatewayを使った開発をしているとAPINestedStackというスタックが作られており、その中で出力される項目数が60を超えると出るようです。以下のissueで議論され続けています。
この出力項目はバージョニングしたものも追加されてしまうらしく、新たにProvisioned Concurrencyを設定してバージョニングが有効になったことで制限に引っかかるようになったようです。
issueの議論の中では、バージョニングをOFFにするようにアドバイスされていますが、根本解決策とまでは言えなさそうです。とはいえ、CloudFormationの制約でもあり60という上限を上げられるわけではありません。
そのため対策としては、一回あたりのデプロイするLambda関数を減らすくらいしかないかな、と思います。
特にAPIを大量に開発している場合APIのみ別リポジトリにしたり、Provisioned Concurrencyを設定するLambdaを限定するなどして出力される項目数を意図的に減らすようにするのがよさそうです。