CloudFormation StackSets のデプロイ失敗をメール通知する

2023.06.26

CloudFormation(CFn) StackSet の Organizations 連携を使うことで、 AWSアカウントがOUに所属したタイミングで自動でスタック展開する仕組みを作れます。 AWSアカウントのベースライン作成を自動化する手段として、便利に活用できます。

ただ、たまに自動デプロイが失敗することもあります。 パラメータの不具合や、AWSの内部的なエラーなど、理由は様々です。

今回は CFn StackSet のスタックデプロイエラーを検知して通知する仕組みを作ってみます。

作ってみる #シンプル版

Organizationsの管理アカウント上で EventBridgeルールを作成します。 以下のようなプロセスです。

  1. EventBridgeルールでスタックセットインスタンスの「ステータス失敗」イベントをキャッチ
  2. 上記のターゲットにSNSトピックを指定
  3. SNSトピックからメール通知

EventBridgeルールのフィルターは以下のとおりです。 スタックセットインスタンスのステータスコード のうち、 正常でないものを選択しています。

{
  "source": ["aws.cloudformation"],
  "detail-type": ["CloudFormation StackSet StackInstance Status Change"],
  "detail": {
    "status-details": {
      "detailed-status": ["INOPERABLE", "CANCELLED", "FAILED", "SKIPPED_SUSPENDED_ACCOUNT"]
    }
  }
}

また、EventBridgeルールのターゲットにSNSトピックを指定します。 入力トランスフォーマーは以下のような設定を入れています。

▼入力パス

{
  "stack-id": "$.detail.stack-id",
  "detailed-status": "$.detail.status-details.detailed-status",
  "detail-type": "$.detail-type",
  "resources": "$.resources",
  "stack-set-arn": "$.detail.stack-set-arn",
  "time": "$.time",
  "status": "$.detail.status-details.status"
}

▼入力テンプレート

"Title: <detail-type>"
"Time: <time>"

"StackSetARN: <stack-set-arn>"
"StackID: <stack-id>"

"Status: <status>"
"DetailedStatus: <detailed-status>"

通知してみる

スタックセットの更新で無効なパラメータを指定して、 以下のようなエラー表示を出してみました。

img

以下のような通知を受け取りました。

▼CANCELLED

"Title: CloudFormation StackSet StackInstance Status Change"
"Time: 2023-06-23T01:32:13Z"

"StackSetARN: arn:aws:cloudformation:ap-northeast-1:111111111111:stackset/example:f838f626-example"
"StackID: arn:aws:cloudformation:ap-northeast-1:222222222222:stack/StackSet-example-8de0ffac-example/a606ab80-example"

"Status: OUTDATED"
"DetailedStatus: CANCELLED"

▼FAILED

"Title: CloudFormation StackSet StackInstance Status Change"
"Time: 2023-06-23T01:32:13Z"

"StackSetARN: arn:aws:cloudformation:ap-northeast-1:111111111111:stackset/example:f838f626-example"
"StackID: arn:aws:cloudformation:ap-northeast-1:333333333333:stack/StackSet-example-91626397-example/a6299cd0-example"

"Status: OUTDATED"
"DetailedStatus: FAILED"

作ってみる #AWSアカウント名を付与したい

上記でも失敗に気づけるので十分ですが、 AWSアカウントIDのみなので、どのAWSアカウント上で失敗したのかパッと見て分かりにくいです。

そこで 先程のEventBridgeルールのターゲットに Lambda関数を指定して、 Lambda上でメッセージを加工、AWSアカウント名も付与して送るようにしてみます。

img

以下のような Lambda関数(Python 3.10)を作成しています。

import boto3
import os
from logging import getLogger, INFO, log

logger = getLogger()
logger.setLevel(INFO)

sns = boto3.client("sns")
organizations = boto3.client("organizations")

SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN']

def get_account_name(account_id):
    resp = organizations.describe_account(AccountId=account_id)
    return resp['Account']['Name']

def lambda_handler(event, context):
    # #####
    # メッセージ作成に必要なパラメータの取得
    # #####
    detail_type = event['detail-type']
    # ### スタックセット名
    stack_set_arn = event['detail'].get('stack-set-arn')
    stack_set_name = stack_set_arn.split('/')[1].split(':')[0]
    # ### リージョン, アカウントID, アカウント名
    stack_id = event['detail'].get('stack-id')
    region = stack_id.split(':')[3]
    account_id = stack_id.split(':')[4]
    account_name = get_account_name(account_id)
    # ### ステータス, ステータス詳細, ステータス理由
    status = event['detail']['status-details'].get('status')
    detailed_status = event['detail']['status-details'].get('detailed-status')
    status_reason = event['detail']['status-details'].get('status-reason')

    # #####
    # メッセージ作成
    # #####
    message = ( f'スタックセット名: {stack_set_name}\n'
                f'アカウント: {account_name} ({account_id})\n'
                f'リージョン: {region}\n\n'
                f'ステータス: {status}\n'
                f'ステータス詳細: {detailed_status}\n'
                f'ステータス理由: {status_reason}')

    # #####
    # SNS 発行
    # #####
    resp = sns.publish(
        TopicArn=SNS_TOPIC_ARN,
        Subject=detail_type,
        Message=message
    )
    return resp

環境変数の SNS_TOPIC_ARN に通知先のSNSトピックARNを記載します。

また、Lambda関数の実行ロールは以下のとおりです。

  • AWS管理ポリシー: AWSLambdaBasicExecutionRole
  • インラインポリシー: 以下参照
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sns:Publish",
                "organizations:DescribeAccount"
            ],
            "Resource": "*"
        }
    ]
}

通知してみる

同じくスタックセットの更新で無効なパラメータを指定して、更新します。

以下のような通知を受け取りました。

▼CANCELLED

スタックセット名: example
アカウント: AAA (222222222222)
リージョン: ap-northeast-1

ステータス: OUTDATED
ステータス詳細: CANCELLED
ステータス理由: Cancelled since failure tolerance has exceeded

▼FAILED

スタックセット名: example
アカウント: BBB (333333333333)
リージョン: ap-northeast-1

ステータス: OUTDATED
ステータス詳細: FAILED
ステータス理由: ResourceLogicalId:FirstVPC, ResourceType:AWS::EC2::VPC, ResourceStatusReason:Resource handler returned message: "Value (10.3000.0.0/16) for parameter cidrBlock is invalid. ...(略)"

ついでに見やすくして、ステータス理由(status_reason)も記載しています※。

※ステータス理由は先程のシンプル版にも記載しようとしていました。 が、ステータス理由のテキスト内容次第で入力トランスフォーマーが失敗しているようなので諦めました。

おわり

以上 CFnスタックセットのデプロイ失敗を通知する仕組みを作ってみました。 AWSアカウントのベースライン自動構築で失敗していないか、 検知するのに便利だと思います。

以上、参考になれば幸いです。

参考