はじめに
CloudWatchのアラーム一覧をなんとなく眺めていたら、同じEC2インスタンスに紐づく2つのアラームの表示が食い違っていることに気づきました。
-
myapp-prod-ec2-cpu-high→ OK -
myapp-prod-ec2-status-check-failed→ ALARM
インスタンスは以前から意図的に停止していたもので、動いていないのだから両方とも同じように「データが来ない」状態のはずです。なのに片方はOK、片方はALARMのまま。最初は「片方だけ本当に何か壊れているのでは」と身構えました。
先に結論:原因は、2つのアラームでTreatMissingData(欠落データの扱い)の設定が異なっていたことでした。
環境
- Amazon CloudWatch Alarms
- Amazon EC2(調査対象は停止中のインスタンス)
- AWS CLI
- リージョン: ap-northeast-1(東京)
症状
コンソールのアラーム一覧はこう表示されていました。
| 名前 | 状態 | 最終状態の更新 |
|---|---|---|
| myapp-prod-ec2-status-check-failed | ⚠️ アラーム状態 | 2026-08-28 10:50:11 (UTC) |
| myapp-prod-ec2-cpu-high | ✅ OK | 2026-05-29 08:35:16 (UTC) |
インスタンスの状態を確認すると、単にstoppedでした。
aws ec2 describe-instances \
--instance-ids i-xxxxxxxxxxxxxxxxx \
--query "Reservations[].Instances[].{State:State.Name,StateReason:StateTransitionReason}"
State: stopped
StateReason: User initiated (2026-08-28 10:34:54 GMT)
停止中なので、CPU使用率もステータスチェックのメトリクスも、どちらも新しいデータポイントは飛んできません。同じ「データが無い」状況のはずなのに、なぜ片方だけALARMなのか。
切り分け:何を比較したか
2つのアラームの詳細設定を、CLIで並べて見比べました。
aws cloudwatch describe-alarms \
--alarm-names "myapp-prod-ec2-cpu-high" "myapp-prod-ec2-status-check-failed" \
--query "MetricAlarms[].{Name:AlarmName,State:StateValue,MetricName:MetricName,TreatMissingData:TreatMissingData,Reason:StateReason}"
[
{
"Name": "myapp-prod-ec2-cpu-high",
"State": "OK",
"MetricName": "CPUUtilization",
"TreatMissingData": "notBreaching",
"Reason": "Threshold Crossed: 3 datapoints [...] were not greater than or equal to the threshold (80.0)."
},
{
"Name": "myapp-prod-ec2-status-check-failed",
"State": "ALARM",
"MetricName": "StatusCheckFailed",
"TreatMissingData": "breaching",
"Reason": "Threshold Crossed: no datapoints were received for 1 period and 1 missing datapoint was treated as [Breaching]."
}
]
TreatMissingDataの値が、片方はnotBreaching、もう片方はbreachingと、正反対になっていました。
原因:アラームごとの「欠落データの扱い」設定
CloudWatchアラームには、メトリクスのデータポイントが届かなかったときの扱いを決めるTreatMissingDataという設定があり、アラームごとに次の4種類から選べます(公式ドキュメント)。
| 設定 | 挙動 |
|---|---|
missing(既定) |
欠落データポイントは評価に使わない。評価期間内のデータが全て欠落するとINSUFFICIENT_DATAになる |
notBreaching |
欠落データポイントを「閾値を超えていないもの」として評価する |
breaching |
欠落データポイントを「閾値を超えたもの」として評価する |
ignore |
現在のアラーム状態をそのまま維持する(欠落する前がALARMならALARMのまま) |
今回のケースでは、
- CPU使用率アラームは
notBreachingだったため、欠落データが「閾値を超えていないもの」として扱われ、結果としてOKになっていた - ステータスチェックアラームは
breachingだったため、データが1周期分来なかった時点で「閾値を超えたもの」として評価され、ALARMに切り替わった
念のため、誰かが誤って停止させた操作ではないかもCloudTrailで確認しました。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ResourceName,AttributeValue=i-xxxxxxxxxxxxxxxxx \
--start-time 2026-08-28T10:00:00Z --end-time 2026-08-28T11:00:00Z \
--query "Events[].{Time:EventTime,Name:EventName,User:Username}"
Name: StopInstances
Time: 2026-08-28T19:34:55+09:00
User: (自分のIAMユーザー)
自分自身が意図的に停止した操作であることが裏付けられ、アラームの表示だけを見て焦る必要は無かったと分かりました。
解決:結論と実務での使い分け
今回は「壊れていた」わけではないので直す作業自体は不要でしたが、実務での使い分けとしては次のように整理できます。
- 停止中は「異常ではない」として扱いたい →
notBreaching - 停止前の状態(ALARMならALARM)をそのまま保持したい →
ignore(「欠落したら正常にする」設定ではない点に注意) - データが来ないこと自体を異常として検知したい →
breaching
なお、EC2を停止・終了・再起動・復旧させるアクション付きのアラームについては、データ欠落だけで誤って操作が実行されないようmissing(既定)のままにしておくことをAWSは推奨しています(Create alarms that stop, terminate, reboot, or recover an instance)。ここは「アラーム状態が変わること」と「その結果アクション(通知・自動操作)が実行されること」を分けて考えると理解しやすいです。
教訓
- CloudWatchアラームの状態(OK/ALARM)だけを見て「異常が起きた」と判断するのは早計で、
TreatMissingDataの設定次第で同じ状況でも表示が変わる - 気になるアラームがあったら、コンソールの状態表示だけでなく
describe-alarmsでTreatMissingDataとStateReasonをCLIで見比べると、原因の切り分けが早い - 操作主体を確認したい場合は、CloudTrailのイベントから
UsernameやuserIdentityを確認する(役割を引き受けた操作の場合、Usernameがセッション名になることもある)
おわりに
「同じインスタンスの2つのアラームなのに表示が食い違っている」と気づいた瞬間は、正直ちょっとヒヤッとしました😅 蓋を開けてみれば、アラームごとの設定の違いによる想定内の挙動だったのですが、CloudWatchアラームは「閾値」だけでなく「データが無いときどうするか」まで個別に設計できるんだな、と実感した一件でした。同じ表示の食い違いに遭遇した人の参考になれば嬉しいです🙌
参考
- AWS公式: Configuring how CloudWatch alarms treat missing data
https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/alarms-and-missing-data.html - AWS公式: describe-alarms — AWS CLI Command Reference
https://docs.aws.amazon.com/cli/latest/reference/cloudwatch/describe-alarms.html - AWS公式: lookup-events — AWS CLI Command Reference
https://docs.aws.amazon.com/cli/latest/reference/cloudtrail/lookup-events.html