Serverless Frameworkがすごく便利だけどAWSの容量制限にひっかかってしまって、CloudFormationを使わずLambda関数を消したら大変な目に会いました


こんにちは!

Serverless Frameworkがすごく便利だけど容量制限にひっかかってしまって、CloudFormationを使わずLambda関数を消したら大変な目に会いました。
その記録と今後はこうしましょう、あとエラーログでググったときに手助けになれば良いなという記録です。

結論

先に結論を書いておきます。

  1. Lambdaの容量がバージョンが多いためにいっぱいになる
  2. プラグインで容量を減らそうとするがそもそもそのプラグインを入れてデプロイできない
  3. 容量を減らそうとLambda関数を手動消したらCloudFormationスタックが壊れてデプロイ出来なくなる

解決には消してしまったLambda関数を手動で作成することで解決しました。

今回のポイントは

  1. そもそもコードは絶対にバージョン管理する
  2. その上でServerlessを使う場合には古いバージョンを削除するプラグインを入れる
  3. ServerlessとCloudFormationで管理されているLambda関数は手動で変更しない

です。

今回のトラブルでは次の記事がすごく役に立ちました。(というかトラブる前に実践しておけばよかった…😊)
Serverless Frameworkを本番運用する際にやっておいたほうが良い事

ServerlessでLambdaのデプロイに失敗する

Serverlessで開発中のサービスで、デプロイに失敗するようになりました。
デプロイはCircleCIで自動化(CD)していたのですが、てっきりデプロイされていると思ったら思いっきりSlackにエラーが飛んできました。

CircleCIでログを確認するとこんなログが出ています。

Serverless: Operation failed!

  Serverless Error ---------------------------------------

  An error occurred: YahooDashoSomethingDashactionLambdaFunction - Code storage limit exceeded. (Service: AWSLambda; Status Code: 400; Error Code: CodeStorageExceededException; Request ID: xxxxxx-xxxxx-xxxxx-xxxxxx-xxxxxxx).

  Get Support --------------------------------------------
     Docs:          docs.serverless.com

CodeStorageExceededExceptionというエラー(例外)が出て落ちているようです。

最初はエラーの内容をあまり読んでいなくて、手動でデプロイしても失敗しました。

AWSコンソールでLambdaの確認

AWSコンソールで確認してみました。
Lambdaのダッシュボードを見ると…「コードのストレージが」いっぱいになっています。

なんてこったい\(^o^)/

「っていうか75GB!?LambdaなんてZip化されてても数10MBだしそんなはずないっしょ!」と余裕こいてたのですがダメでした。未だにそんなはずが無い、信じられないって思っているのですがデプロイできないのでしょうがありません。

改めてLambda関数を確認していくと、それぞれの関数でバージョンを保存しており、そのため容量を圧迫しているようです。

過去バージョンを消していくが…

Lambda(Serverless+CloudFormation)では自動でデプロイされたLambda関数をバージョン管理してていますが、今回はそれがアダとなってしまいました。

そこで「総容量が原因でデプロイ出来なくなる事を防ぐ」の記事を見つけたのでこの記事を参考にして古いバージョンを削除する serverless-prune-plugin を導入することにしました。

が、serverless.ymlを変更してデプロイし直そうとしますが失敗してしまいました。
というのも、そもそもこのプラグイン導入を導入するデプロイすら容量不足で失敗しているようです。

しょうがないので手動でこの古いバージョンを消していきます。
コンソール画面でバージョンを選んで、削除をクリックして、削除加療したら空き容量をみて…、と作業していきます。

が、全然容量減りません!75GBに張り付いたままです。
というのもLambdaはひとつの関数、ひとつのバージョンで数10MBしかないので、削除しても削除しても75GBに対して小さく、手動では全然空き容量が増えませんでした。

多少は空いたはずなので行けるかも、と思ってデプロイを試してみますが失敗してしまいます。

(ちなみに後で気がついたのですが、この段階で「ある関数だけ serverless-prune-plugin を導入してデプロイし直し」すれば解決したのかもしれません。が、もしかするとLinuxのログのように75GBを超えてしまっていてダメだったのかもしれません。)

このままではどうにもならないのでLambda関数ごと削除することにします。

Lambda関数を削除するが…

サービスではLambdaのステージング機能を使っていったため、それなりに多くの関数がありました。
この中で本番運用ではない開発ステージングの関数を消すことにしました。
関数を消せば過去のバージョンはすべて消えるため、多少なりスペースができると考えたからです。

ちなみに今回のServerless(CloudFormation)ではそれなりの数のLambda関数を使っていました。
開発メンバーは複数人で、開発期間もそれなりに長くなっていました。
そういった事情でいくつかの関数はコードがバージョン管理されていない1状態でした。
後に「CloudFormationのスタックごと削除する」という選択肢を検討することになるのですが、バージョン管理されていないために断念することになります。

ともあれ手動でいくつかの関数を消したところ、Lambdaの使用容量が 70GB/75GBほどになりました。
これでデプロイできる!と喜んだのもつかの間だったのですが…。

やっぱりデプロイ出来ない

この状態でプラグインを入れて手動でデプロイしようとしたところ、またもエラーが出てしまいました。

UPDATE_ROLLBACK_FAILEDというエラーが出ています。
「ロールバックの失敗…?なぜ…?」ということでググってみます。

最近まで、スタックがUPDATE_ROLLBACK_FAILED状態になった後は、スタックを削除するか、AWSサポートに連絡してスタックを正常な状態に戻すという2つのオプションしかありませんでした。 多くの場合(たとえば、本番ワークロードを実行している場合など)、スタックを削除することは受け入れられないオプションです。
たとえば、サービス制限違反のためにスタックがUPDATE_ROLLBACK_FAILED状態になっている場合は、制限の増加を要求するか、リソースを削除して制限内に収まるようにしてから、継続更新ロールバック機能を使用してロールバックを再開始し、 UPDATE_ROLLBACK_COMPLETE状態。

「スタックを削除するか、AWSサポートに連絡」…だと…?

ということで結構困ってしまいます。今回削除してしまったLambda関数は、CloudFormationで多くのLambda関数を扱っていたため、CloudFormationのスタックを消すというのはご法度だったようです。

ちなみにCloudFormationの出力は下記のようになっています。

この状態から脱することが出来ないかググっていると、下記のような記述を見つけました。

AWS CloudFormation が正常にロールバックできないリソースをスキップできます。スキップするリソースの論理 ID を検索して入力する必要があります。

とのこと。
ということは該当のCloudFormationスタック、もしくはLambda関数のデプロイをスキップすればServerless全体のデプロイができるかも、ということで試してみました。

先ほどの記事にはデプロイをスキップするには

continue-update-rollback を使用してルートスタックを操作可能な状態にするには、resources-to-skip パラメータを使用して、ロールバックに失敗したリソースをスキップする必要があります。この例では、resources-to-skip には以下の項目が含まれます。

ということが書かれていました。該当のARNを調べてスキップさせてみます…、が、うまくいきません。やはり失敗してしまいました。

スタックの削除を検討する

ここまでの調査、試行でそれなりの時間を要してしまいました。
今回の件は開発中のサービスで、開発を継続するためにはServerless側の更新をする必要がありました。
ですがデプロイに失敗するためLambda関数の更新ができず、サービス全体の更新が止まってしまいました。
そこでチームとして「CloudFormationのスタックごと削除してデプロイし直せば良いんじゃないか」という意見が上がります。

ですが今回の件ではスタックの削除は出来ませんでした。
AWSのLambda関数には便利な機能(というかこの機能のせいでバージョン管理されない!)があり、デプロイ済みのコードをダウンロードする機能があります。

すでにチーム内では
A 「この〇〇ってい関数のコードってどこにありますか?」
B 「あー、誰が書いたか分からないのでLambdaから落として確認してください。」
とう状況でした。

つまり すでにデプロイされているLambda関数の中にソースコードが無いものが含まれていて、スタックを削除するとシステムが止まる可能性がある という状況だったのです😨

ある程度のソースコードはGit化されていたので、「別アカウントでServerlessをデプロイし直して、現状管理されているコードでのサービス稼働率を試す」ということも検討されましたが、残念ながら単体テスト、統合テストが無いためにその選択は行いませんでした。
(とはいえ、解決が遅れていたらこの方法を取っていたと思います)

そのため「できる限りデプロイ済みのLambda関数を消さないようにして、CloudFormationスタックをデプロイ可能に復元させる」ということが必要でした。

サービスの新バージョンがリリース出来ない焦りを覚えつつ、なんとか解決できないか検討していきました。

解決へ

ここまできてついに手詰まりかも…、となってしまったのですが、改めてCloudFormationのログを見てみると、スタックに含まれる該当のLambda関数のデプロイに失敗しています。

Function not found: arn:aws:lambda:ap-northeast-1:0000000000000000:function:yahoo-test (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: 00000-0000-0000-0000-000000000000)

またLambda関数名と論理IDは関連があり、ハイフンで区切られた文字をDashという英文字に直した形となっています。

Lambda関数名 論理ID
yahoo-test YahooDashtest

そこでエラーログを元に、消してしまったLambda関数を手動で作成しました。

消してしまってエラーログが出ているLambda関数をすべて作成してServerlessコマンドでデプロイし直すと…、正常にデプロイされました!よかった!

Serverlessでデプロイする -> CloudFormationで管理しているLambda関数が無いのでエラー -> デプロイ出来ない
という悪循環を脱することが出来ました!

その後

その後も今回の件を教訓にして、古いバージョンを削除するプラグインを導入していきました。
その甲斐もあり、かなりの容量を減らすことが出来ました。

が、それでもまだ21GBもあります…。
今後はこういった値の管理が必要です。

まとめ

今回の件でトラブルになってしまったのは下記のような理由です。

  1. Lambdaの容量がバージョンが多いためにいっぱいになる
  2. プラグインで容量を減らそうとするがそもそもそのプラグインを入れてデプロイできない
  3. 容量を減らそうとLambda関数を手動消したらCloudFormationスタックが壊れてデプロイ出来なくなる

解決には消してしまったLambda関数を手動で作成することで解決しました。

今回の件から学ぶべきServerless開発の教訓は

  1. そもそもコードは絶対にバージョン管理する
  2. その上でServerlessを使う場合には古いバージョンを削除させる
  3. ServerlessとCloudFormationで管理されているLambda関数は手動で変更しない

です。
ちなみに今回の件はプロジェクトの途中からCircleCIを使ってデプロイ自体の自動化(CD)していたので、 ** ServerlessはCI/CDツールからじゃないとデプロイできない ** ルールなどを適用するとバージョン管理化は徹底されるはずです。

今回は色々と焦ることになりましたが、今回の件以外にもServerlessの開発のノウハウ、プラクティスを学んで行きたいです。

下記の記事にはすごく助けられました。是非読むことをオススメします。
Serverless Frameworkを本番運用する際にやっておいたほうが良い事


  1. もちろんですがソースコードがGitなどでバージョン管理されていない、誰が持っているのか分からない、というのはかなり危険な状態です。