前回以下の監視アラートに関する記事を書きました。(予想以上に反響がありました。ありがとうございます)
監視アラートの基準はシステム特性によるものの、同じメトリクスを使用する場合は閾値や集計期間がシステムによって違うぐらいで仕組みとしては同じため、なるべく使い回しができるようコード化しておきたいところです。
今回は監視アラートをAWS CDKで実装する方法について簡単にご紹介したいと思います。
※CDKユーザーの中にはご存じの方も多いかと思いますが、特にメトリクス監視はAWS公式のBLEAにも実装がされており非常に参考になります。本記事においても数多く引用させていただいております。
目次
実装したいもの
前回の記事からの抜粋になりますが、今回はCloudWatchを使用した以下2つの仕組みをCDKで実装することを考えます。
- (1).各サービスから収集したメトリクスを元にアラートを行う。
- (2).アプリから出力したログを元にアラートを行う。
(1).各サービスから収集したメトリクスを元にアラートを行う
こちらですが、AWS公式のBLEAが非常に参考になります(そのまま流用できるレベルで実装がされています)。
以下のサンプル構成においてアラート(メール通知)の実装例が示されています。
ECSによるコンテナアプリのサンプルアプリ github.com
API Gateway-LambdaによるAPIのサンプルアプリ github.com
CloudTrailなどセキュリティサービスを中心にしたガバナンスベース(リンクはstandalone版) github.com
アラートを出すための実装の流れとしては以下になります。
BLEAの実装例を元に詳細を順に見ていきます。
①通知先のSNSトピックを定義する
これはBLEAの実装そのままで基本問題ないと思います。以下の3段階になります。
これはどのサンプル構成でも同じ流れで実装されています。
// SNSトピックを作成 const topic = new sns.Topic(this, 'MonitorAlarmTopic'); // サブスクリプションを作成し、トピックと通知先のメールアドレス(エンドポイント)を紐付け new sns.Subscription(this, 'MonitorAlarmEmail', { endpoint: props.notifyEmail, protocol: sns.SubscriptionProtocol.EMAIL, topic: topic, }); this.alarmTopic = topic; // CloudWatchからSNSトピックに対してパブリッシュを許可 topic.addToResourcePolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, principals: [new iam.ServicePrincipal('cloudwatch.amazonaws.com')], actions: ['sns:Publish'], resources: [topic.topicArn], }), );
※以下より引用。コメントのみ追記・変更しています。
②メトリクスに対してアラームを作成し、SNSトピックを通知先に指定する
やたらタイトルが長いように見えますが、L2 Constructを使用するとタイトルのことが一行でできることが多いです。
具体的には以下の3段階を1行で実装できます。
- L2 Constructのメソッドを使用し
Metric
を生成。例えば以下はECS FargateのCPUUtilizationのMetric
を返却するメソッドはこちら。 Metric
のcreateAlarmを使用し、Alarm
を設定。引数でアラート条件を指定する。Alarm
のaddAlarmActionを使用し、アラート発生時に①で生成したSNSトピックに対して通知するよう設定。以下はaddAlarmAction
のリンク。
以下はBLEAにおける実装例です。一行で書けますが可読性のため改行が入っています。
ecsService // L2 ConstructのmetricCpuUtilization を使用し、Metricを取得 .metricCpuUtilization({ period: cdk.Duration.minutes(1), statistic: cw.Statistic.AVERAGE, }) // MetricのcreateAlarmを使用しAlarmを作成 .createAlarm(this, 'FargateCpuUtil', { evaluationPeriods: 3, datapointsToAlarm: 3, threshold: 95, comparisonOperator: cw.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, actionsEnabled: true, }) // AlarmのaddAlarmActionを使用して、アラート発生時にSNSトピックに通知するよう指定 .addAlarmAction(new cw_actions.SnsAction(alarmTopic));
※以下より引用。コメントのみ追記・変更しています。
L2 Constructが実装されていれば、メジャーどころのメトリクスに対応するMetric
を取得するメソッドが大体ある印象です。
一方で以下のようなケースでは上記のやり方は使えません。
- L2 Constructが存在しない。例えばElastiCacheは2023/2現在、L2 Constructが存在しません。
- L2 Constructは存在するが、アラート対象としたいメトリクスの
Metric
を生成するメソッドがない。 - 手動設定したサービスに対するアラートを設定したい(SNSのSMS料金やSESのバウンス率などのは大体このパターンになることが多いと思います
上記のような場合は名前空間とメトリクス名を元に、直接new
してMetric
を生成すれば同様のことができます。
BLEAだと例えばLambdaのConcurrentExecutions
の監視の実装に使われています。Lambda FunctionはL2 Constructが存在しているもの、ConcurrentExecutions
のMetric
を返すメソッドは実装されていません。
// Metricを直接生成 new cw.Metric({ namespace: 'AWS/Lambda', // 名前空間を指定 metricName: 'ConcurrentExecutions', // メトリクス名を指定 period: cdk.Duration.minutes(5), statistic: cw.Statistic.MAXIMUM, dimensionsMap: { FunctionName: getItemFunction.functionName, }, }) // これ以降は同じ。Alarmを作成して、AlarmActionとしてSNSトピックを指定する .createAlarm(this, 'getItemConcurrentExecutionsAlarm', { evaluationPeriods: 3, threshold: 80, datapointsToAlarm: 3, comparisonOperator: cw.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, actionsEnabled: true, }) .addAlarmAction(new cw_actions.SnsAction(props.alarmTopic));
※以下より引用。コメントのみ追記・変更しています。
なお、名前空間やメトリクス名は実際のものと完全一致している必要があります。マネジメントコンソールやAWS CLIで確認可能です。
メトリクスではなくEventを元にアラートを行いたい場合
この場合は対象のイベントパターンを検知し、ターゲットにSNSトピックを指定することで実現することが可能です。セキュリティ系のサービスではよく用いることになるかと思います。
例えば以下はConfigで非準拠となったルールを検知した場合にアラートを行う、BLEAの実装例です。
new cwe.Rule(this, 'BLEARuleConfigRules', { description: 'CloudWatch Event Rule to send notification on Config Rule compliance changes.', enabled: true, // 検知対象のイベントパターンを定義 eventPattern: { source: ['aws.config'], detailType: ['Config Rules Compliance Change'], detail: { // 検知対象のConfigルールを指定 configRuleName: ['bb-default-security-group-closed'], newEvaluationResult: { // 非準拠の場合 complianceType: ['NON_COMPLIANT'], }, }, }, // 通知先のSNSトピックを指定 targets: [new cwet.SnsTopic(secTopic)], });
※以下より引用。コメントのみ追記・変更しています。
なお上記のblea-security-alarm-stack.ts
は他にもCloudTrailやSecurity Hub, GuardDutyなどのアラートも実装されており、ほぼそのまま使えるようになっており非常に参考になります。
(2).アプリから出力したログを元にアラートを行う。
以下の図の下側のルートになります。CloudWatch Logsに出力したログを元に、サブスクリプションフィルターを使用し通知を行います。
なおSubscriptionFIlterで宛先をLambdaとしているのは、メッセージの加工を行うためです。以下が参考になります。
ここではサブスクリプションフィルターを実装してみます。CloudWatch LogsのロググループのL2 Constructにおける addSubscriptionFilter を使用することで実装可能です。
declare const logGroup: logs.LogGroup // 対象のロググループ const subscriptionFilter = logGroup.addSubscriptionFilter('SubScriptionFilter', { destination: new destinations.LambdaDestination(lambdaFunction), // Lambdaを指定 filterPattern: logs.FilterPattern.anyTerm('ERROR', 'Error', 'error') // パターンとしていずれかに一致(OR条件)で指定。 }); // Escape Hatches を使用して依存関係を定義 (subscriptionFilter.node.defaultChild as logs.CfnSubscriptionFilter).addDependsOn(subscriptionFilter.node.findChild('CanInvokeLambda') as lambda.CfnPermission);
filterPatternについてはメソッドがいくつかあるため、要件に合ったものを使用すれば良いです。ここではERROR
, Error
, error
のいずれか一つにマッチした場合を指定しています。
またEscape Hatchesで CloudWatch LogsからLambdaをトリガーするパーミッション → サブスクリプションフィルターの順でリソース作成が行われるよう、依存関係を定義しています。
これは2023/2現在だと、パーミッション作成とサブスクリプションフィルター作成の間に依存関係がなく並列で作成しようとし、エラーになる可能性があるためです。以下のIssueが起票されています。
なお、私はEscape Hatchesの存在を知らない頃にこの問題にぶち当たり、その時はL1 Constructを使用しました。 今やるのであればL2 Construct+Escape Hatchesでやるのが良いと思いますが、参考までに以下に記載しておきます。
内容としては上記のL2 Constructで実装したものと全く同じです。
// ロググループにからLambda関数を呼び出すパーミッションを作成 const lambdaPermission = new lambda.CfnPermission(this, 'LambdaPermission', { functionName: lambdaFunction.functionName, action: "lambda:InvokeFunction", principal: "logs.amazonaws.com", sourceArn: logGroup.logGroupArn }); // サブスクリプションフィルターを作成 const subscriptionFilter = new logs.CfnSubscriptionFilter(this, "SubScriptionFilter", { logGroupName: logGroup.logGroupName, destinationArn: lambdaFunction.functionArn, filterPattern: "?ERROR ?Error ?error" }); // サブスクリプションフィルターとパーミッションの依存関係を定義 subscriptionFilter.addDependsOn(lambdaPermission);
おまけ:ダッシュボードについて
BLEAにはCloudWatch Dashboardの実装例もあります。これもそのまま流用できる部分も多く、また実装方法の確認にも非常に便利です。
以下はBLEAのものをそのまま使用して、AuroraのWriterとReaderのLatencyをダッシュボードに表示してみた例です。
流用してカスタマイズするだけでいい感じのダッシュボードが作れるのと、書き方も学べるので非常におすすめです。