この記事は公開されてから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状態として扱われます。
より詳しい内容はドキュメントをご覧ください。
template.yaml
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のソースコード編集
今回はメモリ超過やタイムアウトなどのエラーを意図的に発生させるための処理を書いていきます。
app.py
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で通知などできれば良かったのですが、時間がないので今回はこれぐらいにしておきます。
お疲れ様でした。