マルチアカウント環境でCloudWatch Alarmを一元管理する構成を検証してみた

マルチアカウント環境でCloudWatch Alarmを一元管理する構成を検証してみた

Cross-Account ObservabilityとMulti Time Series Metrics Insightsクエリを活用して、複数アカウントのCloudWatch Alarmをモニタリングアカウントに集約し、アカウントごとに通知先を振り分ける構成を紹介します。
2026.03.03

こんにちは!クラウド事業本部の吉田です。

マルチアカウント環境でAWSを運用していると、監視設定が各アカウントに分散しがちです。アカウントごとにCloudWatch AlarmやSNSを作る形だと作成工数がかかってしまいます。また、リソース数やアカウント数が増えるほど管理が煩雑になり、監視漏れのリスクも高まります。

この課題の解決方法として、Cross-Account Observabilityによるメトリクスの集約があります。さらに、Multi Time Series Metrics Insightsクエリ(GROUP BYとORDER BYを使ったMetrics Insightsクエリ)でCloudWatch Alarmを設定すると、アラーム発火時にどのアカウントのどのリソースが原因かを特定できます。

今回は、これらを組み合わせてモニタリングアカウントでCloudWatch Alarmを一元管理し、件名と通知内容を整形した上でアカウントごとに通知先を振り分ける構成を検証しました。

Cross-Account Observabilityについて

Cross-Account Observabilityは、CloudWatch Observability Access Manager(OAM)を使って複数アカウントの監視に関するデータを集約する機能です。モニタリングアカウント側でSink(受け口)を作成し、ソースアカウント側からLink(接続)を作成することで、メトリクスやログ、X-Rayトレースを集約できます。

この機能を使うと、モニタリングアカウントのCloudWatchコンソールから、すべてのソースアカウントのメトリクスを一元的に参照できるようになります。

Cross-Account Observabilityは、以下の記事が参考になります。

Amazon CloudWatch クロスアカウントオブザーバビリティで監視環境を集約しよう | コラム | クラウドソリューション|サービス|法人のお客さま|NTT東日本

Multi Time Series Metrics Insightsクエリについて

Metrics Insightsは、SQLライクなクエリでCloudWatchメトリクスを分析できる機能です。2025年9月に追加された新機能により、GROUP BYとORDER BYを使ったMulti Time Series Metrics Insightsクエリでアラームを作成できるようになりました。※1

Multi Time Seriesアラームでは、GROUP BY句で指定したディメンションごとに個別の「Contributor(寄稿者)」として扱われ、それぞれ独立して評価されます。

アラームが発火すると、CloudWatch Alarm State Changeイベントに加えて、CloudWatch Alarm Contributor State Changeイベントが発生します。このイベントにはalarmContributor.attributesという属性があり、GROUP BY句で指定したディメンションの値が含まれています。GROUP BY AWS.AccountId, ${リソース識別子(例:InstanceId)}と指定すれば、メトリクスの発生元アカウントとリソース識別子(インスタンスIDなど)を取得できます。
これらの情報をアラームの整形や通知の振り分けに活用します。

監視構成の全体像

今回の検証では、2つの監視パターンを構築しました。

パターン 概要 適したケース
パターンA 全アカウントを1つのアラームで一括監視 全アカウント共通の閾値で問題ない場合
パターンB アカウントごとにアラームを作成 アカウントごとに異なる閾値が必要な場合

どちらのパターンも、アラーム発火から通知までの処理フローは共通です。

  1. CloudWatch Alarmがアラーム状態に遷移
  2. EventBridgeルール(デフォルトバス)がCloudWatch Alarm Contributor State Changeイベントを検知
  3. Step Functions(整形処理)がイベントから情報を抽出し、通知メッセージを生成する
  4. カスタムイベントバスに整形済みイベントを転送
  5. EventBridgeルール(カスタムバス)が整形済みイベントを検知
  6. Step Functions(振り分け処理)がアカウントIDに基づいて通知先を決定
  7. SNSがメール送信

ブログ用共通通知基盤.png

パターンA: 全アカウント一括監視

パターンAは、全ソースアカウントのリソースを1つのアラームで監視する構成です。
全アカウントで共通の閾値を適用できる場合に適しています。

Metrics InsightsクエリでGROUP BY AWS.AccountId, ${リソース識別子}を使うことで、アカウントとリソースの組み合わせごとにContributorが作成されます。アラーム発火時にはCloudWatch Alarm Contributor State Changeイベントが発生し、alarmContributor.attributesにアカウントIDが含まれるため、Step Functionsで通知先を振り分けられます。

この構成のメリットは、アラーム数が少なく管理しやすい点です。
新規アカウントを追加した場合も、アラームを新規作成する必要はありません。

パターンB: アカウント別監視

パターンBは、ソースアカウントごとにアラームを作成する構成です。

Metrics InsightsクエリでWHERE AWS.AccountId = 'xxx' GROUP BY ${リソース識別子}を使い、特定のアカウントのみを監視対象にします。アカウントごとにアラームを作成するため、閾値を個別に設定できます。

たとえば、本番アカウントはCPU使用率80%でアラート、開発アカウントは90%でアラートといった設定が可能です。

パターンBでは、CloudWatch Alarm Contributor State ChangeイベントのalarmContributor.attributesにアカウントIDが含まれません(GROUP BYにアカウントIDを指定していないため)。
代わりに、アラーム名の命名規則でアカウントIDを埋め込み、Step Functionsで抽出します。

パターンAの構築

ここからは各パターンの構築手順を記載します。
AWS CLIで作成する方法を紹介しています。設定内容のjsonとコマンドを各手順に記載する形としています。
CloudShell上で実行するのがおすすめです。

構築順序と検証方法

リソースは処理フローの逆順に作成します。ターゲットとなるリソースを先に用意しないと、ルールやステートマシンの設定時に参照できないためです。

本記事では、動作確認のため、テスト用のカスタムメトリクス(Test/EC2名前空間)を使います。実際のEC2インスタンスを用意しなくても、AWS CLIでメトリクスを発行してアラームの動作確認ができます。

動作確認の際は、下記のコマンドを実行します。

aws cloudwatch put-metric-data \
  --namespace "Test/EC2" \
  --metric-name "CPUUtilization" \
  --value 95 \
  --unit Percent \
  --dimensions InstanceId=i-test00001

Cross-Account Observabilityの設定

まず、メトリクス集約の基盤となるOAM Sink/Linkを設定します。詳細な設定手順は下記のブログを参考にしてください。

CloudWatch クロスアカウントオブザーバビリティ機能により AWS Organizations メンバーアカウントのメトリクス情報を集約する

OU単位でSinkポリシーを設定する場合は注意が必要です。aws:PrincipalOrgPaths条件で子OUを含めるには、StringLikeと末尾のワイルドカードが必要になります。StringEqualsで親OUのパスのみを指定した場合、子OUに属するアカウントからLink作成時にエラーが発生します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": ["oam:CreateLink", "oam:UpdateLink"],
            "Resource": "*",
            "Condition": {
                "ForAllValues:StringEquals": {
                    "oam:ResourceTypes": "AWS::CloudWatch::Metric"
                },
                "ForAnyValue:StringLike": {
                    "aws:PrincipalOrgPaths": "o-xxxxxxxxxx/r-xxxx/ou-xxxx-xxxxxxxx/*"
                }
            }
        }
    ]
}

SNSトピックの作成

振り分け先となるSNSトピックを作成します。検証ではデフォルト、本番アカウント用、開発アカウント用の3つを用意しました。

# トピックの作成
aws sns create-topic --name test-sns-default-alarm-notification
aws sns create-topic --name test-sns-production-alarm-notification
aws sns create-topic --name test-sns-development-alarm-notification

# サブスクリプションの追加(メールアドレスは適宜変更)
aws sns subscribe \
  --topic-arn "arn:aws:sns:ap-northeast-1:<モニタリングアカウントID>:test-sns-default-alarm-notification" \
  --protocol email \
  --notification-endpoint "your-email@example.com"

aws sns subscribe \
  --topic-arn "arn:aws:sns:ap-northeast-1:<モニタリングアカウントID>:test-sns-production-alarm-notification" \
  --protocol email \
  --notification-endpoint "your-email@example.com"

aws sns subscribe \
  --topic-arn "arn:aws:sns:ap-northeast-1:<モニタリングアカウントID>:test-sns-development-alarm-notification" \
  --protocol email \
  --notification-endpoint "your-email@example.com"

今回の検証では、開発アカウント用・本番アカウント用通知先としてエイリアスを利用する形としました。
本番環境はyour-email+production@example.com、開発環境はyour-email+development@example.comといった具合です。
サブスクリプションを追加すると確認メールが届くので、リンクをクリックして承認します。

Step Functions(振り分け処理)の作成

カスタムイベントバスから受け取った整形済みイベントを、アカウントIDに基づいてSNSトピックに振り分けるステートマシンを作成します。

入力イベントの$.detail.accountIdを確認し、Choiceステートで条件分岐してSNS Publishを実行します。

フロー図は、下記のようになります。

振り分けステートマシンのフロー図.png

IAMロールの作成(振り分け用Step Functions用)

Step Functions用IAMロール共通信頼ポリシー(sfn-trust-policy.json)

sfn-trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "states.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

IAMポリシー(sfn-dispatcher-permission-policy.json)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sns:Publish",
      "Resource": [
        "arn:aws:sns:ap-northeast-1:<モニタリングアカウントID>:test-sns-default-alarm-notification",
        "arn:aws:sns:ap-northeast-1:<モニタリングアカウントID>:test-sns-production-alarm-notification",
        "arn:aws:sns:ap-northeast-1:<モニタリングアカウントID>:test-sns-development-alarm-notification"
      ]
    }
  ]
}
# ロールの作成
aws iam create-role \
  --role-name test-role-sfn-alarm-dispatcher \
  --assume-role-policy-document file://sfn-trust-policy.json

# ポリシーのアタッチ
aws iam put-role-policy \
  --role-name test-role-sfn-alarm-dispatcher \
  --policy-name test-policy-sfn-alarm-dispatcher \
  --policy-document file://sfn-dispatcher-permission-policy.json

ステートマシンの作成

ステートマシン定義(dispatcher-state-machine.json)

dispatcher-state-machine.json
{
  "Comment": "整形済みアラームイベントをアカウントIDに基づいてSNSへ振り分ける",
  "StartAt": "DetermineNotificationTarget",
  "States": {
    "DetermineNotificationTarget": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.detail.accountId",
          "StringEquals": "<本番アカウントID>",
          "Next": "NotifyProductionAccount"
        },
        {
          "Variable": "$.detail.accountId",
          "StringEquals": "<開発アカウントID>",
          "Next": "NotifyDevelopmentAccount"
        }
      ],
      "Default": "NotifyDefault"
    },
    "NotifyProductionAccount": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:ap-northeast-1:<モニタリングアカウントID>:test-sns-production-alarm-notification",
        "Subject.$": "$.detail.subject",
        "Message.$": "$.detail.message"
      },
      "End": true
    },
    "NotifyDevelopmentAccount": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:ap-northeast-1:<モニタリングアカウントID>:test-sns-development-alarm-notification",
        "Subject.$": "$.detail.subject",
        "Message.$": "$.detail.message"
      },
      "End": true
    },
    "NotifyDefault": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:ap-northeast-1:<モニタリングアカウントID>:test-sns-default-alarm-notification",
        "Subject.$": "$.detail.subject",
        "Message.$": "$.detail.message"
      },
      "End": true
    }
  }
}
aws stepfunctions create-state-machine \
  --name "test-sfn-alarm-dispatcher" \
  --definition file://dispatcher-state-machine.json \
  --role-arn "arn:aws:iam::<モニタリングアカウントID>:role/test-role-sfn-alarm-dispatcher"

カスタムイベントバスの作成

整形済みイベントを受け取るためのカスタムイベントバスを作成します。デフォルトバスと分けることで、生イベントと整形済みイベントを明確に区別できます。

aws events create-event-bus --name test-custom-event-bus

EventBridgeルール(カスタムバス)の作成

カスタムイベントバスに届いた整形済みイベントを検知し、振り分け用Step Functionsに転送するルールを作成します。

IAMロールの作成

EventBridge用IAMロール共通信頼ポリシー(eventbridge-trust-policy.json)

eventbridge-trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

IAMポリシー(eventbridge-to-sfn-dispatcher-permission-policy.json)

eventbridge-to-sfn-dispatcher-permission-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "states:StartExecution",
      "Resource": "arn:aws:states:ap-northeast-1:<モニタリングアカウントID>:stateMachine:test-sfn-alarm-dispatcher"
    }
  ]
}
# ロールの作成
aws iam create-role \
  --role-name test-role-eventbridge-to-sfn-dispatcher \
  --assume-role-policy-document file://eventbridge-trust-policy.json

# ポリシーのアタッチ
aws iam put-role-policy \
  --role-name test-role-eventbridge-to-sfn-dispatcher \
  --policy-name test-policy-eventbridge-to-sfn-dispatcher \
  --policy-document file://eventbridge-to-sfn-dispatcher-permission-policy.json

ルールの作成

イベントパターンは、整形処理で設定したsourcedetail-typeに一致させます。

イベントパターン(custom-event-pattern.json)

custom-event-pattern.json
{
  "source": ["custom.alarm.formatter"],
  "detail-type": ["Formatted Alarm Notification"]
}
aws events put-rule \
  --name "test-ebrule-custom-to-sfn" \
  --event-bus-name "test-custom-event-bus" \
  --event-pattern file://custom-event-pattern.json \
  --state ENABLED

ターゲットの設定

aws events put-targets \
  --rule "test-ebrule-custom-to-sfn" \
  --event-bus-name "test-custom-event-bus" \
  --targets '[{
    "Id": "StepFunctionsTarget",
    "Arn": "arn:aws:states:ap-northeast-1:<モニタリングアカウントID>:stateMachine:test-sfn-alarm-dispatcher",
    "RoleArn": "arn:aws:iam::<モニタリングアカウントID>:role/test-role-eventbridge-to-sfn-dispatcher"
  }]'

Step Functions(整形処理)の作成

CloudWatch Alarm Contributor State Changeイベントを受け取り、通知用の形式に整形してカスタムイベントバスへ転送するステートマシンを作成します。

IAMロールの作成(整形用Step Functions用)

IAMポリシー(sfn-formatter-permission-policy.json)

sfn-formatter-permission-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "events:PutEvents",
      "Resource": "arn:aws:events:ap-northeast-1:123456789012:event-bus/test-custom-event-bus"
    }
  ]
}
# ロールの作成
aws iam create-role \
  --role-name test-role-sfn-alarm-formatter \
  --assume-role-policy-document file://sfn-trust-policy.json

# ポリシーのアタッチ
aws iam put-role-policy \
  --role-name test-role-sfn-alarm-formatter \
  --policy-name test-policy-sfn-alarm-formatter \
  --policy-document file://sfn-formatter-permission-policy.json

ステートマシンの作成

ステートマシン定義(sfn-formatter-permission-policy.json)

sfn-formatter-permission-policy.json
{
  "Comment": "CloudWatch Alarm イベントを整形してカスタムイベントバスへ転送する",
  "StartAt": "FormatMessage",
  "States": {
    "FormatMessage": {
      "Type": "Pass",
      "Parameters": {
        "accountId.$": "$.detail.alarmContributor.attributes['AWS.\"AccountId\"']",
        "alarmName.$": "$.detail.alarmName",
        "resourceAttributes.$": "$.detail.alarmContributor.attributes",
        "state.$": "$.detail.state.value",
        "reason.$": "$.detail.state.reason",
        "timestamp.$": "$.time"
      },
      "Next": "BuildNotification"
    },
    "BuildNotification": {
      "Type": "Pass",
      "Parameters": {
        "accountId.$": "$.accountId",
        "alarmName.$": "$.alarmName",
        "resourceAttributes.$": "$.resourceAttributes",
        "subject.$": "States.Format('CloudWatch Alarm: {} - {}', $.alarmName, $.state)",
        "message.$": "States.Format('アカウントID: {}\nアラーム名: {}\nリソース情報: {}\n状態: {}\n理由: {}\n発生時刻: {}', $.accountId, $.alarmName, States.JsonToString($.resourceAttributes), $.state, $.reason, $.timestamp)"
      },
      "Next": "PutToCustomBus"
    },
    "PutToCustomBus": {
      "Type": "Task",
      "Resource": "arn:aws:states:::events:putEvents",
      "Parameters": {
        "Entries": [
          {
            "Source": "custom.alarm.formatter",
            "DetailType": "Formatted Alarm Notification",
            "Detail": {
              "accountId.$": "$.accountId",
              "alarmName.$": "$.alarmName",
              "resourceAttributes.$": "$.resourceAttributes",
              "subject.$": "$.subject",
              "message.$": "$.message"
            },
            "EventBusName": "test-custom-event-bus"
          }
        ]
      },
      "End": true
    }
  }
}

alarmContributor.attributes全体をresourceAttributesとして保持することで、EC2以外のリソース(DynamoDB、RDS、Lambdaなど)にも対応できます。

aws stepfunctions create-state-machine \
  --name "test-sfn-alarm-formatter" \
  --definition file://formatter-state-machine.json \
  --role-arn "arn:aws:iam::<モニタリングアカウントID>:role/test-role-sfn-alarm-formatter"

EventBridgeルール(デフォルトバス)の作成

デフォルトイベントバスでCloudWatch Alarm Contributor State Changeイベントを検知し、整形用Step Functionsに転送するルールを作成します。

IAMロールの作成

IAMポリシー(eventbridge-to-sfn-formatter-permission-policy.json)

eventbridge-to-sfn-formatter-permission-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "states:StartExecution",
      "Resource": "arn:aws:states:ap-northeast-1:<モニタリングアカウントID>:stateMachine:test-sfn-alarm-formatter"
    }
  ]
}
# ロールの作成
aws iam create-role \
  --role-name test-role-eventbridge-to-sfn \
  --assume-role-policy-document file://eventbridge-trust-policy.json

# ポリシーのアタッチ
aws iam put-role-policy \
  --role-name test-role-eventbridge-to-sfn \
  --policy-name test-policy-eventbridge-to-sfn \
  --policy-document file://eventbridge-to-sfn-formatter-permission-policy.json

ルールの作成

イベントパターン(default-event-pattern.json)

default-event-pattern.json
{
  "source": ["aws.cloudwatch"],
  "detail-type": ["CloudWatch Alarm Contributor State Change"],
  "resources": [{
    "wildcard": "arn:aws:cloudwatch:ap-northeast-1:<モニタリングアカウントID>:alarm:test-*"
  }]
}

プレフィックスを指定する形にしています。
全てのアラームをこの通知基盤で通知する場合は、アラーム名のところ全部ワイルドカードで指定する形でも問題ないと思います。
適宜修正してください。

aws events put-rule \
  --name "test-ebrule-alarm-to-sfn" \
  --event-pattern file://default-event-pattern.json \
  --state ENABLED

ターゲット設定

aws events put-targets \
  --rule "test-ebrule-alarm-to-sfn" \
  --targets '[{
    "Id": "StepFunctionsTarget",
    "Arn": "arn:aws:states:ap-northeast-1:<モニタリングアカウントID>:stateMachine:test-sfn-alarm-formatter",
    "RoleArn": "arn:aws:iam::<モニタリングアカウントID>:role/test-role-eventbridge-to-sfn"
  }]'

CloudWatch Alarmの作成

最後に、Metrics Insightsクエリを使ったアラームを作成します。パターンAではGROUP BY AWS.AccountId, InstanceIdにより、全アカウントのEC2インスタンスを1つのアラームで監視します。
手順解説の冒頭で触れた通り、今回はテスト用のカスタムメトリクス(Test/EC2名前空間)を使います。
CPUUtilizationが80%超えたらアラームが発生する内容となっております。

アラーム作成

アラーム定義(alarm.json)

{
  "AlarmName": "test-ec2-cpu-high",
  "Metrics": [
    {
      "Id": "q1",
      "Expression": "SELECT MAX(CPUUtilization) FROM \"Test/EC2\" GROUP BY AWS.AccountId, InstanceId ORDER BY MAX() DESC",
      "Period": 60,
      "Label": "CPU Utilization"
    }
  ],
  "Threshold": 80,
  "ComparisonOperator": "GreaterThanThreshold",
  "EvaluationPeriods": 1,
  "DatapointsToAlarm": 1,
  "TreatMissingData": "notBreaching"
}
aws cloudwatch put-metric-alarm --cli-input-json file://alarm.json

EventBridge経由でStep Functionsをトリガーするため、アラームアクションを設定する必要はありません。

パターンAの動作確認

構築が完了したら、擬似本番アカウント・擬似開発アカウントでカスタムメトリクスを発行してE2Eテストを行います。
どちらの環境の結果なのかわかりやすくするため、スクショでアカウントIDの冒頭3桁をお見せしております。

  • 擬似本番アカウント:970XXXXXXXXX
  • 擬似開発アカウント:977XXXXXXXXX

ではまず擬似本番アカウントで、閾値(80%)を超えるメトリクスデータを作成します。

aws cloudwatch put-metric-data \
  --namespace "Test/EC2" \
  --metric-name "CPUUtilization" \
  --value 95 \
  --unit Percent \
  --dimensions InstanceId=i-test00001

しばらく待つと、モニタリングアカウント側のアラームがアラーム状態になりました!

パターンAのアラート画面.png

さらに待つと本番アカウント用のメールアドレス(エイリアスがproduction)にメールが届きました。

パターンAの本番環境通知メール.png

次に擬似開発環境上で、閾値(80%)を超えるメトリクスデータを作成しました。
こちらは開発アカウント用のメールアドレス(エイリアスがdevelopment)にメールが届きました。

パターンAの開発環境通知メール.png

ちゃんとアカウントによって通知先が振り分けられているのがわかりました。

パターンBの構築

パターンBはパターンAとの差分のみ説明します。
SNS、振り分け処理用ステートマシン、カスタムイベントバス、EventBridgeルール(デフォルトバス&カスタムバス)は、パターンAと共通です。

アラームの設定

パターンBでは、ソースアカウントごとにアラームを作成します。

項目 パターンA パターンB
クエリ GROUP BY AWS.AccountId, InstanceId WHERE AWS.AccountId = 'xxx' GROUP BY InstanceId
アラーム数 1つ(全アカウント共通) ソースアカウントごとに作成
閾値 全アカウント共通 ソースアカウントごとに設定可能
アラーム名 test-ec2-cpu-high test-account-{アカウントID}-ec2-cpu-high

本番アカウント用(閾値80%)と開発アカウント用(閾値90%)のアラーム例を示します。

パターンBのアラーム例(本番アカウント用)

alarm-pattern-b-production.json
{
  "AlarmName": "test-account-111122223333-ec2-cpu-high",
  "Metrics": [
    {
      "Id": "q1",
      "Expression": "SELECT MAX(CPUUtilization) FROM \"Test/EC2\" WHERE AWS.AccountId = '111122223333' GROUP BY InstanceId ORDER BY MAX() DESC",
      "Period": 60,
      "Label": "CPU Utilization"
    }
  ],
  "Threshold": 80,
  "ComparisonOperator": "GreaterThanThreshold",
  "EvaluationPeriods": 1,
  "DatapointsToAlarm": 1,
  "TreatMissingData": "notBreaching"
}

パターンBのアラーム例(開発アカウント用)

alarm-pattern-b-development.json
{
  "AlarmName": "test-account-444455556666-ec2-cpu-high",
  "Metrics": [
    {
      "Id": "q1",
      "Expression": "SELECT MAX(CPUUtilization) FROM \"Test/EC2\" WHERE AWS.AccountId = '444455556666' GROUP BY InstanceId ORDER BY MAX() DESC",
      "Period": 60,
      "Label": "CPU Utilization"
    }
  ],
  "Threshold": 90,
  "ComparisonOperator": "GreaterThanThreshold",
  "EvaluationPeriods": 1,
  "DatapointsToAlarm": 1,
  "TreatMissingData": "notBreaching"
}

アラーム名にはtest-account-{アカウントID}-ec2-cpu-highという命名規則を使います。
後述するStep FunctionsでアカウントIDを抽出するためです。

# 本番アカウント用
aws cloudwatch put-metric-alarm --cli-input-json file://alarm-pattern-b-production.json

# 開発アカウント用
aws cloudwatch put-metric-alarm --cli-input-json file://alarm-pattern-b-development.json

Step Functions(整形処理)の変更

パターンBでは、Contributor State ChangeイベントのalarmContributor.attributesにアカウントIDが含まれません。GROUP BYにアカウントIDを指定していないためです。

代わりに、アラーム名からアカウントIDを抽出します。States.StringSplitでアラーム名をハイフンで分割し、States.ArrayGetItemで3番目の要素(インデックス2)を取得します。

formatter-state-machine-pattern-b.json
{
  "Comment": "CloudWatch Alarm イベントを整形してカスタムイベントバスへ転送する(パターンB)",
  "StartAt": "ExtractInfo",
  "States": {
    "ExtractInfo": {
      "Type": "Pass",
      "Parameters": {
        "alarmName.$": "$.detail.alarmName",
        "resourceAttributes.$": "$.detail.alarmContributor.attributes",
        "state.$": "$.detail.state.value",
        "reason.$": "$.detail.state.reason",
        "timestamp.$": "$.time"
      },
      "Next": "ParseAccountId"
    },
    "ParseAccountId": {
      "Type": "Pass",
      "Parameters": {
        "alarmName.$": "$.alarmName",
        "accountId.$": "States.ArrayGetItem(States.StringSplit($.alarmName, '-'), 2)",
        "resourceAttributes.$": "$.resourceAttributes",
        "state.$": "$.state",
        "reason.$": "$.reason",
        "timestamp.$": "$.timestamp"
      },
      "Next": "BuildNotification"
    },
    "BuildNotification": {
      "Type": "Pass",
      "Parameters": {
        "accountId.$": "$.accountId",
        "alarmName.$": "$.alarmName",
        "resourceAttributes.$": "$.resourceAttributes",
        "subject.$": "States.Format('CloudWatch Alarm: {} - {}', $.alarmName, $.state)",
        "message.$": "States.Format('アカウントID: {}\nアラーム名: {}\nリソース情報: {}\n状態: {}\n理由: {}\n発生時刻: {}', $.accountId, $.alarmName, States.JsonToString($.resourceAttributes), $.state, $.reason, $.timestamp)"
      },
      "Next": "PutToCustomBus"
    },
    "PutToCustomBus": {
      "Type": "Task",
      "Resource": "arn:aws:states:::events:putEvents",
      "Parameters": {
        "Entries": [
          {
            "Source": "custom.alarm.formatter",
            "DetailType": "Formatted Alarm Notification",
            "Detail": {
              "accountId.$": "$.accountId",
              "alarmName.$": "$.alarmName",
              "resourceAttributes.$": "$.resourceAttributes",
              "subject.$": "$.subject",
              "message.$": "$.message"
            },
            "EventBusName": "test-custom-event-bus"
          }
        ]
      },
      "End": true
    }
  }
}

パターンBの動作確認

パターンBでは、アカウントごとに異なる閾値を設定できることを確認します。

CPU使用率85%のメトリクスデータを擬似本番・開発アカウントともに作成します。

# 本番アカウントのみアラーム発火する想定(本番:85% > 80%、開発:85% < 90%)
aws cloudwatch put-metric-data \
  --namespace "Test/EC2" \
  --metric-name "CPUUtilization" \
  --value 85 \
  --unit Percent \
  --dimensions InstanceId=i-test00001

しばらく待つと、本番アカウント用アラームだけアラーム状態になることを確認できました!

パターンBのアラート画面.png

メールも、本番アカウント用のメールアドレスだけに届いています。

パターンBの本番環境通知メール.png

では次に、CPU使用率95%のメトリクスデータを擬似本番・開発アカウントともに作成します。

# 開発アカウントのアラームも発火する想定(本番:95% > 80%、開発:95% > 90%)
aws cloudwatch put-metric-data \
  --namespace "Test/EC2" \
  --metric-name "CPUUtilization" \
  --value 95 \
  --unit Percent \
  --dimensions InstanceId=i-test00001

開発環境のメールアドレスにもアラートメールが届きました!

パターンBの開発環境通知メール.png

さいごに

今回は、Cross-Account ObservabilityとCloudWatch Alarm Contributor State Changeイベントを組み合わせて、マルチアカウント環境の監視を一元化する構成を検証しました。

ただ検証した後に気づいたのですが、CloudWatchはリソースタグによるメトリクスモニタリングもサポートしております。

[アップデート] Amazon CloudWatchがリソースのタグを用いてメトリクスモニタリングができるようになり、リソースの追加や変更に自動的に適応する動的なCloudWatchアラームを設定可能になりました | DevelopersIO

今回は、アラーム発火条件や通知の振り分けとしてアカウントIDを利用する形にしましたが、リソースタグを利用する方がスマートかもしれません。

ただ、マルチアカウントのCloudWatch Alarm一元管理方法としては、今回の検証の構成は参考になるかと考えております。

この記事がどなたかのお役に立てれば嬉しいです。
以上、クラウド事業本部の吉田でした!

参考資料

この記事をシェアする

FacebookHatena blogX

関連記事