CloudFormation Custom Resourceの意図しない削除を防ぐ

最近はCloudFormationをよく使っています。大変便利なのですが、S3オブジェクトやDynamoDBのItemの作成には対応していないため、Lambda-backed Custom Resourceで作成しています。ただCustom Resourceのライフサイクルは少し独特なところがあって、理解せずに使うと、意図せずResourceが削除されてしまうことがあります(あった)。

意図しない削除を起こさないようにするため、この記事ではいくつかの状況についてCustom Resourceがいつ作られ、いつ更新され、いつ削除されるのかまとめます。Lambda-backed Custom Resourceを想定していますが、SNS-backedでも基本は同じではないかと思います。

PhysicalResourceIDについて

まずは前提として、PhysicalResourceIDについて説明します。PhysicalResourceIDは、Custom Resourceから呼ばれるLambda関数がレスポンスに設定する値の一つです。レスポンスについて説明したドキュメントには以下のように書かれています。

This value should be an identifier unique to the custom resource vendor,
and can be up to 1 Kb in size. The value must be a non-empty string and
must be identical for all responses for the same resource. 

最後のmust be identical for all responses for the same resourceが重要です。ここの意味するところは、Custom Resourceのドキュメントに以下のようにはっきり書かれています。

When AWS CloudFormation receives the response, it compares the
PhysicalResourceId between the old and new custom resources.
If they are different, AWS CloudFormation recognizes the update
as a replacement and sends a delete request to the old resource.

要はPhysicalResourceIDが変わったら古いResourceは削除される、ということですね。

前述のように、PhysicalResourceIDはLambda関数がレスポンスの中に含める値です。レスポンスは、Node.jsの場合はcfn-responseモジュールのsendメソッドで送ることが多いかと思います。PhysicalResourceIDはsendメソッドの5つ目の引数で指定できます。例えば以下のような感じです。

var response = require('cfn-response');
exports.handler = function(event, context) {
  response.send(event, context, response.SUCCESS, {}, 'PhysicalResourceID');
};

cfn-responseモジュールのドキュメントを見ると、PhysicalResourceIDについては以下のように書かれています。

Optional. The unique identifier of the custom resource that invoked
the function. By default, the module uses the name of the Amazon
CloudWatch Logs log stream that is associated with the Lambda function.

PhysicalResourceIDの指定を省略するとLog stream名が使われるとのことです。これは例えば2017/05/13/[$LATEST]2582d4dedea542868953b34db229dd35のような値で、Lambda関数を更新するたびに変わっているようです。

ここまでのポイントとしては以下です。

  • PhysicalResourceIDはCustom ResourceのIDで、Lambda関数のレスポンスに含めることで指定できる。
  • このIDが変わると、古いCustom Resourceが削除される。
  • PhysicalResourceIDを指定しない場合、Lambda関数が更新されるたびに異なるPhysicalResourceIDが設定される。

ここからは、いくつかの状況について、Custom Resourceがいつ作られ、更新され、削除されるのか見ていきます。

PhysicalResourceIDを指定しない場合

以下のようなPhysicalResourceIDを指定しない場合を考えます。CloudFormationのTemplateはこちらから参照できます。

var response = require('cfn-response');
exports.handler = function(event, context) {
  response.send(event, context, response.SUCCESS, {});
};

まずはスタックの作成をします。以下はCloudFormationのEventsタブのスクショです。

f:id:ks888:20170513133719p:plain

Custom Resrouceが作成されています。

次にスタックの更新をします。何か変更しないと更新できないので、スタックに与えるパラメータを変えています。

f:id:ks888:20170513132222p:plain

Custom Resourceが更新されています。

ここでLambda関数を更新してみます。今回はLambda関数に割り当てるメモリサイズをAWSコンソールから変更しました。再びスタックの更新をします。

f:id:ks888:20170513133729p:plain

Custom Resourceが更新されているのに加え、最後に同Resourceが削除されています。これは、Lambda関数の更新に伴ってPhysicalResourceIDが更新されたからです。

もう一度スタックの更新をしてみます。

f:id:ks888:20170513132248p:plain

Custom Resourceが更新されていますが、Physical IDが変わっています。

最後にスタックを削除します。

f:id:ks888:20170513132257p:plain

Custom Resourceが削除されました。

作成から更新、削除までを一通りみました。PhysicalResourceIDを指定しない場合に重要なのは以下の2点かと思います。

  • Lambda関数を更新するとPhysicalResourceIDが変わるため、古いCustom Resourceが削除される。
  • 古いCustom Resourceが削除されても、新しいCustom ResourceのCreateは呼ばれない。

特に2点目はやや意外な挙動な気がするので、ID指定をしない場合は注意したいところです。

PhysicalResourceIDを指定する場合

以下のようにPhysicalResourceIDを指定する場合を考えます。Templateはこちらから参照できます。

var response = require('cfn-response');
exports.handler = function(event, context) {
  response.send(event, context, response.SUCCESS, {}, 'PhysicalResourceID');
};

まずはスタックの作成をします。

f:id:ks888:20170513133741p:plain

Custom Resrouceが作成されています。

次にLambda関数を更新してみた上で、スタックを更新します。

f:id:ks888:20170513132314p:plain

Custom Resourceが更新されています。今度はResourceは削除されていません。

最後にスタックを削除します。

f:id:ks888:20170513132325p:plain

Custom Resourceが削除されました。

シンプルですね。PhysicalResourceIDを指定しておけば、Lambda関数が更新されてもCustom Resourceは削除されません。

まとめ

PhysicalResourceIDを指定しない場合と指定する場合について、Custom Resourceがいつ作成・更新・削除されるかまとめました。Resourceの意図しない削除を防ぐには、PhysicalResourceIDを常に同じ値にしておくのがよさそうです。他にも削除されるケースをご存知でしたら、ぜひ教えてください。