EventBridgeとLambdaを連携させるServerless Frameworkの設定方法

2019.12.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも!大阪オフィスの西村祐二です。

この記事はAWS LambdaとServerless #1 Advent Calendar 2019の14日目 の記事です。

個人的に今後良く利用しそうだなと思っているサービスの一つであるEventBridgeとLambdaを連携させるためのServerless Frameworkの設定方法をまとめていきたいと思います。

EventBridgeはLambdaを挟まなくても良い場面が多そうではありますが、やっぱり要件満たせないからLambdaを挟むこともたぶん発生すると思うので、そういった場面で即座に対応できるようにしておきたいと思ったのが執筆の動機です。

また、EventBridgeの挙動の確認や、サービス理解の勉強にもお手軽でちょうど良いかなとも思っています。

Serverless Frameworkを使うのは、個人的な好みです。AWS SAMなど他のツールでの設定方法も別の機会にまとめてみたいと思います。

Amazon EventBridgeとは

Amazon EventBridge は、独自のアプリケーション、SaaS (Software-as-a-Service) アプリケーション、AWS のサービスからのデータを使用してアプリケーションどうしを簡単に接続することを可能にするサーバーレスイベントバスです。EventBridge は、Zendesk、Datadog、Pagerduty などのイベントソースからリアルタイムデータのストリームを配信し、そのデータを AWS Lambda などのターゲットにルーティングします。お客様は、データの送信先を判断するためのルーティングルールを設定して、すべてのデータソースにリアルタイムで反応するアプリケーションアーキテクチャを構築できます。EventBridge では、イベントの取り込みと配信、セキュリティ、承認、エラー処理が自動的に行われるため、イベントドリブンアーキテクチャを簡単に構築することができます。

詳細は下記のサービスページをご参照ください。

https://aws.amazon.com/jp/eventbridge/

EventBridgeの概要図をみるとイメージつきやすいかと思います。イベントソースには、AWSの各サービス、SaaSアプリ、独自アプリなどを用いることができ、Ruleによってイベントのフィルタリングをした上でAWS Lambda、Amazon Kinesis Data Firehose、Amazon SNS、またはそれ以外のターゲットに流すことができます。

サービスサイトより引用

また、EventBridgeはCloudWatch Eventsと同じことができるのですが、内部ではCloudWatch Eventsと同じサービスAPIとエンドポイント、同じ基盤となるサービスインフラストラクチャを使用しており、将来的に、Amazon CloudWatch Eventsの名称は Amazon EventBridgeに置き換えられる予定であるとのことです。

https://aws.amazon.com/jp/eventbridge/faqs/

環境

Serverless Framework:1.59.3

Node.JS:12.13.0

試してみる

事前準備

Serverless Frameworkを使ってプロジェクトを作っておきます。

$ mkdir eventbrige-demo
$ cd eventbrige-demo
$ sls create -t aws-nodejs -n eventbrige-demo

eventをログ出力するように設定します。

handler.js

'use strict';

module.exports.hello = async event => {
  console.log(event);
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'Go Serverless v1.0! Your function executed successfully!',
        input: event
      },
      null,
      2
    )
  };

  // Use this code if you don't use the http event with the LAMBDA-PROXY integration
  // return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};

私はいつも東京リージョンをつかっているので、リージョン情報を追加しておきます。

serverless.yml

service: eventbrige-demo

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello

既存のイベントバスにルールを作成する

EventBridgeの既存のイベントバスとは下記のようにはじめから用意されているdefaultというバスを指します。これに対してルールを作成していきます。

EventBridgeからLambdaを定期実行

EventBridgeはCloudWatch Eventsと同じように一定期間ごとにイベント実行することができますので、その設定を行います。

下記設定では10分に一回Lambdaが実行され、{key1: value1}がペイロードとして渡されてるという設定です。

serverless.yml

service: eventbrige-demo

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello
    events:
      - eventBridge:
          schedule: rate(10 minutes)
          input:
            key1: value1

下記コマンドでデプロイします。

$ sls deploy

デプロイが完了したらマネコンから確認してみましょう。

「eventbrige-demo-dev-hello-rule-1」というルールが作成されていました。

LambdaのトリガーのところにはEventBridgeではなく、「CloudWatch Events」が設定されています。これは内部でCloudWatch Eventsと同じサービスAPIとエンドポイントを使用しているからだと思われます。

なぜか2つのLambdaが作られている

Lambdaを確認すると、知らないLambda「eventbrige-demo-dev-custom-resource-event-bridge」ができていました。

これはServerless Frameworkの内部でカスタムリソースをつかってEventBridgeを構築しているからのようです。

CloudFormationはEventBridgeのリソースに対応しているので、今後カスタムリソースを使った構築方法は変更されるかもしれませんね。

https://aws.amazon.com/jp/about-aws/whats-new/2019/10/amazon-eventbridge-supports-aws-cloudformation/

イベントパターンマッチング設定

あるパターンにマッチしたイベントが実行されたらLambdaを起動するようなこともできます。

下記ではcloudformationが実行されるとLambdaが実行されるというようなパターンを記載しています。

serverless.yml

service: eventbrige-demo

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello
    events:
      - eventBridge:
          pattern:
            source:
              - aws.cloudformation
            detail-type:
              - AWS API Call via CloudTrail
            detail:
              eventSource:
                - cloudformation.amazonaws.com

仕組みはsourceで対象のソース(今回はAWSサービスのCloudFormation)を指定し、detail-typeの値をAWS API Call via CloudTrailにすることでCloudTrailによってキャプチャされた情報でトリガーする設定ができます。

マネコンで設定する際は下記の画像と同じことを設定しています。

どのようにパターンを記載するかはマネコンから確認するかこちらのドキュメントから確認する方法しかなさそうです。未確認ですがAWS CDKなら、エディタ補完が効くのでパターンの記載は楽そうだなと思います。

上記設定をしたときLambdaでは下記のようなログが出力されます。特にCloudFormation側の設定などはいらないので簡単にイベントドリブルな仕組みを作ることができます。

{
  version: '0',
  id: 'xxxxxxxxxxxxxxxxxxx',
  'detail-type': 'AWS API Call via CloudTrail',
  source: 'aws.cloudformation',
  account: 'xxxxxxxxxxxxxxxxxxx',
  time: '2019-12-12T14:08:01Z',
  region: 'ap-northeast-1',
  resources: [],
  detail: {
    eventVersion: '1.05',
    userIdentity: {
      type: 'AssumedRole',
      principalId: 'xxxxxxxxxxxxxxxxxxx',
      arn: 'arn:aws:sts::xxxxxxxxxxxxxxxxxxx:assumed-role/xxxxxxxxxxxxxxxxxxx',
      accountId: 'xxxxxxxxxxxxxxxxxxx',
      accessKeyId: 'xxxxxxxxxxxxxxxxxxx',
      sessionContext: [Object]
    },
    eventTime: '2019-12-12T14:08:01Z',
    eventSource: 'cloudformation.amazonaws.com',
    eventName: 'UpdateStack',
    awsRegion: 'ap-northeast-1',
    sourceIPAddress: 'xxxxxxxxxxxxxxxxxxx',
    userAgent: 'aws-sdk-nodejs/2.588.0 darwin/v12.13.0 promise',
    requestParameters: {
      templateURL: 'https://s3.amazonaws.com/eventbrige-demo-dev-serverlessdeploymentbucket-1s2yxjjdltahv/serverless/eventbrige-demo/dev/xxxxxxxxxxxxxxxxxxx-2019-12-12T14:08:02.967Z/compiled-cloudformation-template.json',
      parameters: [],
      stackName: 'eventbrige-demo-dev',
      capabilities: [Array]
    },
    responseElements: {
      stackId: 'arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxxxxxxxxx:stack/eventbrige-demo-dev/xxxxxxxxxxxxxxxxxxx'
    },
    requestID: 'xxxxxxxxxxxxxxxxxxx',
    eventID: 'xxxxxxxxxxxxxxxxxxx',
    eventType: 'AwsApiCall'
  }
}

独自イベントバスを作成する

既存のバスのdefaultではなく独自のバスを作成する場合は、eventBusというプロパティを設定し、バス名を指定することで独自のイベントバスを作成して、ルールを設定してくれます。

serverless.yml

service: eventbrige-demo

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello
    events:
      - eventBridge:
          eventBus: custom-saas-events
          pattern:
            source:
              - saas.external

既存のイベントバスを使用する

既存のイベントバスを使用する場合はeventBusにARNを設定する必要があります。アカウントIDはプラグインを使うか外部のJSファイルから取得する方法が良いでしょう。

serverless.yml

・・・
custom:
  accountId:
    ${file(./account.js):getAccountId}
 ・・・
      
      - eventBridge:
          eventBus: arn:aws:events:${self:provider.region}:${self:custom.accountId}:event-bus/custom-private-events
          pattern:
            source:
              - "aws.ec2"
            detail-type:
              - "EC2 Instance State-change Notification"
            detail:
              state:
                - pending
          inputTransformer:
            inputPathsMap:
              eventTime: "$.time"
            inputTemplate: '{"time": <eventTime>, "key1": "value1"}'

account.js

const { STS } = require('aws-sdk');
const sts = new STS();

module.exports.getAccountId = async () => {
  // Checking AWS user details
  const { Account } = await sts.getCallerIdentity().promise();
  return Account;
};

さいごに

Serverless FrameworkでEventBridgeとLambdaを連携させる設定をみていきました。

数行の設定で簡単にEventBridgeとLambdaを連携させることができてとても楽チンだと思います。

ただ、まだプレビューですがスキーマレジストリなどの設定はできないようなので今後のアップデートに期待したいと思います。

このブログの作成を通じてEventBridgeの挙動や設定方法、またServerless Frameworkが内部でカスタムリソースを使っているなどの挙動の理解などいろいろ勉強になりました。

興味のある方は試してみてはいかがでしょうか。

機会があれば、AWS SAMなど他のツールでの設定方法や挙動などまとめてみたいと思います。

誰かの参考になれば幸いです。