[アップデート] CloudFormationの各種イベントがAmazon EventBridgeに送信されるようになりました

CloudFormationのイベントを駆使するアーキテクチャの実装ができるぞ
2022.07.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

スタックに更新があったことをトリガーに処理を行いたいな

こんにちは、のんピ(@non____97)です。

皆さんはCloudFormationのスタックに更新があったことをトリガーに何か処理を行いたいと思ったことはありますか? 私はあります。

例えば、スタック内の特定リソースが更新されたら、Lambda関数で更新されたリソースの情報を使って関連リソースの設定内容を更新したい場面があります。

今回、CloudFormationの各種イベントがAmazon EventBridgeに送信されるようになったとアナウンスされました。

スタック間の連携や、ドリフト検出通知に使えそうですね。

早速試してみたので紹介します。

対象のイベント

今回のアップデートでサポートされたCloudFormationのイベントを確認します。

サポート対象のイベントは以下の3つです。

  • リソースのステータス
  • スタックのステータス
  • ドリフト検出ステータス

スタックのステータスだけでなく、リソースやドリフト検出のステータスを検知できるのはありがたいですね。

EventBridgeのサンドボックスを確認すると、早速追加されたイベントのサンプルイベントが追加されていました。

それぞれのサンプルイベントは以下の通りです。

CloudFormation Resource Status Changeのサンプルイベント

{
  "version": "0",
  "source": "aws.cloudformation",
  "account": "123456789012",
  "id": "12345678-1234-1234-1234-111122223333",
  "region": "us-east-1",
  "detail-type": "CloudFormation Resource Status Change",
  "time": "2022-04-31T17:00:00Z",
  "resources": ["arn:aws:cloudformation:us-east-1:123456789012:stack/teststack"],
  "detail": {
    "stack-id": "arn:aws:cloudformation:us-west-1:123456789012:stack/teststack",
    "logical-resource-id": "my-s3-bucket",
    "physical-resource-id": "arn:aws:s3:us-east-1:123456789012:bucket:my-s3-bucket",
    "status-details": {
      "status": "CREATE_COMPLETE",
      "status-reason": ""
    },
    "resource-type": "AWS::S3::Bucket"
  }
}

CloudFormation Stack Status Changeのサンプルイベント

{
  "version": "0",
  "source": "aws.cloudformation",
  "account": "123456789012",
  "id": "12345678-1234-1234-1234-111122223333",
  "region": "us-east-1",
  "detail-type": "CloudFormation Stack Status Change",
  "time": "2022-04-31T17:00:00Z",
  "resources": ["arn:aws:cloudformation:us-east-1:123456789012:stack/teststack"],
  "detail": {
    "stack-id": "arn:aws:cloudformation:us-west-1:123456789012:stack/teststack",
    "status-details": {
      "status": "CREATE_COMPLETE",
      "status-reason": ""
    }
  }
}

CloudFormation Drift Detection Status Changeのサンプルイベント

{
  "version": "0",
  "source": "aws.cloudformation",
  "account": "123456789012",
  "id": "12345678-1234-1234-1234-111122223333",
  "region": "us-east-1",
  "detail-type": "CloudFormation Drift Detection Status Change",
  "time": "2022-04-31T17:00:00Z",
  "resources": ["arn:aws:cloudformation:us-east-1:123456789012:stack/teststack"],
  "detail": {
    "stack-id": "arn:aws:cloudformation:us-west-1:123456789012:stack/teststack",
    "stack-drift-detection-id": "12345678-1234-123a-ab12-1234example",
    "status-details": {
      "stack-drift-status": "DRIFTED",
      "detection-status": "DETECTION_COMPLETE"
    },
    "drift-detection-details": {
      "drifted-stack-resource-count": 1
    }
  }
}

各ステータスがどの状態になっているのかはもちろん、リソースの論理IDや物理ID、ドリフトしているリソースの数なども通知されるのはありがたいです。Lambda関数でメッセージを整形してSlackに通知するのが捗りそうです。

スタックとリソースのイベントは以下の通りです。

ステータス 説明
CREATE_COMPLETE 1つ以上のスタックまたはリソースの作成が正常に完了
CREATE_IN_PROGRESS 1つ以上のスタックまたはリソースを作成中
CREATE_FAILED 1つ以上のスタックまたはリソースの作成に失敗
DELETE_COMPLETE 1つ以上のスタックまたはリソースの削除を正常に完了
DELETE_FAILED 1つ以上のスタックまたはリソースの削除に失敗
DELETE_IN_PROGRESS 1つ以上のスタックまたはリソースの削除中
REVIEW_IN_PROGRESS テンプレートやリソースがないにもかかわらず、予想されるStackIdを持つ1つまたは複数のスタックが作成中
ROLLBACK_COMPLETE スタックの作成に失敗またはスタックの作成の明示的なキャンセル後に1つ以上のスタックを正常に削除
ROLLBACK_FAILED スタックの作成に失敗またはスタックの作成の明示的なキャンセル後に1つ以上のスタックの削除に失敗
ROLLBACK_IN_PROGRESS スタックの作成に失敗またはスタックの作成の明示的なキャンセル後に1つ以上のスタックを削除中
UPDATE_COMPLETE 1つ以上のスタックまたはリソース更新が正常に完了
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS スタックの正常な更新後に1つ以上のスタックの古いリソースを削除中
UPDATE_FAILED 1つ以上のスタックまたはリソースの更新に失敗
UPDATE_IN_PROGRESS 1つ以上のスタックまたはリソースを更新中
UPDATE_ROLLBACK_COMPLETE スタックの更新の失敗後、1つ以上のスタックを前の動作状態へのロールバックを正常に完了
UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS スタックの更新の失敗後、1つ以上のスタックの新しいリソースを削除中
UPDATE_ROLLBACK_FAILED スタックの更新の失敗後、1つ以上のスタックを前の動作ステートに戻すことに失敗
UPDATE_ROLLBACK_IN_PROGRESS スタックの更新の失敗後、1つ以上のスタックを前の動作状態へロールバック中
IMPORT_IN_PROGRESS インポートオペレーションが進行中
IMPORT_COMPLETE resource import をサポートするスタック内のすべてのリソースについてインポートが正常に完了
IMPORT_ROLLBACK_IN_PROGRESS インポートの以前のテンプレート構成にロールバック中
IMPORT_ROLLBACK_FAILED スタック内の少なくとも1つのリソースでインポートロールバックに失敗
IMPORT_ROLLBACK_COMPLETE インポートが以前のテンプレート構成に正常にロールバック完了

参考 : スタックの情報とリストの取得 - AWS CloudFormation

ドリフト検出ステータスは以下の3種類があります。

  • ドリフト検出オペレーションステータス
  • ドリフトステータス
  • リソースドリフトステータス

その内、EventBridgeに送信されるイベントに含まれるステータスは、ドリフト検出オペレーションステータスとドリフトステータスです。

それぞれのステータスは以下の通りです。

ドリフト検出オペレーションステータス

ステータス 説明
DETECTION_COMPLETE スタックドリフト検出オペレーションがドリフト検出をサポートするスタック内のすべてのリソースに対して正常に完了
DETECTION_FAILED スタックドリフト検出オペレーションがスタック内の少なくとも1つのリソースに対して失敗
DETECTION_IN_PROGRESS スタックドリフト検出オペレーションを実行中

ドリフトステータス

ステータス 説明
DRIFTED スタックがテンプレートと異なる
NOT_CHECKED スタック、スタックセット、またはスタックインスタンスがテンプレートと異なるかどうかをチェックしていない
IN_SYNC サポートされている各リソースの現在の設定がテンプレートと一致している

参考 : スタックとリソースに対するアンマネージド型設定変更の検出 - AWS CloudFormation

なお、全てのリソースのドリフトを検出できるわけではありません。ドリフト検出オペレーションをサポートするリソースは以下AWS公式ドキュメントをご覧ください。

やってみた

検証の構成

検証の構成は以下の通りです。

構成図

各イベントをトリガーにEメール通知とAWS Chat経由でのSlack通知を行います。

  • リソースのステータス変更
  • スタックのステータス変更
  • ドリフト検出ステータス変更

こちらのリソースをAWS CDKでデプロイ後、再度スタックを更新してどのようなメッセージが送信されるか確認します。

リソースのデプロイ

それでは、AWS CDKで各種リソースをデプロイします。使用したコードは以下リポジトリに保存しています。

デプロイする際は.envファイルに以下情報を入力しておきます。

AWS_CHATBOT_WORKSPACE_ID=<AWS ChatbotのワークスペースのID>
SLACK_CHANNEL_ID=<通知先のSlackチャンネルのID>
SLACK_CHANNEL_NAME=<通知先のSlackチャンネルの名前>
EMAIL_ADDRESS=<通知先のメールアドレス>

通知確認

デプロイ後、AWS Chatbotのログ保存期間を1週間から2週間に修正して再デプロイします。

./lib/cfn-events-stack.ts

    new chatbot.SlackChannelConfiguration(this, "Slack Channel", {
      slackChannelConfigurationName: props.slackChannelName,
      slackWorkspaceId: props.awsChatbotWorkspaceID,
      slackChannelId: props.slackChannelID,
      logRetention: logs.RetentionDays.TWO_WEEKS,
      loggingLevel: chatbot.LoggingLevel.INFO,
      notificationTopics: [topic],
    });

再デプロイすると以下のようなSlack通知が来ていました。

スタックとリソース更新の通知

スタックやリソースに対して何かした更新があったことは分かりますが、どのようなステータスになったのかは表示されませんでした。

スタックにSNS通知を設定することで、スタックに何か更新があったらSNS経由でメールやSlackに通知することができます。

こちらの方が詳細な情報を通知することができるので、手軽に更新を通知したい場合は、スタックにSNS通知を設定した方が良さそうですね。

メールも5件来ていました。メッセージ本文のJSONは以下の通りです。

{
  "version": "0",
  "id": "d27886c4-36b8-e9e7-b1e2-f8d8959c1d0f",
  "detail-type": "CloudFormation Stack Status Change",
  "source": "aws.cloudformation",
  "account": "<AWSアカウントID>",
  "time": "2022-07-22T09:19:06Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7"
  ],
  "detail": {
    "stack-id": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7",
    "status-details": {
      "status": "UPDATE_IN_PROGRESS",
      "status-reason": "User Initiated"
    }
  }
}

{
  "version": "0",
  "id": "db3e571f-190b-9e8d-a9ff-7bdf178680fa",
  "detail-type": "CloudFormation Resource Status Change",
  "source": "aws.cloudformation",
  "account": "<AWSアカウントID>",
  "time": "2022-07-22T09:19:12Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7"
  ],
  "detail": {
    "stack-id": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7",
    "logical-resource-id": "SlackChannelLogRetention46A4D367",
    "physical-resource-id": "/aws/chatbot/times-non-97",
    "resource-type": "Custom::LogRetention",
    "status-details": {
      "status": "UPDATE_IN_PROGRESS",
      "status-reason": ""
    }
  }
}

{
  "version": "0",
  "id": "aa9828bd-03ac-77ec-cf36-4ebec1ebe09e",
  "detail-type": "CloudFormation Resource Status Change",
  "source": "aws.cloudformation",
  "account": "<AWSアカウントID>",
  "time": "2022-07-22T09:19:16Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7"
  ],
  "detail": {
    "stack-id": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7",
    "logical-resource-id": "SlackChannelLogRetention46A4D367",
    "physical-resource-id": "/aws/chatbot/times-non-97",
    "resource-type": "Custom::LogRetention",
    "status-details": {
      "status": "UPDATE_COMPLETE",
      "status-reason": ""
    }
  }
}

{
  "version": "0",
  "id": "323fa81d-7dc2-24a2-685b-b4036c115570",
  "detail-type": "CloudFormation Stack Status Change",
  "source": "aws.cloudformation",
  "account": "<AWSアカウントID>",
  "time": "2022-07-22T09:19:18Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7"
  ],
  "detail": {
    "stack-id": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7",
    "status-details": {
      "status": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS",
      "status-reason": ""
    }
  }
}

{
  "version": "0",
  "id": "9246974f-baf3-cf9b-15b6-c39211042d12",
  "detail-type": "CloudFormation Stack Status Change",
  "source": "aws.cloudformation",
  "account": "<AWSアカウントID>",
  "time": "2022-07-22T09:19:18Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7"
  ],
  "detail": {
    "stack-id": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7",
    "status-details": {
      "status": "UPDATE_COMPLETE",
      "status-reason": ""
    }
  }
}

EventBridgeに送信されたイベントをそのまま確認できました。通知内容をEventBridgeのインプットトランスフォーマーで整形すれば、かなり観やすくなりそうです。

次にドリフト検出のイベントを確認してみます。

スタックアクション-ドリフトを検出をクリックします。

ドリフトの検出

しばらくすると、SNSトピックについてのドリフトが検出されました。

ドリフト結果の確認

内容を確認すると、SNSトピックにAWS Chatbotのエンドポイントのサブスクリプションが追加ことによるドリフトのようです。

ドリフトの詳細

通知内容を確認します。

Slackに通知されたメッセージは1件のみでした。

ドリフト検知時のSlackへの通知

メールも1件のみでした。

{
  "version": "0",
  "id": "8b3095e7-e4a6-936c-e86d-611c8644720a",
  "detail-type": "CloudFormation Drift Detection Status Change",
  "source": "aws.cloudformation",
  "account": "<AWSアカウントID>",
  "time": "2022-07-22T09:41:07Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7"
  ],
  "detail": {
    "stack-id": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CfnEventsStack/52856e20-0960-11ed-bde0-0aa886aa4ef7",
    "stack-drift-detection-id": "69909b70-09a2-11ed-90e8-123de96e8c03",
    "status-details": {
      "stack-drift-status": "",
      "detection-status": "DETECTION_IN_PROGRESS"
    },
    "drift-detection-details": {
      "drifted-stack-resource-count": -1
    }
  }
}

DETECTION_COMPLETEDRIFTEDの通知も来ると思ったのですが、来ませんでした。

AMIの作成や登録解除などのイベントがAmazon EventBridgeに送信されるようになったアップデート当日も一部イベントは検知できなかったので、一過性のものだと信じます。

CloudFormationのイベントを駆使するアーキテクチャの実装ができるぞ

CloudFormationの各種イベントがAmazon EventBridgeに送信されるようになったアップデートを紹介しました。

今まで同様の仕組みを採用する場合は、スタックやリソースのステータスをポーリングする必要がありました。今回のアップデートでその手間がなくなりました。

CloudFormationのイベントを使って色々なアーキテクチャが考えられそうですね。

なお、EventBridgeルール経由でSlackに通知する際は、このままだと情報が足りないと思うので、Lambda関数かEventBridgeのインプットトランスフォーマーでメッセージを整形することをおすすめします。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!