Lambdaが異常終了した際にアラート通知させる仕組みをAWS SAMのテンプレートで一括デプロイ
お疲れ様です。サーバーレス開発部の新井です。
本日は、Lambdaが異常終了した場合に、CloudWatch Alarms → SNS でアラート通知する仕組みをAWS SAMのテンプレートから一括でデプロイする方法を紹介したいと思います。現在、Lambdaの監視には
- CloudWatchの標準メトリクスをCloudWatch Alarmsで監視してからSNS経由で通知
- CloudWatchLogsのメトリクスフィルタでカスタムメトリクスを作成し、CloudWatch Alarmsで監視してSNS経由で通知
- CloudWatch LogsのログをLambdaへストリーミングして、Lambdaで加工してからSNSで通知
- CloudWatchのメトリクスをDatadogなどの3irdParty製ツールを使って定期的に取得し、アラートを設定して通知
などの方法がありますが、今回は「1. CloudWatchの標準メトリクスをCloudWatch Alarmsで監視してからSNS経由で通知」で実装していきたいと思います。
それぞれメリット・デメリットなどはありますが、Lambdaの監視って何すればいいの?って時は、導入コストも少ないので、とりあえずこれを入れとけば良いと思います。
それでは、さっそく始めていきます。
- 参考にしたサイト
- https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/monitoring-functions-metrics.html
- https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html
- https://qiita.com/onooooo/items/f59c69e30dc5b477f9fd
構成図
やり方
AWS SAMでいろいろ用意
まずは、例のごとく以下のコマンドを実行
$ sam --version SAM CLI, version 0.4.0 $ sam init --runtime python3.6
作成されたフォルダの中身はこんな感じ
$ tree -L 2 . ├── deploy.sh ├── hello_world │ ├── app.py │ ├── __init__.py │ └── __pycache__ ├── output.yaml ├── README.md ├── requirements.txt ├── template.yaml └── tests └── unit
テンプレートの編集
必要なリソースの作成などを行っていきます。
ここでの設定のポイントとなるCloudWatch Alarmsについて少し解説します。
下記のテンプレートの設定では、期間(Period)を1分に設定しているの1分毎に1つのデータポイントが存在し、各データポイントでは1分間の合計値がプロットされています。 評価期間(EvaluationPeriods)は1に設定しているので、直近1つ目のデータポイントがしきい値(Threshold)以上であればCloudWatch Alaramsがアラーム状態に遷移します。(しきい値以下になれば、OK状態に戻ります)
つまり、毎分エラーが1回以上発生していればアラーム状態に遷移しSNSでe-mail通知するということです。また欠損データ(TreatMissingData)はしきい値内(notBreaching)として扱うように設定していますので、エラーもなにも発生してない時はOK状態として扱われます。
より詳しい内容はドキュメントをご覧ください。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-cwl-alarms Sample SAM Template for sam-cwl-alarms # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 10 MemorySize: 128 Resources: HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: hello_world Handler: app.lambda_handler Runtime: python3.6 Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object Variables: PARAM1: VALUE Events: HelloWorldFunction: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get ReservedConcurrentExecutions: 1 #同時実行数の上限エラーを検知したいので1に設定 # Lambdaの標準メトリクスErrorsを監視するCloudWatch Alarms HelloWorldErrorAlarm: Type: AWS::CloudWatch::Alarm Properties: AlarmDescription: "Lambda関数のErrorメトリクスを監視して、60秒間のデータポイントの合計値が1以上の場合にSNSをトリガーするアラーム" AlarmActions: - Ref: HelloWorldSNSTopic MetricName: Errors Namespace: AWS/Lambda Statistic: Sum Period: '60' EvaluationPeriods: '1' Threshold: 1 ComparisonOperator: GreaterThanOrEqualToThreshold TreatMissingData: notBreaching Dimensions: - Name: FunctionName Value: !Ref HelloWorldFunction # Lambdaの標準メトリクスThrottlesを監視するCloudWatch Alarms HelloWorldThrottlesAlarm: Type: AWS::CloudWatch::Alarm Properties: AlarmDescription: "Lambda関数のThrottlesメトリクスを監視して、60秒間のデータポイントの合計値が1以上の場合にSNSをトリガーするアラーム" AlarmActions: - Ref: HelloWorldSNSTopic MetricName: Throttles Namespace: AWS/Lambda Statistic: Sum Period: '60' EvaluationPeriods: '1' Threshold: 1 ComparisonOperator: GreaterThanOrEqualToThreshold TreatMissingData: notBreaching Dimensions: - Name: FunctionName Value: !Ref HelloWorldFunction # CloudWatch AlarmsからトリガーされるSNS HelloWorldSNSTopic: Type: AWS::SNS::Topic Properties: Subscription: - Endpoint: your_mail_address@mail.com Protocol: email TopicName: HelloWorldAlertSNS Outputs: HelloWorldFunctionApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" HelloWorldFunction: Description: "Hello World Lambda Function ARN" Value: !GetAtt HelloWorldFunction.Arn HelloWorldFunctionIamRole: Description: "Implicit IAM Role created for Hello World function" Value: !GetAtt HelloWorldFunctionRole.Arn
Lambdaのソースコード編集
今回はメモリ超過やタイムアウトなどのエラーを意図的に発生させるための処理を書いていきます。
from time import sleep import json import logging import boto3 logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): try: # 権限エラーを意図的に引き起こす処理 if(event['queryStringParameters']['permissionDeny']): logger.info('権限エラーになる処理') client = boto3.client('dynamodb') ddb_response = client.list_tables() logger.info(ddb_response) # タイムアウトを意図的に引き起こす処理 if(event['queryStringParameters']['timeOut']): logger.info('タイムアウトになる処理') sleep(30) # メモリ超過を意図的に引き起こす処理 if(event['queryStringParameters']['memoryExceed']): logger.info('メモリ超過になる処理') some_str = ' ' some_str *= 128000000 logger.info(some_str) return { "statusCode": 200, "body": json.dumps({'message': 'hello world'}) } except Exception as e: # キーが存在しない場合などの、例外処理 logger.error(e) raise e
デプロイ
テンプレートを流して、リソースの作成を行っていく
aws s3 mb s3:// aws cloudformation package --template-file template.yaml --output-template-file output.yaml --s3-bucket aws cloudformation deploy --template-file output.yaml --stack-name --capabilities CAPABILITY_IAM
アラート通知されているか確認してみる
それでは、いくつかリクエストを送ってメールに次のようなアラート通知が来ているか確認してみます。
SNSのメールの内容
CloudWatch Alarmsが状態遷移している様子
正常にレスポンスが返ってくる場合
$ curl "https://<domain>/Prod/hello?permissionDeny=&timeOut=&memoryExceed=" {"message": "hello world"}
- メール受信
- 無
- アラームの状態
- OK
PythonのKeyエラーとなる場合
$ curl "https://<domain>/Prod/hello" {"message": "Internal server error"}
- メール受信
- 有
- アラームの状態
- ALARM
権限エラーとなる場合
$ curl "https://<domain>/Prod/hello?permissionDeny=true&timeOut=&memoryExceed=" {"message": "Internal server error"}
- メール受信
- 有
- アラームの状態
- ALARM
タイムアウトとなる場合
$ curl "https://<domain>/Prod/hello?permissionDeny=&timeOut=true&memoryExceed=" {"message": "Internal server error"}
- メール受信
- 有
- アラームの状態
- ALARM
メモリ超過となる場合
$ curl "https://<domain>/Prod/hello?permissionDeny=&timeOut=&memoryExceed=true" {"message": "Internal server error"}
- メール受信
- 有
- アラームの状態
- ALARM
同時実行数の上限となる場合
$ curl "https://<domain>/Prod/hello?permissionDeny=&timeOut=&memoryExceed=" & curl "https://<domain>/Prod/hello?permissionDeny=&timeOut=&memoryExceed=" {"message": "Internal server error"}
- メール受信
- 有
- アラームの状態
- ALARM
まとめ
Lambdaのアラート通知としては最もシンプルなやり方ですが、割とこれで事足りるケースが多いのではないかと思ってます。 SNSの後ろにLambdaを置いて、周辺のログを取得してからSNSで通知などできれば良かったのですが、時間がないので今回はこれぐらいにしておきます。
お疲れ様でした。