AWS lambdaを使用してアラートの件名・本文をカスタマイズする
はじめに
あと約1ヶ月で2024年が終わってしまうことに驚愕しているmickeyです。今年も例外なくあっという間でした。
さて、AWSの各サービスでパフォーマンスを監視し、設定された閾値を超えた際にSNSで設定されたEメールにアラートを発報する仕組みは広く用いられていますが、今回はLambdaを活用し、アラートメールの件名や本文をカスタマイズすることで、アラートの種類や内容をよりわかりやすくする方法をご紹介します。これにより、メールの可読性が向上しアラート受信者が迅速に対応できる効果が期待されます。
構成
今回の構成は以下の通りです。
CloudWatchアラートからの通知を受け取る場合と、EventBridgeからの通知を受け取る場合の両方を想定して設計を実施しました。
アラート設定
上記の各サービスの具体的なアラート設定は以下の通りです。
- Redshift:クラスターの起動・停止エラーを監視し、ステータスが「Unhealty」の場合にアラートを通知
- Glue:Glueジョブが失敗またはタイムアウトした場合にアラートを通知
カスタマイズ後の件名・本文
カスタマイズ後の件名・本文は以下の通りです。
- 件名:「〇〇環境 ××」
- 〇〇:開発・検証・本番・テストのいずれか
- ××:Redshiftアラート通知・Glueジョブ失敗のいずれか
- 本文:整形されたJSONの出力
件名にて、どの環境で、どのサービスでどのようなアラートが発報されたかの概要を掴み、かつ本文で整形されたJSONを出力させることで、アラートの詳細を確認しやすくしています。
AWS CDKでの実装
ここからは、実装に入っていきます。上記設定をAWS CDKでの実装を、一部抜粋しながら紹介します。使用言語はTypeScriptです。
各サービスのアラート設定
Redshiftのアラート設定
private createRedshiftAlarms(lambdaFunction: IFunction, clusterId: string) {
const healthStatusAlarm = new cloudwatch.Alarm(this, `RedshiftHealthStatusAlarm`, {
metric: new cloudwatch.Metric({
namespace: 'AWS/Redshift',
metricName: 'HealthStatus',
dimensionsMap: { ClusterIdentifier: clusterId },
statistic: 'Minimum',
}),
threshold: 1,
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
evaluationPeriods: 1,
alarmDescription: 'クラスタのヘルスステータスが不健康な場合にアラーム',
});
healthStatusAlarm.addAlarmAction(new cloudwatchActions.LambdaAction(lambdaFunction));
}
Redshiftクラスタのヘルスステータスがしきい値を下回った際に(Unhealthy
の場合には0になる)、設定されたアラームが発報され、そのアクションとして指定されたLambda関数が呼び出されます。
そして、healthStatusAlarm.addAlarmAction(new cloudwatchActions.LambdaAction(lambdaFunction))
の部分で、CloudWatchアラームのアクションとして指定されたLambda関数を実行する設定を行っています。
Glueのアラート設定
private createGlueAlarms(lambdaFunction: IFunction, JobName: string) {
// Glue ジョブが失敗またはタイムアウトした場合のルール
const failedRule = new events.Rule(this, 'GluePythonShellFailedEvent', {
eventPattern: {
source: ['aws.glue'],
detailType: ['Glue Job State Change'],
detail: {
state: ['FAILED', 'TIMEOUT'],
},
},
targets: [new events_targets.LambdaFunction(lambdaFunction)],
});
上記コードでは、AWS Glueジョブが失敗またはタイムアウトした際に、設定されたアラームが発報され、そのアクションとして指定されたLambda関数が呼び出されます。
そして、targets: [new events_targets.LambdaFunction(lambdaFunction)]
の部分で、指定されたLambda関数をターゲットとして実行する設定を行っています。
これら2つのコードでは、いずれもアラート通知のターゲットとしてLambdaを使用することで、検知した内容を整形した上で、SNSに渡せるようにしています。
lambdaの設定
次に、lambda関数のCDK上での設定について説明します。
const EmailContentFormatterFunction = new NodejsFunction(this, 'EmailContentFormatter', {
functionName: 'EmailContentFormatter',
entry: './function/handler.ts',
handler: 'index.handler',
runtime: Runtime.NODEJS_20_X,
environment: {
ENV: environment,
SNS_TOPIC_ARN: alarmTopicArn,
},
deadLetterTopic: Topic.fromTopicArn(this, 'DeadLetterTopic', alarmTopicArn),
});
new cdk.CfnOutput(this, 'EmailContentFormatterFunctionArn', {
value: EmailContentFormatterFunction.functionArn,
exportName: 'EmailContentFormatterFunctionArn',
});
EmailContentFormatterFunction.addToRolePolicy(new iam.PolicyStatement({
actions: ['sns:Publish'],
resources: [alarmTopicArn],
}));
// EventBridgeからLambdaを呼び出す権限を付与
EmailContentFormatterFunction.addPermission('AllowEventBridgeInvoke', {
principal: new iam.ServicePrincipal('events.amazonaws.com'),
action: 'lambda:InvokeFunction',
sourceArn: cdk.Stack.of(this).formatArn({
service: 'events',
resource: '*',
}),
});
environment
には、Lambda 関数で使用する環境変数として ENV
と SNS_TOPIC_ARN
が設定されています。さらに、addToRolePolicy
メソッドを使用して Lambda 関数に SNS トピックへのメッセージ送信権限 (sns:Publish
) を付与しています。これにより、この Lambda 関数は指定された alarmTopicArn
に対してメッセージを公開できるようになります。
最後に、addPermission
メソッドを使って、EventBridge サービス (events.amazonaws.com) がこの Lambda 関数を呼び出す権限を設定しています。これにより、EventBridge からのイベントトリガーによって Lambda 関数が実行される仕組みが構築されています。sourceArn
には EventBridge イベントソースの ARN が指定されており、この設定により EventBridge が Lambda 関数を適切にトリガーできるようになります。
Lambda上で動作させるコード(handler.ts
)は以下の通りです。
handler.ts
import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";
// SNSクライアントの初期化
const snsClient = new SNSClient({});
export const handler = async (event: any, context: any) => {
// 受け取ったイベントをJSON文字列としてフォーマット
const formattedMessage = JSON.stringify(event, null, 4);
// イベント詳細に基づいてメッセージタイプを決定
const detailType = event['detail-type'] || '';
const source = event['source'] || '';
// イベントのタイプに応じてメッセージタイプを設定
let messageType: string = ''; // 空で文字列型として定義
if (source === "aws.cloudwatch" && 'alarmArn' in event) {
messageType = "Redshiftアラート通知";
} else if (detailType === "Glue Job State Change") {
const state = event.detail?.state || '';
if (state === "SUCCEEDED") {
messageType = "Glueジョブ成功";
} else if (state === "FAILED") {
messageType = "Glueジョブ失敗";
} else {
messageType = "Glueジョブ状態不明";
}
} else {
messageType = "通知"; // 最後のelseで「通知」とする
}
// 環境変数から環境名を取得
const env = process.env.ENV || '';
const topicArn = process.env.SNS_TOPIC_ARN;
// 環境名に基づいて件名の環境部分を決定
let environmentName: string = ''; // 空で文字列型として定義
if (env === 'dev') {
environmentName = "開発環境";
} else if (env === 'stg') {
environmentName = "検証環境";
} else if (env === 'prod') {
environmentName = "本番環境";
} else {
environmentName = "テスト環境";
}
// SNSメッセージの件名をカスタマイズ
const subject = `${environmentName} ${messageType}`;
// SNSトピックにメッセージを公開
try {
await snsClient.send(new PublishCommand({
TopicArn: topicArn,
Subject: subject,
Message: formattedMessage
}));
return {
statusCode: 200,
body: JSON.stringify('Notification sent successfully!')
};
} catch (error) {
console.error('Error publishing to SNS:', error);
return {
statusCode: 500,
body: JSON.stringify('Failed to send notification.')
};
}
};
CloudWatchからのアラートであるかどうかを判別する為、if文の条件式でsource === "aws.cloudwatch" && 'alarmArn' in event
とすることで、イベントのソースがAWS CloudWatchであり、かつ'alarmArn' in event
でイベントオブジェクト内にalarmArnというフィールドが存在するかどうかを確認しています(alarmArnはCloudWatchアラームに関連するARNです)。
また、同様にEventBridgeからのアラートであるかどうかを判別する為、こちらもif文の条件式のdetailType === "Glue Job State Change"
にてGlueジョブの状態変化であるかを確認し、かつstate
の状態によってそれぞれ異なる状態であることを表示させています。ジョブの状態が「SUCCEEDED」または「FAILED」であれば、それぞれ「Glueジョブ成功」や「Glueジョブ失敗」として表示するよう設定します。それ以外は「Glueジョブ状態不明」という表示になります。
そして、環境変数 ENV
に基づいて通知がどの環境から送信されたものかを判別します。例えば、dev
なら「開発環境」、prod
なら「本番環境」といった具合です。環境変数が設定されていない場合は「テスト環境」として扱います。
本文は、const formattedMessage = JSON.stringify(event, null, 4);
にて、受け取った event オブジェクトを JSON 形式に変換し、インデント(4 スペース)で整形しています。
件名には先ほど決定した環境名とメッセージタイプを組み合わせたものを使用し、本文には整形済みのJSONデータを含め、最終的にSNSトピックに対してメッセージを公開します。
実際の動作
上記設定後、実際に各アラートを発報させてみた結果は以下の通りです。
Redshiftのアラート通知メール
Glueのアラート通知メール
いずれの場合でも、件名からアラートの内容が明確に把握でき、さらに本文の JSON が整形されて出力されることで、アラートに関する可読性が向上していることが確認できました!
最後まで読んでいただいた皆さん、ありがとうございました。