[AWS CDK] EventBridge API DestinationでGoogle ChatへCloudWatchアラームの通知をしてみた
Google Chatにも通知したい
こんにちは、のんピ(@non____97)です。
皆さんはAWS上で発生したイベントをGoogle Chatに通知したいなと思ったことはありますか? 私はあります。
SlackやTeamsへの通知については調べると、よく出てきます。
Google Chatへの通知に関する記事もありにはしましたが、Lambda関数からWebhookでPOSTするような形がほとんどでした。
指定したHTTPエンドポイントにPOSTするだけなら、EventBridge API Destinationでもできます。
せっかくなので、LambdaレスでGoogle Chatに通知をしてみました。
やってみた
検証環境の構成
検証環境の構成は以下のとおりです。
![[AWS CDK] EventBridge API DestinationでGoogle ChatへCloudWatchアラームの通知をしてみた検証環境構成図.png](https://devio2024-2-media.developers.io/upload/0RqxpJ2Gm3nEdxVLGKLWmq/2025-05-14/nlNVwqyHVTR3.png)
CloudWatch Alarmの状態遷移をEventBridge Ruleで拾い、EventBridge API DestinationでGoogle ChatのWebhook URLにPOSTします。
また、通知するメッセージの整形はEventBridge Input Transformerで行います。
Webhook URLの発行
まずはWebhook URLの発行です。
適当に通知先のチャンネルを用意しました。
アプリと統合-WebhookからWebhookを追加をクリックします。

適当に名前をつけて保存します。

Webhook URLが発行されたことを確認します。

発行されたWebhook URLはSSM Parameter Storeに保存しておきます。

各種リソースのデプロイ
CloudWatchアラームやEventBridge API Destinationなどの各種リソースをデプロイします。
デプロイはAWS CDKで行いました。使用したコードは以下リポジトリに保存しています。
少し補足します。
EventBridge API Destination周りは以下のとおりです。
    const destination = new cdk.aws_events.ApiDestination(this, "Destination", {
      connection,
      endpoint: cdk.aws_ssm.StringParameter.fromStringParameterAttributes(
        this,
        "Endpoint",
        {
          parameterName: "/google-chat/webhook/aws-notification",
        }
      ).stringValue,
      httpMethod: cdk.aws_events.HttpMethod.POST,
    });
    const role = new cdk.aws_iam.Role(this, "Role", {
      assumedBy: new cdk.aws_iam.ServicePrincipal("events.amazonaws.com"),
      inlinePolicies: {
        InvokeApiDestination: new cdk.aws_iam.PolicyDocument({
          statements: [
            new cdk.aws_iam.PolicyStatement({
              effect: cdk.aws_iam.Effect.ALLOW,
              actions: ["events:InvokeApiDestination"],
              resources: [destination.apiDestinationArn],
            }),
          ],
        }),
      },
先ほど用意したWebhook URLを保存しているSSM Parameter Storeを参照するようにしています。
なんとなくSecure Stringにしたかったのですが、Secure Stringを受け付けられるプロパティは決まっています。注意しましょう。
| リソース | プロパティタイプ | プロパティ | 
|---|---|---|
| AWS::DirectoryService::MicrosoftAD | Password | |
| AWS::DirectoryService::SimpleAD | Password | |
| AWS::ElastiCache::ReplicationGroup | AuthToken | |
| AWS::IAM::User | LoginProfile | Password | 
| AWS::KinesisFirehose::DeliveryStream | RedshiftDestinationConfiguration | Password | 
| AWS::OpsWorks::App | Source | Password | 
| AWS::OpsWorks::Stack | CustomCookbooksSource | Password | 
| AWS::OpsWorks::Stack | RdsDbInstances | DbPassword | 
| AWS::RDS::DBCluster | MasterUserPassword | |
| AWS::RDS::DBInstance | MasterUserPassword | |
| AWS::Redshift::Cluster | MasterUserPassword | 
抜粋 : Systems Manager Parameter Store から SecureString 値を取得する - AWS CloudFormation
通知するメッセージの組み立てに使用しているEventBridge Input Transformer周りは以下のとおりです。
    const inputTemplate = JSON.stringify({
      text:
        "👋🌎 こんにちは! AWSに関する通知だよ!!" +
        "以下のメッセージを確認してね 🌎👋",
      cards_v2: [
        {
          card: {
            header: {
              title: "CloudWatch Alarm State Change",
              image_url:
                "https://fonts.gstatic.com/s/i/short-term/release/googlesymbols/error/default/24px.svg",
            },
            sections: [
              {
                header:
                  "\u003ca href='https://<region>.console.aws.amazon.com/cloudwatch/home?region=<region>#alarmsV2:alarm/<alarmName>'\u003eCloudWatchアラーム情報\u003c/a\u003e\u003c/a\u003e",
                widgets: [
                  {
                    text_paragraph: {
                      text: "AWSアカウントID : <account>",
                    },
                  },
                  {
                    text_paragraph: {
                      text: "リージョン : <region>",
                    },
                  },
                  {
                    text_paragraph: {
                      text: "CloudWatchアラーム名 : <alarmName>",
                    },
                  },
                  {
                    text_paragraph: {
                      text: "CloudWatchアラームの説明 : <alarmDescription>",
                    },
                  },
                ],
              },
              {
                header: "現在の状態",
                widgets: [
                  {
                    text_paragraph: {
                      text: "状態 : <stateValue>",
                    },
                  },
                  {
                    text_paragraph: {
                      text: "理由 : <stateReason>",
                    },
                  },
                  {
                    text_paragraph: {
                      text: "時刻 : <stateTimestamp>",
                    },
                  },
                ],
              },
              {
                header: "前回の状態",
                widgets: [
                  {
                    text_paragraph: {
                      text: "状態 : <previousStateValue>",
                    },
                  },
                  {
                    text_paragraph: {
                      text: "理由 : <previousStateReason>",
                    },
                  },
                  {
                    text_paragraph: {
                      text: "時刻 : <previousStateTimestamp>",
                    },
                  },
                ],
              },
            ],
          },
        },
      ],
    });
.
.
(中略)
.
.
    const cfnRule = rule.node.defaultChild as cdk.aws_events.CfnRule;
    cfnRule.addPropertyOverride("Targets", [
      {
        Arn: destination.apiDestinationArn,
        Id: "AwsNotificationTarget",
        RoleArn: role.roleArn,
        InputTransformer: {
          InputTemplate: inputTemplate,
          InputPathsMap: {
            account: "$.account",
            region: "$.region",
            alarmName: "$.detail.alarmName",
            alarmDescription: "$.detail.configuration.description",
            stateValue: "$.detail.state.value",
            stateReason: "$.detail.state.reason",
            stateTimestamp: "$.detail.state.timestamp",
            previousStateValue: "$.detail.previousState.value",
            previousStateReason: "$.detail.previousState.reason",
            previousStateTimestamp: "$.detail.previousState.timestamp",
          },
        },
      },
    ]);
メッセージの本文はカードインターフェイスを使って組み立てています。
カードインターフェイスのドキュメントは以下のとおりです。
それぞれのプロパティの説明について詳細に記載があるため、まずはこちらを確認するのが良いでしょう。
text_paragraphにはHTML形式で入力します。
ただし、EventBridge Input Transformerのプレースホルダーは<>とHTMLの各種タグと相性が悪いです。
回避策として以下記事で紹介しているとおり、ユニコードエスケープシーケンス形式でHTMLタグを表現します。
動作確認
動作確認をしてみましょう。
CloudWatchアラームはCreateTagが一回以上呼び出された時にアラートになるように組んでいます。
適当にEC2インスタンスタグを付与すると、以下のようにGoogle Chatに通知が来ました。

アラーム名やAWSアカウントID、リージョン、現在の状態、時刻と各種情報が整理されていて良い感じですね。
CloudWatchアラーム情報のリンクをクリックすると、AWSマネジメントコンソールで対象CloudWatchアラームを確認することができます。良い感じです。

対象のCloudWatchアラームはデータポイントがPUTされていない場合は閾値を超過していないと判定するようにしています。
その後、しばらくするとOKになったことを知らせる通知が来ました。

CloudWatchアラーム情報のリンクからCloudWatchアラームを確認すると、確かにOKになっていますね。

EventBridge API Destination便利
EventBridge API DestinationでGoogle ChatへCloudWatchアラームの通知をしてみました。
Lambda関数を用意する手間がかからない分、便利ですね。メッセージの整形で複雑なことをしないのであれば十分選択肢に入るのではないでしょうか。
今回は指定していませんが、レートリミットの設定も可能です。DLQと組み合わせて通知できなかったイベントがあれば後で流すこともしやすいのかなと思います。
この記事が誰かの助けになれば幸いです。
以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!











