
Lambdaが異常終了した際にアラート通知させる仕組みをAWS SAMのテンプレートで一括デプロイ
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
お疲れ様です。サーバーレス開発部の新井です。
本日は、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で通知などできれば良かったのですが、時間がないので今回はこれぐらいにしておきます。
お疲れ様でした。














