何をするか?
ここでは、CloudFormation のテンプレートを使って、次のような AWS リソースを定義してみます。
- S3 バケット(Logical ID:
MyBucket
) - SNS トピック (Logical ID:
MyTopic
) - 上記の S3 バケットへの書き込み時に、SNS トピックへ publish
動作のイメージとしてはこんな感じです。
このような構成でリソースを作っておくと、S3 バケット上のデータ更新を、SNS トピックのサブスクライブによって監視できるようになります。 例えば、Lambda 関数を SNS トピックのサブスクライバーとして登録すれば、S3 バケットへの書き込みを Lambda 関数でハンドルできます。
参考情報
CloudFormation テンプレートで S3 バケットや SNS トピックを定義する方法は、下記の記事を参考にしてください。
テンプレートの記述例
次の CloudFormation テンプレートでは、S3 バケットと SNS トピックのリソースを定義しています。 デフォルトの名前はそれぞれ次のようになっています。
- S3 バケット名:
myapp-sample-bucket-<AccountId>
- SNS トピック名:
myapp-sample-topic
S3 バケット名は世界で一意でなければいけないため、末尾に使用中のアカウント ID を付加するようにしています。
このテンプレートを使って CloudFormation スタックを生成するには、例えば AWS CLI を使って次のように実行します。
1 分ほど待つと、スタックの生成が完了します。
あとは、SNS トピックのコンソールから E メールなどをサブスクリプション登録して、S3 バケットに適当にファイルをアップロードすれば、JSON 形式のイベント通知が届くはずです。 その通知には S3 バケット名などが含まれているので、例えば、Lambda 関数から S3 バケットの情報を取り出すといったことが簡単に行えます。
テンプレートの解説
まず、SNS トピックは次のような感じで簡単に定義できます。
S3 バケットから SNS トピックに対して public できるようにする権限を設定するには、S3 バケットを作成する前に、次のような AWS::SNS::TopicPolicy
リソースを作成しておく必要があります。
形としては、Topics
プロパティで指定した SNS トピックに対して、PolicyDocument
プロパティで定義したポリシーが割り当てられるという意味になります。
S3 バケットの定義では、何らかの操作時(例えばオブジェクトの追加時)に、SNS トピックに対して publish を行うという指定を NotificationConfiguration
プロパティで設定します。
この設定を行った状態で CloudFormation スタックを生成しようとすると、S3 バケットリソースの生成時に、SNS トピックポリシーが正しく設定されているかの確認が行われます。
そのため CloudFormation は、依存する SNS トピックポリシー(ここでは前述の MyTopicPolicy
)を先に生成しようとします。
まとめると、次のような順序でリソースが生成されることになります(生成順序は CloudFormation が依存関係に基づいて判断するので、特に意識して記述する必要はありません)。
- SNS トピック
- SNS トピックポリシー
- 定義内で「SNS トピック」と「S3 バケット」を指定する
- S3 バケット
- 適切な「SNS トピックポリシー」が先に定義されている必要がある
ここで問題になるのは、2 番目のトピックポリシーの定義内で S3 バケットを指定するときに、まだ S3 バケットの生成が完了していないということです。
これは、CloudFormation によって 自動で割り当てられる S3 バケット名 (Physical ID) を使用できない ということを意味します。
例えば、トピックポリシーの定義で !Ref MyBucket
のように S3 バケット名を参照しようとすると、そんなバケットはまだ定義されていないというエラーになります。
CloudFormation はできる限り依存関係を解決するような順序でリソースを構築していきますが、S3 バケットに TopicConfigurations
を設定するときは、その振る舞いを許可するための SNS トピックポリシーが事前に生成されている必要があります。
そのため、S3 バケットを SNS トピックポリシーより先に構築することはできません。
これにより、鶏と卵の関係のような「循環参照エラー」が発生します。
この問題の解決方法は こちらの記事 に、以下の 2 種類が紹介されています。
- 入力パラメータなどを使って、S3 バケット名を事前に決めておく(管理する名前が増える・・・)
- S3 バケットの
NotificationConfiguration
を無効にした状態でスタックを生成してから、もう一度今度は有効にした状態でスタックを更新する(手作業すぎるでしょ・・・)
いやいやこれは CloudFormation の設計ミスでしょと言いたくなるのをグッとこらえて、 とにかく何らかの方法で S3 バケット名を参照できるようにしなければいけないので、上記のテンプレート例では、S3 バケットの名前を入力パラメータ (BucketPrefix
) で指定できるようにしています。
あと、ついでに SNS トピック名も入力パラメータ (TopicName
) で指定できるようにしています。