LambdaとAPI GatewayのCloudWatchアラームをAWS SAMで定義して監視する! Slack通知もするよ!! 〜CloudFormationの参考にもどうぞ〜
はじめに
サーバーレス開発部の藤井元貴です。
サーバーレスな開発では、サーバの管理をする必要はありませんが、システム運用を行うにあたって、監視からは逃げられません。
そこで、「CloudWatchアラームを定義し、アラーム発生時にSlackに通知する仕組み」を「AWS SAM」で作ってみました。
CloudFormationでCloudWatchアラームを定義する際の参考にもどうぞ!!!
おすすめの方
- AWS SAMでCloudWatchアラームを設定したい
- AWS SAMで1つのLambdaに複数のイベントトリガーを設定したい
- AWS SAMでSwaggerを使ってAPIを定義したい
- CloudFormationでもCloudWatchアラームを設定したい
- LambdaからSlackにPostしたい
環境
項目 | バージョン |
---|---|
macOS | High Sierra 10.13.6 |
AWS CLI | aws-cli/1.16.89 Python/3.6.1 Darwin/17.7.0 botocore/1.12.79 |
AWS SAM CLI | 0.10.0 |
Python | 3.6 |
構成
API GatewayとLambdaを用意し、別のLambdaを非同期実行させます。
非同期実行されたLambdaのリトライ失敗時、DLQによってSNS Topicを発行します。
また、CloudWatchアラームの動作時に、SNS Topicを発行します。
SNS Topicが発行されると起動するLambdaで、Slackに通知します。
通知対象
AWSサービス | 通知対象 | CloudWatchメトリクス名 |
---|---|---|
API Gateway | 5xx系エラー発生時 | 5XXError |
Lambda(API Gatewayの裏側) | エラー発生時 | Errors |
Lambda(非同期実行された側) | エラー発生時 | Errors |
Lambda(非同期実行された側) | リトライ失敗時 | 無し(DLQのため) |
Slack通知用のLambdaは、監視しません。
やってみる
Slackの設定(Incoming Webhook)
下記を参考にIncoming Webhook
の設定を行い、Webhook URL
をメモしておきます。
プロジェクトフォルダの作成
AWS SAMでプロジェクトフォルダを作成します。
sam init --runtime python3.6 --name TestWatchServerless
フォルダ構成
3つのLambdaを作成するため、次のようにしています。(最低限の内容のみ記載)
├── TestWatchServerlessApi.yaml ├── src │ ├── lambda1 │ │ ├── app.py │ │ └── requirements.txt │ ├── lambda2 │ │ ├── app.py │ │ └── requirements.txt │ └── notify_slack │ ├── app.py │ └── requirements.txt └── template.yaml
templateファイル
AWS SAMのtemplate.yaml
は下記です。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: TestWatchServerless Globals: Function: Timeout: 3 Parameters: SlackWebhookUrl: Type: String Default: hoge Resources: # CloudWatchアラーム用のTopic TestWatchServerlessAlarmTopic: Type: AWS::SNS::Topic Properties: TopicName: TestWatchServerlessAlarmTopic DisplayName: TestWatchServerlessAlarmTopic # DLQ用のTopic TestWatchServerlessDLQTopic: Type: AWS::SNS::Topic Properties: TopicName: TestWatchServerlessDLQTopic DisplayName: TestWatchServerlessDLQTopic # API Gatewayの定義 HelloWorldApi: Type: AWS::Serverless::Api Properties: StageName: Prod DefinitionBody: Fn::Transform: Name: AWS::Include Parameters: Location: s3://cm-fujii.genki-sam-test-bucket/TestWatchServerlessApi.yaml # Lambda1の定義(API Gatewayの裏側) HelloWorldFunction1: Type: AWS::Serverless::Function Properties: CodeUri: src/lambda1 Handler: app.lambda_handler Runtime: python3.6 Environment: Variables: TARGET_LAMBDA_FUNCTION: !Ref HelloWorldFunction2 Policies: LambdaInvokePolicy: FunctionName: !Ref HelloWorldFunction2 Events: HelloWorld: Type: Api Properties: Path: /hello Method: get RestApiId: !Ref HelloWorldApi # Lambda2の定義(非同期実行される側) HelloWorldFunction2: Type: AWS::Serverless::Function Properties: CodeUri: src/lambda2 Handler: app.lambda_handler Runtime: python3.6 DeadLetterQueue: Type: SNS TargetArn: !Ref TestWatchServerlessDLQTopic # Lambda3の定義(Slack通知用) NotifySlackFunction: Type: AWS::Serverless::Function Properties: CodeUri: src/notify_slack Handler: app.lambda_handler Runtime: python3.6 Environment: Variables: # このURLはコミット&公開したくないため、デプロイ時にコマンドで設定する SLACK_WEBHOOK_URL: !Ref SlackWebhookUrl Events: SnsAlarmTopic: Type: SNS Properties: Topic: !Ref TestWatchServerlessAlarmTopic SnsDLQTopic: Type: SNS Properties: Topic: !Ref TestWatchServerlessDLQTopic # CloudWatchアラームの定義(Lambda1のErros用) AlarmFunction1Errors: Type: AWS::CloudWatch::Alarm Properties: AlarmName: Lambda1Errors Namespace: AWS/Lambda Dimensions: - Name: Resource Value: !Ref HelloWorldFunction1 - Name: FunctionName Value: !Ref HelloWorldFunction1 MetricName: Errors ComparisonOperator: GreaterThanOrEqualToThreshold # 閾値以上 Period: 60 # 期間[s] EvaluationPeriods: 1 # 閾値を超えた回数 Statistic: Maximum # 最大 Threshold: 1 # 閾値 AlarmActions: - !Ref TestWatchServerlessAlarmTopic # アラーム発生時のアクション # CloudWatchアラームの定義(Lambda2のErros用) AlarmFunction2Errors: Type: AWS::CloudWatch::Alarm Properties: AlarmName: Lambda2Errors Namespace: AWS/Lambda Dimensions: - Name: Resource Value: !Ref HelloWorldFunction2 - Name: FunctionName Value: !Ref HelloWorldFunction2 MetricName: Errors ComparisonOperator: GreaterThanOrEqualToThreshold # 閾値以上 Period: 60 # 期間[s] EvaluationPeriods: 1 # 閾値を超えた回数 Statistic: Maximum # 最大 Threshold: 1 # 閾値 AlarmActions: - !Ref TestWatchServerlessAlarmTopic # アラーム発生時のアクション # CloudWatchアラームの定義(API Gatewayの5XXError用) AlarmApi5XXError: Type: AWS::CloudWatch::Alarm Properties: AlarmName: Api5XXError Namespace: AWS/ApiGateway Dimensions: - Name: ApiName Value: Test Watch Serverless API # Swaggerファイルの title に合わせる - Name: Stage Value: Prod MetricName: 5XXError ComparisonOperator: GreaterThanOrEqualToThreshold # 閾値以上 Period: 60 # 期間[s] EvaluationPeriods: 1 # 閾値を超えた回数 Statistic: Maximum # 最大 Threshold: 1 # 閾値 AlarmActions: - !Ref TestWatchServerlessAlarmTopic # アラーム発生時のアクション Outputs: HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${HelloWorldApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
Swaggerファイル
API Gatewayを定義するためのSwaggerファイルは下記です。
swagger: "2.0" info: description: "SwaggerとAPI Gatewayのサンプルです。" version: "1.0.0" title: "Test Watch Serverless API" basePath: "/Prod" schemes: - "https" paths: /hello: get: tags: - "Hello" summary: "This is summary" description: "This is description" consumes: - "application/json" produces: - "application/json" responses: 200: description: "successful operation" schema: $ref: "#/definitions/Hello" x-amazon-apigateway-integration: uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction1.Arn}/invocations passthroughBehavior: when_no_templates httpMethod: POST type: aws_proxy definitions: Hello: type: "object" required: - "message" properties: message: type: "string"
Lambda関数の作成
3つのLambdaを作成します。
Lambda1(API Gatewayの裏側)
別のLambdaを非同期実行させたあと、例外発生でエラーにします。
import os import json import boto3 def lambda_handler(event, context): client = boto3.client('lambda') param = { 'hoge': 'fuga' } # 別のLambdaを非同期実行する client.invoke( FunctionName=os.getenv('TARGET_LAMBDA_FUNCTION'), InvocationType='Event', Payload=json.dumps(param) ) # 強制的にエラー発生させる raise NotImplementedError() return { "statusCode": 200, "body": json.dumps({ "message": "hello world", }), }
Lambda2(非同期実行される側)
例外発生でエラーにします。
def lambda_handler(event, context): print('lambda2') # 強制的にエラー発生させる raise NotImplementedError()
notify_slack(Slack通知用)
Lambda実行時に受け取ったパラメータをそのままSlackに投げます。
ここで見やすいように加工すると親切ですね(今回はやらない)。
import os import json import requests def lambda_handler(event, context): # https://api.slack.com/incoming-webhooks # https://api.slack.com/docs/message-formatting # https://api.slack.com/docs/messages/builder payload = { 'text': f'```{json.dumps(event, indent=2)}```' } # http://requests-docs-ja.readthedocs.io/en/latest/user/quickstart/ try: response = requests.post(os.getenv('SLACK_WEBHOOK_URL'), data=json.dumps(payload)) except requests.exceptions.RequestException as e: print(e) else: print(response.status_code)
このLambda関数のコード(app.py)と同じ場所にあるrequirements.txt
には下記を記載します。
requests==2.20.0
S3バケットの作成
コード等を格納するためのS3バケットを作成します。作成済みの場合は飛ばします。
aws s3 mb s3://cm-fujii.genki-sam-test-bucket
Swaggerファイルを格納
SwaggerファイルをS3バケットに格納します。
aws s3 cp TestWatchServerlessApi.yaml s3://cm-fujii.genki-sam-test-bucket/TestWatchServerlessApi.yaml
build
下記コマンドでビルドします。
sam build
package
続いてコード一式をS3バケットにアップロードします。
sam package \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-sam-test-bucket
deploy
最後にデプロイします。template.yaml
の環境変数をオーバーライドし、ここでSlackのWebhook URL
を設定します。
sam deploy \ --template-file packaged.yaml \ --stack-name TestWatchServerless \ --capabilities CAPABILITY_IAM \ --parameter-overrides SlackWebhookUrl=https://hooks.slack.com/services/xxxxxxxxxxxxx
CloudWatchアラームの様子
無事に作成できました。
動作確認
WebAPIのURLを取得
次のコマンドでWebAPIのURLを取得します。
$ aws cloudformation describe-stacks --stack-name TestWatchServerless --query 'Stacks[].Outputs' [ [ { "OutputKey": "HelloWorldApi", "OutputValue": "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/", "Description": "API Gateway endpoint URL for Prod stage for Hello World function" } ] ]
いざ!!!
取得したURLにGETリクエストを行います!
$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"message": "Internal server error"}
目論見通り、Internal server error
が返ってきました。
CloudWatchアラームの様子(動作確認中)
1分ほど経過後、状態が「アラーム」になりました!!
Slack通知の様子
無事に4件の通知が来ました!
(画像モザイクだらけですが……)
簡単に中身を見ます。(一部抜粋)
1件目
Lambda1(API Gatewayの裏側)のエラー通知です。
"Sns": { "Type": "Notification", "TopicArn": "arn:aws:sns:ap-northeast-1:1234567890:TestWatchServerlessAlarmTopic", "Subject": "ALARM: \"Lambda1Errors\" in Asia Pacific (Tokyo)", "Message": "略", "Timestamp": "2019-04-12T06:24:03.121Z", }
2件目
API Gatewayのエラー通知です。
"Sns": { "Type": "Notification", "TopicArn": "arn:aws:sns:ap-northeast-1:1234567890:TestWatchServerlessAlarmTopic", "Subject": "ALARM: \"Api5XXError\" in Asia Pacific (Tokyo)", "Message": "略", "Timestamp": "2019-04-12T06:25:19.535Z", }
3件目
Lambda2(非同期実行される側)のエラー通知です。
"Sns": { "Type": "Notification", "TopicArn": "arn:aws:sns:ap-northeast-1:1234567890:TestWatchServerlessAlarmTopic", "Subject": "ALARM: \"Lambda2Errors\" in Asia Pacific (Tokyo)", "Message": "略", "Timestamp": "2019-04-12T06:25:31.352Z", }
4件目
Lambda2(非同期実行される側)のリトライ失敗の通知(DLQ)です。
"Sns": { "Type": "Notification", "TopicArn": "arn:aws:sns:ap-northeast-1:1234567890:TestWatchServerlessDLQTopic", "Subject": null, "Message": "{\"hoge\": \"fuga\"}", "Timestamp": "2019-04-12T06:26:38.221Z", "SignatureVersion": "1", }
最後に
監視対象は何にするのか? CloudWatchアラームの設定値はどうするのか? など、考えるべき内容はたくさんです。
下記などを参考にしていきたいです。