Serverless FrameworkからCDKへ
みなさんご存知の通り、サーバレスアプリケーションを便利に開発するためのライブラリであるServerless Frameworkはv4から有償になっています。
CDK移行を検討している方も多いのではないかと思っていますが、今回はそんな時に直面した課題と解決策らしきものを共有します。
全リソースにDeletionPolicyを適用する方法と、その難しさ
Serverless Framework (以下sls) は非常に便利なツールですが、CDKへの移行を考えたとき、すべてのリソースにCloudFormationのDeletionPolicy: Retainを一括で適用したくなることがあります。
しかし、この単純に見える作業は、slsの設計思想に起因する特有の難しさを伴います。
この記事では、sls packageコマンドを用いた検証に基づき、DeletionPolicyを適用する具体的な手順と、そのアプローチに伴う課題を解説します。
補足
なぜDeletionPolicy: Retainを適用する必要があるかというと、cdk importを実施するために、slsによってデプロイされたスタックを一度削除してから、そこで管理されていたリソースをCDKの管理下に置く必要があるからです。
そもそも既存リソースのCDK移行には、cdk migrateとcdk import二つの手段が存在しています。
ただしcdk migrateは現状ネストされたスタックには対応していないため、今回私たちのプロジェクトでは取りうる選択肢にはなりませんでした![]()
よって「cdk importを行う必要がある→リソースを残してスタックを削除したい→DeletionPolicy: Retainを適用する必要がある」というロジックです。
なぜDeletionPolicyの一括適用は難しいのか?
slsには、全リソースにDeletionPolicyを一括適用するような便利なオプションは用意されていません。
それではserverelss.yml内に定義された各リソースごとのセクションで、DeletionPolicyを付与していけばいいのかというと、そういうわけでもありません。
その理由は、slsが開発者の利便性のために多くのリソースを抽象化し、暗黙的に生成しているためです。
例えば、serverless.ymlにLambda関数を定義するだけで、slsは以下のリソースを自動的に作成します。
- Lambda実行用のIAMロール (
IamRoleLambdaExecution) - CloudWatch Logsのロググループ (
...LogGroup) - API Gatewayの各種リソース (
ApiGatewayRestApiなど)
これらのリソースはserverless.ymlに明示的に記述されていないため、DeletionPolicyを直接書き加える場所がないのです。
sls packageでデフォルトのテンプレートを確認する
それではまず、sls packageコマンドを使い、slsが内部で生成するCloudFormationテンプレートを確認します。
このコマンドはデプロイを行わず、.serverlessディレクトリにテンプレートファイルを作成します。
service: sls-runtime-test
frameworkVersion: "3"
provider:
name: aws
runtime: nodejs20.x
functions:
hello:
handler: handler.hello
events:
- s3:
bucket: some-bucket
event: s3:ObjectCreated:*
existing: true
sls package実行後に生成される.serverless/cloudformation-template-update-stack.jsonを確認すると、IamRoleLambdaExecutionやHelloLambdaFunctionといったリソースにDeletionPolicyが設定されていないことがわかります。これがデフォルトの状態です。
resources.extensionsによるテンプレートの上書き
この課題に対する解決策として挙げられるのは、resources.extensionsブロックによるテンプレートの上書きです。
修正方法
DeletionPolicyのように追加したいプロパティのみを記述します。
# ...
resources:
extensions:
HelloLambdaFunction:
DeletionPolicy: Retain
IamRoleLambdaExecution:
DeletionPolicy: Retain
この修正後に再度sls packageを実行すると、生成されたテンプレート内の対象リソースに"DeletionPolicy": "Retain"が正しく追加されていることが確認できます。
課題(つらみ)
一見この方法はかなり有効かつ簡単ですが、実際のプロジェクトで移行するには、いくつか無視できない課題がありました。
全リソースの論理IDを特定する手間
sls packageで生成されたテンプレートから、DeletionPolicyを適用したいすべてのリソースの論理IDを一つずつ手動で抽出し、serverless.ymlに転記する必要があります。これはリソース数が増えるほど、手間とヒューマンエラーのリスクが増大します。
Colpilotに作ってもらったスクリプトで対応
手動で抽出するのは面倒だったので、Copilot Agentに要件をプロンプトとして渡して、論理IDを抽出し、serverless.ymlに追記するためのスクリプトを作成してもいました。
こういう一回限りの使い捨て便利スクリプトをAIに作ってもらうのはとても良いですよね。
動的な論理IDへの対応
ApiGatewayDeployment{randomNumber}のように、デプロイのたびに論理IDが変化するリソースも存在します。
当初、ApiGatewayDeployment${sls:instanceId}のような変数で対応できないか試みましたが、Serverless Frameworkの仕様上、serverless.ymlのキー部分には変数を展開できないため、この方法は利用できませんでした。
(DependsOnとかだったらこの記事のように、割と簡単に設定できるんですよね・・・)
この問題への対応策は、「デプロイ後にAWSコンソールから手動でテンプレートを更新する」というものしかわかっていません。
なにか良い方法ご存知の方がいればぜひコメントいただきたいです。
デプロイ失敗時の後片付け問題
今回の検証に際して、sls deployコマンドを使って新規にスタックを作成したりすることが多かったのですが、DeletionPolicy: Retain を設定した状態で新しいスタックのデプロイに失敗すると、CloudFormationのロールバック機能が働かず、中途半端に作成されたリソースがAWSアカウントに残り続けてしまう、という厄介な問題がありました。
これにより、再デプロイのために手動でのリソース削除が必要な場面が多く発生していました。
RetainExceptOnCreateによる解決
しかし、この問題については、2024年のAWSアップデートで解決策が提供されていたようでした。
DeletionPolicyに新しい値としてRetainExceptOnCreateが指定できるようになったのです。
-
RetainExceptOnCreateの挙動-
スタック新規作成時:
Deleteと同じ挙動。デプロイが失敗すれば、リソースは自動で削除される。 -
スタック更新・削除時:
Retainと同じ挙動。リソースは保護される。
-
スタック新規作成時:
これにより、serverless.ymlを以下のように記述するだけで、検証時の簡便さとデプロイ後のリソース保護を両立できるようになりました。
# ...
resources:
extensions:
HelloLambdaFunction:
DeletionPolicy: RetainExceptOnCreate
IamRoleLambdaExecution:
DeletionPolicy: RetainExceptOnCreate
まとめ
Serverless Frameworkで、プラグインを使わずに全リソースへDeletionPolicyを適用する方法は、「可能だが、相応の手間を要する」というのが私の結論です。
この手間というのは、slsがこれまで提供してくれていた恩恵(多くのリソースを抽象化することによる開発時の利便性)とのトレードオフの関係にあると思います。
今回紹介した手順を「CDK移行のための一時的な作業」と割り切るかどうするかは、プロジェクトの状況に応じた判断が求められるのだと思います。
リソースを引き継ぐことなく、新規スタックを作成して、必要なデータだけ移行してくるというのも検討対象となるのだと思います。
この記事が、同様の課題に直面した開発者の参考になれば幸いです。