AWS Serverless Application Modelを利用してAWS Health Toolsを使ってみた

ServerlessConf

はじめに

こんにちは、中山です。

米国時間03/06、AWSからAWS Health Tools発表されました。このリポジトリは、AWS Healthに発生したイベントに応じてCloudWatch Eventsで定義したルールを呼び出し、ターゲットとなるLambda関数を起動させるサンプルを集めたものです。サンプルには主にLambda関数のコードとCloudFromationのテンプレートが含まれています。GitHubで管理されているため、世界中の開発者によりコミュニティベースで開発可能なリポジトリになっています。早速使ってみたので本エントリにまとめたいと思います。ただし、そのまま使うのもあまりおもしろくないので、今回はAWS Serverless Application Model(以下AWS SAM)から使ってみます。

なお、本エントリを執筆する上で検証に利用した主要な各種ツールのバージョンは以下の通りです。バージョンによって結果が変更される可能性があるので、その点ご了承ください。

  • AWS SAM: 2016-10-31
  • AWS CLI: aws-cli/1.11.57 Python/2.7.12 Darwin/16.4.0 botocore/1.5.20

概要

上述のようにAWS Healthで発生したイベントをCloudWatch Eventsに通知することが可能です。CloudWatch Eventsは事前に定義しておいたルールに応じてターゲットを実行させることができます。この機能に関するドキュメントはこちらです。執筆時点(2017/03/07)で以下のターゲットがサポートされています。

  • AWS Lambda functions
  • Amazon Kinesis streams
  • Amazon SQS queues
  • Built-in targets (CloudWatch alarm actions)
  • Amazon SNS topics

AWS Healthは基本的に障害情報を収集するためのサービスです。そのためこの機能の主なユースケースは、ある障害に応じてそれを通知/緩和/復旧させるためのアクションを自動化させるという点です。上記ドキュメントにユースケースが記載されているので以下に引用します。

Use a Lambda function to pass a notification to a Slack channel when an event occurs.

Send custom text or SMS notifications via Amazon SNS when an AWS Health event happens by using Lambda and CloudWatch Events.

特にLambda関数をターゲットに指定できるという点が大きいですね。各種言語用AWS SDKを使えば基本的に何でもできるので、自分の環境に合ったアクションを定義できます。更に、今回発表されたAWS Health Toolsを利用することで、他の開発者の方が作成したLambda関数を利用できるという点も便利なポイントだと思います。

使ってみる

それでは早速使ってみましょう。今回はAWS Health Toolsのサンプルの中から、ELBがスケールした場合に必要となるENIが枯渇した場合( AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED イベント)、利用していないENIを削除するサンプルをAWS SAMで定義してみます。ディレクトリ構成はi以下のようにしました。 LambdaFunction.jsこちらのソースコードを所定のディレクトリに配置してください。

$ tree
.
├── sam.yml
└── src
    └── handlers
        └── handler
            └── LambdaFunction.js

3 directories, 2 files

AWS SAMのテンプレートである sam.yml は以下のようにしました。

---
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Automatically delete unused ENIs that are blocking ELB scaling using Amazon Cloudwatch events and AWS Lambda

Parameters:
  DryRun:
    Type: String
    Description: Set to true to test function without actually deleting ENIs
    Default: true
    AllowedValues: [ true, false ]
  MaxENI:
    Description: Number of ENIs to process. Set to 0 to do all the function finds (this may result in account throttling)
    Type: Number
    Default: 100

Metadata:
  LICENSE: https://github.com/aws/aws-health-tools
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: General Configuration
        Parameters:
          - DryRun
          - MaxENI
    ParameterLabels:
      DryRun:
        default: Dry Run
      MaxENI:
        default: Maximum ENI to process

Resources:
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Description: Delete unused ENIs in response to AWS health events
      CodeUri: src/handlers/handler
      Handler: LambdaFunction.handler
      Runtime: nodejs4.3
      Timeout: 120
      Policies:
        - Version: 2012-10-17
          Statement:
            - Sid: ENI
              Effect: Allow
              Action:
                - ec2:DescribeNetworkInterfaces
                - ec2:DeleteNetworkInterface
              Resource: "*"
      Environment:
        Variables:
          DRY_RUN: !Ref DryRun
          MAX_ENI: !Ref MaxENI
      Events:
        AWSHealth:
          Type: CloudWatchEvent
          Properties:
            Pattern:
              source: [ aws.health ]
              detail-type: [ "AWS Health Event" ]
              detail:
                service: [ ELASTICLOADBALANCING ]
                eventTypeCategory: [ issue ]
                eventTypeCode: [ AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED ]

テンプレートの中で、今回の場合特有の設定を以下に補足します。

  • 41 - 49行
    • Lambda関数にアタッチするIAM Roleの権限を Policies プロパティで定義しています
    • ENIの表示/削除を実施しているため、それに応じた権限を付与しています
    • この他にも arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole が自動で付与されます
  • 54 - 64行
    • Events プロパティの TypeCloudWatchEvent にすることで、Lambda関数のイベントソースを定義しています
    • Pattern 以下に指定しているオブジェクトの詳細はこちらのドキュメントにまとまっています
    • 今回AWS Healthをソースとするため、 sourceaws.health を指定しています

ちなみに、AWS SAMを使わずにCloudFromationだけでもAWS::Events::Ruleを利用すればCloudWatch Eventsは定義可能です。先程のリポジトリに(JSONですが)テンプレートがあるので、該当の部分を引用します。

        "CloudWatchEventRule": {
            "Type": "AWS::Events::Rule",
            "Properties": {
                "Description": "AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED",
                "EventPattern": {
                    "source": [
                        "aws.health"
                    ],
                    "detail-type": [
                        "AWS Health Event"
                    ],
                    "detail": {
                        "service": [
                            "ELASTICLOADBALANCING"
                        ],
                        "eventTypeCategory": [
                            "issue"
                        ],
                        "eventTypeCode": [
                            "AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED"
                        ]
                    }
                },
                "State": "ENABLED",
                "Targets": [
                    {
                        "Arn": {
                            "Fn::GetAtt": [
                                "LambdaFunction",
                                "Arn"
                            ]
                        },
                        "Id": "InsufficientENIsFunction"
                    }
                ]
}

JSONで記述されているということもあるのですが、やはりAWS SAMの Events プロパティで定義した方がよりシンプルに記述できるかと思います。AWS SAMのデプロイはいつものようにAWS CLIを使うだけです。

$ aws cloudformation package \
  --template-file sam.yml \
  --s3-bucket <_YOUR_S3_BUCKET_> \
  --output-template-file .sam/packaged.yml
$ aws cloudformation deploy \
  --template-file .sam/packaged.yml \
  --stack-name <_YOUR_STACK_NAME_> \
  --capabilities CAPABILITY_IAM

デプロイ後、CloudWatch Eventsの設定を見るとAWS Healthをソースとしたルールが定義されていることを確認できます。

$ aws events describe-rule \
  --name "$(aws events list-rules \
    --query 'Rules[?contains(Name, `aws-health-tools`)].Name' \
    --output text)"
{
    "EventPattern": "{\"detail-type\":[\"AWS Health Event\"],\"source\":[\"aws.health\"],\"detail\":{\"eventTypeCode\":[\"AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED\"],\"service\":[\"ELASTICLOADBALANCING\"],\"eventTypeCategory\":[\"issue\"]}}",
    "State": "ENABLED",
    "Name": "aws-health-tools-sample-de-LambdaFunctionAWSHealth-1BR2PT9E6MGW3",
    "Arn": "arn:aws:events:ap-northeast-1:************:rule/aws-health-tools-sample-de-LambdaFunctionAWSHealth-1BR2PT9E6MGW3"
}

Lambda関数の設定を見ると、CloudWatch EventsにLambda関数をInvokeできる権限が設定されていることを確認できます。

$ aws lambda get-policy \
  --function-name "$(aws lambda list-functions \
    --query 'Functions[?contains(FunctionName, `aws-health-tools`)].FunctionName' \
    --output text)" \
  --output text | jq .
{
  "Version": "2012-10-17",
  "Id": "default",
  "Statement": [
    {
      "Sid": "aws-health-tools-sample-dev-LambdaFunctionAWSHealthPermission-L00FLZXLXL8S",
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "lambda:invokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:************:function:aws-health-tools-sample-dev-LambdaFunction-1IYBNJYYIJD1J",
      "Condition": {
        "ArnLike": {
          "AWS:SourceArn": "arn:aws:events:ap-northeast-1:************:rule/aws-health-tools-sample-de-LambdaFunctionAWSHealth-1BR2PT9E6MGW3"
        }
      }
    }
  ]
}

まとめ

いかがだったでしょうか。

AWS SAMでAWS Health Toolsを利用する方法をご紹介しました。AWS HealthとCloudWatch Events及びLambda関数の組み合わせは障害復旧方法として非常に便利な組み合わせだと思います。積極的に使っていきたいですね。ただ、AWS Healthは基本的にAWSに起因する障害を通知するものなので、AWSユーザ自身が検証目的で意図的に障害をエミュレートするのは現状難しいです。障害が起きた時に本当に意図した動作をするのか事前に検証しておきたいので、そういった機能があると嬉しいなと思いました。

本エントリがみなさんの参考になれば幸いに思います。