要約
Lambda で特定の文字列がログ出力されたらメール通知してくれる仕組みを CloudFormation で作ります。
導入
Lambda で特定の文字列がログ出力されたらメール通知してくれる仕組みがサクッと作れればいいな、って思うことはありませんか?ありますよね? Lambda 自体は SAM (Serverless Application Model) や Serverless Framework で作成するので一緒に作ってしまった方が良いですよね。設定し忘れていて通知されなかったら大変です。
本題
今回は Lambda のログに SystemFault という文字列が出力されたら CloudWatch のアラーム状態になってそれが SNS でメール通知してくれるような仕組みを SAM で作ってみます。
ディレクトリ構成はとりあえずこんな感じとしておきます。
. ├ sns.yml ├ template.yml └src └handler.py
Lambda のコード( src/handler.py )は以下のものとします。
def main(event, context):
print('↓の文字列が出力されるとメールが飛ぶ仕組み')
print('SystemFault')
return 'Hello, World!'
ただ実行されるだけだとエラーは起きませんが、この SystemFault をメトリクスフィルターで引っ掛けて拾ってみよう、というわけです。(参考:https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/MonitoringLogData.html)
テンプレートを書く
まず、メール通知に使う SNS トピックおよびサブスクリプションを作成します。複数の Lambda が存在しても通知するトピックは共通化されているのが自然なので、実際に使用するときもテンプレートを別にすることが多いであろうという意図でテンプレートを分けています。
AWSTemplateFormatVersion: "2010-09-09"
Description: SNS for Alarm Mail Notification
Parameters:
SNSSubscriptionEmail:
Type: String
Default: xxxx@example.com
Description: Email for Error Notice SNS Subscription.
Resources:
# SNS Topic
MyNoticeAlertTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: my-notice-error
DisplayName: my-notice-error
Subscription:
- Endpoint: !Ref SNSSubscriptionEmail
Protocol: email
Outputs:
MyNoticeAlertTopicArn:
Description: Topic Arn of MyNoticeAlert
Value: !Ref MyNoticeAlertTopic
Export:
Name: !Sub ${AWS::StackName}-MyNoticeAlertTopic
そしてメインの Lambda とロググループ、メトリクスフィルター、アラームについてのテンプレートですが、一気に全部載せると長いので部分ごとに分けてご説明します。
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Log Filter and Alarm Sample
Globals:
Function:
Runtime: python3.8
Timeout: 30
MemorySize: 256
Resources:
LogFilterAlarmSampleFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: log-filter-alarm-sample-function
Handler: handler.main
CodeUri: ./src
Role: !GetAtt LogFilterAlarmSampleFunctionRole.Arn
LogFilterAlarmSampleFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
RoleName: LogFilterAlarmSampleFunctionRole
Policies:
- PolicyName: LogFilterAlarmSampleFunctionPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/log-filter-alarm-sample-function*:*
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/log-filter-alarm-sample-function*:*:*
まずこちら。 Lambda のソースと実行ロールです。SAM なので頭に Transform: AWS::Serverless-2016-10-31 がついています。サンプルなのでログを出力する権限しかつけていません。
通常 SAM ではロググループも自動で作成されますが、このあとメトリクスフィルターを定義する際にリソース名を指定するため、このように明示的に定義しています。(参考:https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html)
# Log Group
LogFilterAlarmSampleFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${LogFilterAlarmSampleFunction}
RetentionInDays: 14
そしてメトリクスフィルターです。
# Log Filter
LogFilterAlarmSampleFunctionMetricFilter:
Type: AWS::Logs::MetricFilter
Properties:
FilterPattern: "SystemFault"
LogGroupName:
!Ref LogFilterAlarmSampleFunctionLogGroup
MetricTransformations:
-
MetricValue: "1"
MetricNamespace: LogMetrics
MetricName: !Sub ${LogFilterAlarmSampleFunction}/SystemFault
FilterPattern として SystemFault という文字列の出現回数をカウントします。(参考:https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-logs-metricfilter.html)
最後にアラームです。(参考:https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-cw-alarm.html)
# Alarm
LogFilterAlarmSampleFunctionAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmDescription: My Lambda SystemFault Alarm
AlarmName: !Sub ${LogFilterAlarmSampleFunction}-system-fault
AlarmActions:
- !ImportValue SampleSNSStack-MyNoticeAlertTopic
ComparisonOperator: GreaterThanOrEqualToThreshold
Threshold: 1
DatapointsToAlarm: 1
EvaluationPeriods: 1
MetricName: !Sub ${LogFilterAlarmSampleFunction}/SystemFault
Namespace: LogMetrics
Period: 60
Statistic: Maximum
上に書いたメトリクスを MetricName で指定し、「1分以内にメトリクスフィルターで拾った出現回数の最大値が1を超えたら」つまり「1分以内にフィルターパターンに合致したログが1回でも出力されれば」アラームのメール通知が飛ぶようになっています。
冒頭のディレクトリ構成の図に書いた template.yml はこれらの4つをそのままつなげたものです。
デプロイする
デプロイします。
まず SNS です。エンドポイントのメールアドレスは必要に応じて上書きします。
$ aws cloudformation deploy \ --template-file sns.yml \ --stack-name SampleSNSStack \ --parameter-overrides SNSSubscriptionEmail=yyyy@example.com
デプロイに成功するとサブスクリプションを Confirm して下さいというメールが来るので Confirm します。ちなみに 2020 年5月時点で CloudFormation のマネジメントコンソールの「リソース」タブから SNS トピックのリンクを飛ぼうとすると ARN の「:(コロン)」がエスケープされてしまって変な感じになりますが、問題ありません。
続けて Lambda 周りのテンプレートをデプロイしますが、まず Lambda のソースコードをパッケージングしてやる必要があります。バケット名には適切な既存のバケットを指定します。
$ aws cloudformation package --template-file template.yml \ --output-template-file packaged.yml \ --s3-bucket hoge-bucket
成功するとこんなログが出るかと思います。
Successfully packaged artifacts and wrote output template to file packaged.yml. Execute the following command to deploy the packaged template aws cloudformation deploy --template-file C:\Users\xxxx\hoge\packaged.yml --stack-name <YOUR STACK NAME>
デプロイします。ひっかけのような気がしますが、出力されたコマンドそのままでは不十分です。 IAM ロールを作成しますので --capabilities CAPABILITY_NAMED_IAM を付けてやる必要があるからです(十分な権限が付与されている前提ですが…)。
$ aws cloudformation deploy --template-file packaged.yml \ --stack-name SampleLogFilterAlarmStack \ --capabilities CAPABILITY_NAMED_IAM
完了したらマネジメントコンソールで確認してみましょう。
メトリクスフィルターですが、名前はマネジメントコンソールから手動で作ったときに比べてランダム文字列がサフィックスとして付きます。

アラームはこんな感じです。上で説明した通りの設定になっています。「1」という数字が各所に出ていて混乱しますがテンプレートとしてガッチリ管理してあれば安心ですね!

ちなみになぜかわからないのですが、アクションの項目を見る通知先の SNS トピックには保留中の確認という「エンドポイントは Confirm されていないよ!」というメッセージが出っぱなしになります。ちゃんと Confirm していても出るので動作としては問題ないですがちょっとアレです。

次は実際に動かしてちゃんとメール通知がくるか試してみます。
試す
デプロイが完了したら早速挙動を確かめてみます。
どういう方法でも良いので作成された Lambda を実行します。今回のサンプルでは適当でいいです。
テストイベントを作成したら実行します。

ちょっとするとメールが来ます。やったね。

まとめ
以上の方法を活用すれば、Lambda の処理中にランダムな文字列を生成してログ出力するようにしたうえで、それが事前に登録したランダムな文字列と一致したら開発者の自分にメール通知させて「おめでとうございます!一兆分の一の確率で当選しました!」という粋な遊びができますね。