はじめに
Webアプリケーションを本番環境で運用する際、監視は不可欠です。しかし、いざ監視を設定しようとすると、こんな悩みに直面することがあります:
- 「CloudWatchのメトリクスが多すぎて、何を監視すればいいか分からない」
- 「すべて監視するとコストが心配」
- 「重要なメトリクスを見逃して、障害に気づけなかったら...」
本記事では、シンプルな3層構成(ALB + EC2 + RDS)のWebアプリケーションを例に、「何を監視すべきか」の判断基準と、AWS公式の推奨アラームを活用した実践的な監視設計を紹介します。
まずは結論
-
すべてを監視する必要はない
- Four Golden Signals(Latency、Traffic、Errors、Saturation)を基準に
- ビジネスへの影響度で優先順位をつける
-
AWS公式の推奨アラームを活用
- CloudWatchが提供する推奨アラームは、AWSの運用ノウハウの結晶
- しきい値も事前設定されており、すぐに使える
-
各レイヤーで監視すべきメトリクス
- ALB: TargetResponseTime、HTTPCode_Target_5XX_Count、HealthyHostCount
- EC2: CPUUtilization、StatusCheckFailed、メモリ・ディスク使用率
- RDS: DatabaseConnections、CPUUtilization、FreeableMemory、レイテンシ
直面した課題
構成:シンプルな3層アーキテクチャ
私たちが運用していたWebアプリケーションは、以下の構成でした:
Internet
↓
[ALB] ← ロードバランサー層
↓
[EC2] ← アプリケーション層
↓
[RDS] ← データベース層
- ALB(Application Load Balancer): HTTPSリクエストを受け付け、EC2インスタンスに振り分け
- EC2: Webアプリケーション(Node.js/Python/Java等)が稼働
- RDS(PostgreSQL/MySQL): アプリケーションデータを永続化
この構成は多くのWebアプリケーションで採用されており、シンプルで理解しやすいアーキテクチャです。
監視を始めようとしたときの悩み
本番リリースを控え、監視を設定しようとしたときに直面した問題:
-
メトリクスが多すぎる
- ALBだけで20種類以上のメトリクス
- EC2、RDSも同様に多数のメトリクスが存在
- 「どれが重要なのか」の判断基準がない
-
コストへの懸念
- CloudWatchアラームは1つあたり月額約$0.10
- カスタムメトリクスはさらに高額
- すべてを監視すると月数十ドル以上のコスト増
-
見逃しへの不安
- 重要なメトリクスを監視し忘れたら?
- 障害が発生してから気づくのは遅すぎる
この状況で、「何を監視すべきか」の明確な基準が必要でした。
監視設計の2つのアプローチ
「何を監視すべきか」を判断するため、2つのアプローチを組み合わせます。
3.1 理論的フレームワーク:Four Golden Signals
Google SREが提唱するFour Golden Signalsは、監視設計の業界標準フレームワークです。
1. Latency(レイテンシ)
定義: リクエストの応答時間
重要性: ユーザー体験に直結。遅いレスポンスはユーザー離脱につながる
例: APIの応答時間、データベースクエリ時間
2. Traffic(トラフィック)
定義: システムへの需要(リクエスト数、スループット)
重要性: 負荷の増減を把握し、スケーリングの判断材料にする
例: 秒間リクエスト数、データベース接続数
3. Errors(エラー)
定義: 失敗したリクエストの割合
重要性: サービスの可用性を示す最も重要な指標
例: HTTP 5xxエラー率、データベース接続エラー
4. Saturation(飽和度)
定義: リソースの使用率(キャパシティの限界に近いか)
重要性: リソース枯渇を事前に検知し、スケールアウトやチューニングの機会を得る
例: CPU使用率、メモリ使用率、ディスク容量
このフレームワークにより、「なぜこのメトリクスを監視するのか」が明確になります。
3.2 AWS公式の実践ガイド:CloudWatch推奨アラーム
AWSは、各サービスごとに推奨アラームを提供しています。これは、AWSが長年の運用経験から「このサービスではこのメトリクスを監視すべき」と推奨するものです。
CloudWatch推奨アラームの特徴:
- ✅ AWS公式のベストプラクティスに基づく
- ✅ しきい値が事前設定されている(調整も可能)
- ✅ CloudFormation/CDK形式でエクスポート可能
- ✅ 追加料金なし(アラーム作成後は通常料金)
活用方法:
- CloudWatchコンソール → Metrics → All metrics
- 「アラームの推奨事項」フィルターを選択
- サービス名前空間(ALB、EC2、RDSなど)を選択
- 推奨アラームを確認
本記事では、この推奨アラームをベースに、Four Golden Signalsの観点から各メトリクスを解説します。
各レイヤーの監視設計
4.1 ALB(ロードバランサー層)
ALBは、インターネットからのリクエストを最初に受け付けるレイヤーです。ここでの異常は、即座にユーザー体験に影響します。
必須メトリクス
1. TargetResponseTime(ターゲット応答時間)
何を測定するか:
ALBからバックエンド(EC2)へリクエストを送信してから、レスポンスを受け取るまでの時間。
なぜ重要か:
- ユーザー体験に直結する指標(Latency)
- アプリケーションのパフォーマンス劣化を検知
- データベーススロークエリやアプリケーションバグの兆候
しきい値の考え方:
- 目標レスポンス時間: 通常200ms以下が理想
- 警告: 500ms以上(ユーザーが遅いと感じ始める)
- 緊急: 1000ms以上(ユーザー離脱のリスク)
CloudWatch推奨値: 通常500ms〜1000msが推奨される
// CDKでの実装例
new cloudwatch.Alarm(this, 'ALBHighLatency', {
metric: targetGroup.metricTargetResponseTime(),
threshold: 0.5, // 500ms
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
2. HTTPCode_Target_5XX_Count(5xxエラー数)
何を測定するか:
バックエンド(EC2)から返された5xxエラー(サーバーエラー)の数。
なぜ重要か:
- サービスの可用性を示す(Errors)
- アプリケーションのバグやインフラ障害を検知
- ユーザーにエラー画面が表示されている状態
しきい値の考え方:
- 理想: 0件(5xxエラーは本来発生すべきでない)
- 警告: 1分間に5件以上
- 緊急: 1分間に20件以上または全体の1%以上
CloudWatch推奨値: 通常5〜10件/分が推奨される
new cloudwatch.Alarm(this, 'ALBServerErrors', {
metric: targetGroup.metricHttpCodeTarget(
elbv2.HttpCodeTarget.TARGET_5XX_COUNT
),
threshold: 5,
evaluationPeriods: 1,
datapointsToAlarm: 1,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
statistic: 'Sum',
period: cdk.Duration.minutes(1),
});
3. HealthyHostCount(正常なホスト数)
何を測定するか:
ALBのヘルスチェックに成功しているEC2インスタンスの数。
なぜ重要か:
- システムの可用性を示す(Saturation + Errors)
- すべてのインスタンスがダウンすると、サービス全停止
- スケーリング不足の検知
しきい値の考え方:
- 最小: 2台以上(冗長性の確保)
- 警告: 想定台数の50%以下
- 緊急: 1台以下(単一障害点)
CloudWatch推奨値: 通常1台以下でアラート
new cloudwatch.Alarm(this, 'ALBUnhealthyHosts', {
metric: targetGroup.metricHealthyHostCount(),
threshold: 1,
evaluationPeriods: 1,
datapointsToAlarm: 1,
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
});
4. RequestCount(リクエスト数)
何を測定するか:
ALBが処理したHTTPリクエストの総数。
なぜ重要か:
- トラフィックの傾向を把握(Traffic)
- 急激な増減は異常の兆候
- 急増: DDoS攻撃、バイラル拡散
- 急減: サービス障害、DNS問題
しきい値の考え方:
- 通常トラフィックのベースラインを把握
- 平常時の2倍以上: 注意
- 平常時の5倍以上: 緊急(スケーリング必要)
CloudWatch推奨値: ベースラインの150%〜200%
// トラフィック監視はダッシュボードで傾向を見るのが一般的
// 異常検知アラームとしてはAnomalyDetectorを使用
new cloudwatch.Alarm(this, 'ALBTrafficAnomaly', {
metric: targetGroup.metricRequestCount(),
threshold: 1000, // 自環境に合わせて調整
evaluationPeriods: 3,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
推奨メトリクス(オプション)
HTTPCode_Target_4XX_Count(4xxエラー数)
何を測定するか:
バックエンドから返された4xxエラー(クライアントエラー)の数。
なぜ重要か:
- クライアント側の問題を検知(Errors)
- 404エラー急増: リンク切れ、URL変更
- 401/403エラー急増: 認証問題
監視すべきか:
- 必須ではない(クライアント側の問題のため)
- ただし、急増は異常の兆候なので傾向監視は推奨
4.2 EC2(アプリケーション層)
EC2は、ビジネスロジックが実行されるレイヤーです。リソース枯渇やアプリケーションクラッシュを早期に検知する必要があります。
必須メトリクス
1. CPUUtilization(CPU使用率)
何を測定するか:
EC2インスタンスのCPU使用率(0〜100%)。
なぜ重要か:
- リソースの飽和度を示す(Saturation)
- 高CPU使用率が継続すると、レスポンス遅延やタイムアウト
- スケールアウトの判断材料
しきい値の考え方:
- 理想: 50%以下(余裕がある状態)
- 警告: 70%以上が5分以上継続
- 緊急: 90%以上が継続(即座にスケールアウト必要)
CloudWatch推奨値: 通常80%以上
new cloudwatch.Alarm(this, 'EC2HighCPU', {
metric: new cloudwatch.Metric({
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
dimensionsMap: {
InstanceId: instance.instanceId,
},
statistic: 'Average',
}),
threshold: 80,
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
2. StatusCheckFailed(ステータスチェック失敗)
何を測定するか:
EC2インスタンスのシステムステータスチェックとインスタンスステータスチェックの失敗。
なぜ重要か:
- インスタンスの可用性を示す(Errors)
- ハードウェア障害、ネットワーク障害、OS障害を検知
- 失敗時は自動的にインスタンス再起動などのリカバリーが必要
しきい値の考え方:
- 失敗 = 1(0 = 正常、1 = 失敗)
- 1度でも失敗したら即座にアラート
CloudWatch推奨値: 1(失敗)
new cloudwatch.Alarm(this, 'EC2StatusCheckFailed', {
metric: new cloudwatch.Metric({
namespace: 'AWS/EC2',
metricName: 'StatusCheckFailed',
dimensionsMap: {
InstanceId: instance.instanceId,
},
statistic: 'Maximum',
}),
threshold: 1,
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
});
推奨メトリクス(CloudWatch Agent必要)
EC2のデフォルトメトリクスには、メモリとディスクの情報が含まれません。これらを監視するには、CloudWatch Agentのインストールが必要です。
3. メモリ使用率(mem_used_percent)
何を測定するか:
EC2インスタンスのメモリ(RAM)使用率。
なぜ重要か:
- メモリリークやメモリ不足を検知(Saturation)
- メモリ枯渇時、OOM KillerがプロセスをKillする
- スワップが発生するとパフォーマンスが激減
しきい値の考え方:
- 警告: 80%以上
- 緊急: 95%以上(OOMリスク)
new cloudwatch.Alarm(this, 'EC2HighMemory', {
metric: new cloudwatch.Metric({
namespace: 'CWAgent',
metricName: 'mem_used_percent',
dimensionsMap: {
InstanceId: instance.instanceId,
},
statistic: 'Average',
}),
threshold: 80,
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
4. ディスク使用率(disk_used_percent)
何を測定するか:
EC2インスタンスのディスク使用率。
なぜ重要か:
- ディスク容量不足を検知(Saturation)
- ログファイルの肥大化、一時ファイルの蓄積
- ディスク満杯時、アプリケーションが書き込めずにクラッシュ
しきい値の考え方:
- 警告: 80%以上(クリーンアップ推奨)
- 緊急: 90%以上(即座に対応必要)
new cloudwatch.Alarm(this, 'EC2HighDisk', {
metric: new cloudwatch.Metric({
namespace: 'CWAgent',
metricName: 'disk_used_percent',
dimensionsMap: {
InstanceId: instance.instanceId,
path: '/',
fstype: 'ext4',
},
statistic: 'Average',
}),
threshold: 80,
evaluationPeriods: 1,
datapointsToAlarm: 1,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
4.3 RDS(データベース層)
RDSは、アプリケーションデータを保持する最も重要なレイヤーです。データベースの障害は、サービス全体の停止につながります。
必須メトリクス
1. DatabaseConnections(データベース接続数)
何を測定するか:
RDSインスタンスへのアクティブな接続数。
なぜ重要か:
- データベースの飽和度を示す(Saturation)
- 接続数上限に達すると、新しい接続が拒否される
- アプリケーションのコネクションプールのリーク検知
しきい値の考え方:
- RDSインスタンスの最大接続数を確認(例: db.t3.medium = 約400接続)
- 警告: 最大接続数の80%
- 緊急: 最大接続数の95%
CloudWatch推奨値: 最大接続数の80%
new cloudwatch.Alarm(this, 'RDSHighConnections', {
metric: database.metricDatabaseConnections(),
threshold: 320, // db.t3.mediumの場合(400の80%)
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
2. CPUUtilization(CPU使用率)
何を測定するか:
RDSインスタンスのCPU使用率。
なぜ重要か:
- データベースの飽和度を示す(Saturation)
- 高CPU使用率は、スロークエリやインデックス不足の兆候
- 継続すると、クエリタイムアウトやレスポンス遅延
しきい値の考え方:
- 警告: 70%以上が5分以上継続
- 緊急: 90%以上(クエリ最適化またはスケールアップ必要)
CloudWatch推奨値: 80%以上
new cloudwatch.Alarm(this, 'RDSHighCPU', {
metric: database.metricCPUUtilization(),
threshold: 80,
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
3. FreeableMemory(空きメモリ)
何を測定するか:
RDSインスタンスの空きメモリ容量(バイト単位)。
なぜ重要か:
- メモリ不足を検知(Saturation)
- データベースはメモリをキャッシュに使用
- メモリ不足時、ディスクI/Oが増加してパフォーマンス劣化
しきい値の考え方:
- 警告: 空きメモリが総メモリの10%以下
- 緊急: 空きメモリが総メモリの5%以下
CloudWatch推奨値: 100MB以下(インスタンスサイズによる)
new cloudwatch.Alarm(this, 'RDSLowMemory', {
metric: database.metricFreeableMemory(),
threshold: 100 * 1024 * 1024, // 100MB(バイト単位)
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
});
4. ReadLatency / WriteLatency(読み取り/書き込みレイテンシ)
何を測定するか:
ディスクI/O操作の平均レイテンシ(ミリ秒単位)。
なぜ重要か:
- データベースのパフォーマンスを示す(Latency)
- 高レイテンシは、スロークエリやディスクI/O負荷の兆候
- アプリケーションのレスポンス遅延に直結
しきい値の考え方:
- 理想: 5ms以下
- 警告: 10ms以上
- 緊急: 20ms以上(IOPS不足、スケールアップ検討)
CloudWatch推奨値: 10ms以上
new cloudwatch.Alarm(this, 'RDSHighReadLatency', {
metric: database.metricReadLatency(),
threshold: 0.01, // 10ms(秒単位)
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
new cloudwatch.Alarm(this, 'RDSHighWriteLatency', {
metric: database.metricWriteLatency(),
threshold: 0.01, // 10ms(秒単位)
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
推奨メトリクス(オプション)
5. FreeStorageSpace(空きストレージ容量)
何を測定するか:
RDSインスタンスの空きディスク容量。
なぜ重要か:
- ディスク容量不足を検知(Saturation)
- ディスク満杯時、データベースが書き込みできなくなる
- ログファイルやバックアップの蓄積
しきい値の考え方:
- 警告: 空き容量が総容量の10%以下
- 緊急: 空き容量が5GB以下
new cloudwatch.Alarm(this, 'RDSLowStorage', {
metric: database.metricFreeStorageSpace(),
threshold: 5 * 1024 * 1024 * 1024, // 5GB(バイト単位)
evaluationPeriods: 1,
datapointsToAlarm: 1,
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
});
CDKによる統合実装
ここまでの監視設計をTypeScript CDKで実装します。
5.1 プロジェクト構造
monitoring-stack/
├── bin/
│ └── app.ts
├── lib/
│ ├── monitoring-stack.ts # メインスタック
│ ├── alarms/
│ │ ├── alb-alarms.ts # ALBアラーム
│ │ ├── ec2-alarms.ts # EC2アラーム
│ │ └── rds-alarms.ts # RDSアラーム
│ └── dashboard/
│ └── monitoring-dashboard.ts # 統合ダッシュボード
├── package.json
└── tsconfig.json
5.2 メインスタック
// lib/monitoring-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
import { Construct } from 'constructs';
import { AlbAlarms } from './alarms/alb-alarms';
import { Ec2Alarms } from './alarms/ec2-alarms';
import { RdsAlarms } from './alarms/rds-alarms';
import { MonitoringDashboard } from './dashboard/monitoring-dashboard';
interface MonitoringStackProps extends cdk.StackProps {
albArn: string;
targetGroupArn: string;
instanceId: string;
dbInstanceIdentifier: string;
alertEmail: string;
}
export class MonitoringStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: MonitoringStackProps) {
super(scope, id, props);
// SNSトピックの作成(アラート通知先)
const alarmTopic = new sns.Topic(this, 'AlarmTopic', {
displayName: 'Web Application Monitoring Alarms',
topicName: 'web-app-monitoring-alarms',
});
// メール通知の設定
alarmTopic.addSubscription(
new subscriptions.EmailSubscription(props.alertEmail)
);
// ALBアラームの作成
new AlbAlarms(this, 'AlbAlarms', {
albArn: props.albArn,
targetGroupArn: props.targetGroupArn,
alarmTopic,
});
// EC2アラームの作成
new Ec2Alarms(this, 'Ec2Alarms', {
instanceId: props.instanceId,
alarmTopic,
});
// RDSアラームの作成
new RdsAlarms(this, 'RdsAlarms', {
dbInstanceIdentifier: props.dbInstanceIdentifier,
alarmTopic,
});
// 統合ダッシュボードの作成
new MonitoringDashboard(this, 'Dashboard', {
albArn: props.albArn,
instanceId: props.instanceId,
dbInstanceIdentifier: props.dbInstanceIdentifier,
});
// 出力
new cdk.CfnOutput(this, 'AlarmTopicArn', {
value: alarmTopic.topicArn,
description: 'SNS Topic ARN for alarms',
});
}
}
5.3 ALBアラーム
// lib/alarms/alb-alarms.ts
import * as cdk from 'aws-cdk-lib';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as cloudwatch_actions from 'aws-cdk-lib/aws-cloudwatch-actions';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as sns from 'aws-cdk-lib/aws-sns';
import { Construct } from 'constructs';
interface AlbAlarmsProps {
albArn: string;
targetGroupArn: string;
alarmTopic: sns.ITopic;
}
export class AlbAlarms extends Construct {
constructor(scope: Construct, id: string, props: AlbAlarmsProps) {
super(scope, id);
// ALBの参照取得
const alb = elbv2.ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(
this,
'ALB',
{
loadBalancerArn: props.albArn,
securityGroupId: '', // ダミー値
}
);
// ターゲットグループの参照取得
const targetGroup = elbv2.ApplicationTargetGroup.fromTargetGroupAttributes(
this,
'TargetGroup',
{
targetGroupArn: props.targetGroupArn,
}
);
// 1. TargetResponseTime(レイテンシ)
const highLatencyAlarm = new cloudwatch.Alarm(this, 'ALBHighLatency', {
alarmName: 'ALB-HighLatency',
alarmDescription: 'ALB target response time is high',
metric: targetGroup.metricTargetResponseTime({
statistic: 'Average',
period: cdk.Duration.minutes(1),
}),
threshold: 0.5, // 500ms
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
highLatencyAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 2. HTTPCode_Target_5XX_Count(5xxエラー)
const serverErrorsAlarm = new cloudwatch.Alarm(this, 'ALBServerErrors', {
alarmName: 'ALB-ServerErrors',
alarmDescription: 'ALB is returning 5xx errors',
metric: targetGroup.metricHttpCodeTarget(
elbv2.HttpCodeTarget.TARGET_5XX_COUNT,
{
statistic: 'Sum',
period: cdk.Duration.minutes(1),
}
),
threshold: 5,
evaluationPeriods: 1,
datapointsToAlarm: 1,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
serverErrorsAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 3. HealthyHostCount(正常なホスト数)
const unhealthyHostsAlarm = new cloudwatch.Alarm(this, 'ALBUnhealthyHosts', {
alarmName: 'ALB-UnhealthyHosts',
alarmDescription: 'ALB has unhealthy hosts',
metric: targetGroup.metricHealthyHostCount({
statistic: 'Minimum',
period: cdk.Duration.minutes(1),
}),
threshold: 1,
evaluationPeriods: 1,
datapointsToAlarm: 1,
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
treatMissingData: cloudwatch.TreatMissingData.BREACHING,
});
unhealthyHostsAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
}
}
5.4 EC2アラーム
// lib/alarms/ec2-alarms.ts
import * as cdk from 'aws-cdk-lib';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as cloudwatch_actions from 'aws-cdk-lib/aws-cloudwatch-actions';
import * as sns from 'aws-cdk-lib/aws-sns';
import { Construct } from 'constructs';
interface Ec2AlarmsProps {
instanceId: string;
alarmTopic: sns.ITopic;
}
export class Ec2Alarms extends Construct {
constructor(scope: Construct, id: string, props: Ec2AlarmsProps) {
super(scope, id);
// 1. CPUUtilization(CPU使用率)
const highCpuAlarm = new cloudwatch.Alarm(this, 'EC2HighCPU', {
alarmName: `EC2-HighCPU-${props.instanceId}`,
alarmDescription: 'EC2 instance CPU utilization is high',
metric: new cloudwatch.Metric({
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
dimensionsMap: {
InstanceId: props.instanceId,
},
statistic: 'Average',
period: cdk.Duration.minutes(5),
}),
threshold: 80,
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
highCpuAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 2. StatusCheckFailed(ステータスチェック失敗)
const statusCheckAlarm = new cloudwatch.Alarm(this, 'EC2StatusCheckFailed', {
alarmName: `EC2-StatusCheckFailed-${props.instanceId}`,
alarmDescription: 'EC2 instance status check failed',
metric: new cloudwatch.Metric({
namespace: 'AWS/EC2',
metricName: 'StatusCheckFailed',
dimensionsMap: {
InstanceId: props.instanceId,
},
statistic: 'Maximum',
period: cdk.Duration.minutes(1),
}),
threshold: 1,
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
});
statusCheckAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 3. メモリ使用率(CloudWatch Agent必要)
const highMemoryAlarm = new cloudwatch.Alarm(this, 'EC2HighMemory', {
alarmName: `EC2-HighMemory-${props.instanceId}`,
alarmDescription: 'EC2 instance memory utilization is high',
metric: new cloudwatch.Metric({
namespace: 'CWAgent',
metricName: 'mem_used_percent',
dimensionsMap: {
InstanceId: props.instanceId,
},
statistic: 'Average',
period: cdk.Duration.minutes(5),
}),
threshold: 80,
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
highMemoryAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 4. ディスク使用率(CloudWatch Agent必要)
const highDiskAlarm = new cloudwatch.Alarm(this, 'EC2HighDisk', {
alarmName: `EC2-HighDisk-${props.instanceId}`,
alarmDescription: 'EC2 instance disk utilization is high',
metric: new cloudwatch.Metric({
namespace: 'CWAgent',
metricName: 'disk_used_percent',
dimensionsMap: {
InstanceId: props.instanceId,
path: '/',
fstype: 'ext4',
},
statistic: 'Average',
period: cdk.Duration.minutes(5),
}),
threshold: 80,
evaluationPeriods: 1,
datapointsToAlarm: 1,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
highDiskAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
}
}
5.5 RDSアラーム
// lib/alarms/rds-alarms.ts
import * as cdk from 'aws-cdk-lib';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as cloudwatch_actions from 'aws-cdk-lib/aws-cloudwatch-actions';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as sns from 'aws-cdk-lib/aws-sns';
import { Construct } from 'constructs';
interface RdsAlarmsProps {
dbInstanceIdentifier: string;
alarmTopic: sns.ITopic;
}
export class RdsAlarms extends Construct {
constructor(scope: Construct, id: string, props: RdsAlarmsProps) {
super(scope, id);
// RDSインスタンスの参照取得
const dbInstance = rds.DatabaseInstance.fromDatabaseInstanceAttributes(
this,
'Database',
{
instanceIdentifier: props.dbInstanceIdentifier,
instanceEndpointAddress: '', // ダミー値
port: 5432,
securityGroups: [],
}
);
// 1. DatabaseConnections(接続数)
const highConnectionsAlarm = new cloudwatch.Alarm(this, 'RDSHighConnections', {
alarmName: `RDS-HighConnections-${props.dbInstanceIdentifier}`,
alarmDescription: 'RDS database connections are high',
metric: dbInstance.metricDatabaseConnections({
statistic: 'Average',
period: cdk.Duration.minutes(5),
}),
threshold: 320, // db.t3.mediumの80%(要調整)
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
highConnectionsAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 2. CPUUtilization(CPU使用率)
const highCpuAlarm = new cloudwatch.Alarm(this, 'RDSHighCPU', {
alarmName: `RDS-HighCPU-${props.dbInstanceIdentifier}`,
alarmDescription: 'RDS CPU utilization is high',
metric: dbInstance.metricCPUUtilization({
statistic: 'Average',
period: cdk.Duration.minutes(5),
}),
threshold: 80,
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
highCpuAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 3. FreeableMemory(空きメモリ)
const lowMemoryAlarm = new cloudwatch.Alarm(this, 'RDSLowMemory', {
alarmName: `RDS-LowMemory-${props.dbInstanceIdentifier}`,
alarmDescription: 'RDS freeable memory is low',
metric: dbInstance.metricFreeableMemory({
statistic: 'Average',
period: cdk.Duration.minutes(5),
}),
threshold: 100 * 1024 * 1024, // 100MB
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
});
lowMemoryAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 4. ReadLatency(読み取りレイテンシ)
const highReadLatencyAlarm = new cloudwatch.Alarm(this, 'RDSHighReadLatency', {
alarmName: `RDS-HighReadLatency-${props.dbInstanceIdentifier}`,
alarmDescription: 'RDS read latency is high',
metric: dbInstance.metricReadLatency({
statistic: 'Average',
period: cdk.Duration.minutes(5),
}),
threshold: 0.01, // 10ms
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
highReadLatencyAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 5. WriteLatency(書き込みレイテンシ)
const highWriteLatencyAlarm = new cloudwatch.Alarm(this, 'RDSHighWriteLatency', {
alarmName: `RDS-HighWriteLatency-${props.dbInstanceIdentifier}`,
alarmDescription: 'RDS write latency is high',
metric: dbInstance.metricWriteLatency({
statistic: 'Average',
period: cdk.Duration.minutes(5),
}),
threshold: 0.01, // 10ms
evaluationPeriods: 2,
datapointsToAlarm: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
});
highWriteLatencyAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
// 6. FreeStorageSpace(空きストレージ)
const lowStorageAlarm = new cloudwatch.Alarm(this, 'RDSLowStorage', {
alarmName: `RDS-LowStorage-${props.dbInstanceIdentifier}`,
alarmDescription: 'RDS free storage space is low',
metric: dbInstance.metricFreeStorageSpace({
statistic: 'Average',
period: cdk.Duration.minutes(5),
}),
threshold: 5 * 1024 * 1024 * 1024, // 5GB
evaluationPeriods: 1,
datapointsToAlarm: 1,
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
});
lowStorageAlarm.addAlarmAction(new cloudwatch_actions.SnsAction(props.alarmTopic));
}
}
5.6 統合ダッシュボード
// lib/dashboard/monitoring-dashboard.ts
import * as cdk from 'aws-cdk-lib';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import { Construct } from 'constructs';
interface MonitoringDashboardProps {
albArn: string;
instanceId: string;
dbInstanceIdentifier: string;
}
export class MonitoringDashboard extends Construct {
constructor(scope: Construct, id: string, props: MonitoringDashboardProps) {
super(scope, id);
const dashboard = new cloudwatch.Dashboard(this, 'Dashboard', {
dashboardName: 'WebAppMonitoring',
});
// ALBセクション
dashboard.addWidgets(
new cloudwatch.TextWidget({
markdown: '# ALB Metrics',
width: 24,
height: 1,
})
);
dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'ALB Target Response Time',
left: [
new cloudwatch.Metric({
namespace: 'AWS/ApplicationELB',
metricName: 'TargetResponseTime',
dimensionsMap: {
LoadBalancer: this.extractAlbName(props.albArn),
},
statistic: 'Average',
}),
],
width: 12,
}),
new cloudwatch.GraphWidget({
title: 'ALB 5XX Errors',
left: [
new cloudwatch.Metric({
namespace: 'AWS/ApplicationELB',
metricName: 'HTTPCode_Target_5XX_Count',
dimensionsMap: {
LoadBalancer: this.extractAlbName(props.albArn),
},
statistic: 'Sum',
}),
],
width: 12,
})
);
// EC2セクション
dashboard.addWidgets(
new cloudwatch.TextWidget({
markdown: '# EC2 Metrics',
width: 24,
height: 1,
})
);
dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'EC2 CPU Utilization',
left: [
new cloudwatch.Metric({
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
dimensionsMap: {
InstanceId: props.instanceId,
},
statistic: 'Average',
}),
],
width: 12,
}),
new cloudwatch.GraphWidget({
title: 'EC2 Memory Utilization',
left: [
new cloudwatch.Metric({
namespace: 'CWAgent',
metricName: 'mem_used_percent',
dimensionsMap: {
InstanceId: props.instanceId,
},
statistic: 'Average',
}),
],
width: 12,
})
);
// RDSセクション
dashboard.addWidgets(
new cloudwatch.TextWidget({
markdown: '# RDS Metrics',
width: 24,
height: 1,
})
);
dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'RDS Database Connections',
left: [
new cloudwatch.Metric({
namespace: 'AWS/RDS',
metricName: 'DatabaseConnections',
dimensionsMap: {
DBInstanceIdentifier: props.dbInstanceIdentifier,
},
statistic: 'Average',
}),
],
width: 12,
}),
new cloudwatch.GraphWidget({
title: 'RDS CPU Utilization',
left: [
new cloudwatch.Metric({
namespace: 'AWS/RDS',
metricName: 'CPUUtilization',
dimensionsMap: {
DBInstanceIdentifier: props.dbInstanceIdentifier,
},
statistic: 'Average',
}),
],
width: 12,
})
);
}
private extractAlbName(albArn: string): string {
// ARNからALB名を抽出
// arn:aws:elasticloadbalancing:region:account-id:loadbalancer/app/load-balancer-name/load-balancer-id
const parts = albArn.split(':');
const namePart = parts[parts.length - 1];
return namePart.split('/').slice(1).join('/');
}
}
コスト最適化のテクニック
監視を実装する際、コストを抑えるためのテクニックを紹介します。
1. アラーム数の最適化
不要なアラームを削除:
- 実際にアクションを取らないアラームは削除
- 類似のアラームは統合(例: 複数のしきい値ではなく、1つの適切なしきい値)
コスト:
- CloudWatchアラーム: $0.10/月/アラーム
- 10個のアラーム = $1/月
- 100個のアラーム = $10/月
2. メトリクスの保持期間
CloudWatchメトリクスは自動的に以下の保持期間で保存されます:
- 1分間隔: 15日間
- 5分間隔: 63日間
- 1時間間隔: 455日間
古いデータは自動的に削除されるため、追加コストはかかりません。
3. カスタムメトリクスの頻度調整
CloudWatch Agentで取得するカスタムメトリクス(メモリ、ディスク)は、頻度を調整できます。
{
"metrics": {
"metrics_collected": {
"mem": {
"measurement": [
{
"name": "mem_used_percent",
"rename": "mem_used_percent",
"unit": "Percent"
}
],
"metrics_collection_interval": 60
}
}
}
}
- リアルタイム監視: 60秒(1分)
- コスト重視: 300秒(5分)
コスト:
- カスタムメトリクス: $0.30/月/メトリクス(最初の10,000メトリクス)
4. ログの保持期間設定
CloudWatch Logsの保持期間を適切に設定します。
new logs.LogGroup(this, 'ApplicationLogs', {
logGroupName: '/aws/application/logs',
retention: logs.RetentionDays.ONE_WEEK, // 1週間
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
- 本番環境: 30日〜90日
- 開発環境: 1週間〜14日
まとめ
本記事では、「何を監視すべきか」という課題に対して、理論と実践の2つのアプローチを組み合わせて解説しました。
重要なポイント
-
すべてを監視する必要はない
- Four Golden Signals(Latency、Traffic、Errors、Saturation)を基準に
- ビジネスへの影響度で優先順位をつける
-
AWS公式の推奨アラームを活用
- CloudWatchが提供する推奨アラームは、AWSの運用ノウハウの結晶
- しきい値も事前設定されており、すぐに使える
-
各レイヤーで監視すべきメトリクス
- ALB: TargetResponseTime、HTTPCode_Target_5XX_Count、HealthyHostCount
- EC2: CPUUtilization、StatusCheckFailed、メモリ・ディスク使用率
- RDS: DatabaseConnections、CPUUtilization、FreeableMemory、レイテンシ
次のステップ
監視設計は、一度設定したら終わりではありません。以下のサイクルで継続的に改善していきましょう:
- デプロイ: CDKで監視基盤を構築
- 観察: アラートの発生頻度を確認
- 調整: 誤検知が多ければしきい値を調整
- 改善: 見逃しがあればメトリクスを追加
監視は、システムの健全性を維持し、ユーザー体験を向上させるための重要な要素です。本記事の内容が、皆さんの監視設計の一助となれば幸いです。
参考リンク: