Help us understand the problem. What is going on with this article?

CloudWatchのアラームをLambdaでチャットワークに通知 w/ Serverless

More than 3 years have passed since last update.

AWS Lambda & Serverless Frameworkの簡単なお試し。

CloudWatchのアラームの通知先をSNSトピックに設定し、それをLambdaのトリガーにしてChatworkに通知する、というのをやってみる。

Serverless - The Serverless Application Framework powered by AWS Lambda and API Gateway

環境

  • serverless 1.3.0

準備

作業用ディレクトリの作成と、ライブラリのインストールなど。

$ mkdir hello-serverless
$ cd hello-serverless
$ npm init
$ npm install -D serverless

package.jsonに、Serverlessのプロジェクト(テンプレート)作成用のnpm runタスクを追加。

package.json
{
  "scripts": {
    "create-template": "sls create --template"
  },
  ...
}

Lambdaで動かす言語として、今回はNode (v4.3) を選択。

$ npm run create-template -- aws-nodejs

> hello-serverless@1.0.0 create-template /Users/Aono/works/hello-serverless
> sls create --template aws-nodejs

Serverless: Generating boilerplate...
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.3.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name

作業ディレクトリを確認してみると、handler.js, serverless.ymlが作成されている。前者が実際にLambdaファンクションとして動くロジックを書くファイル、後者がServerless Frameworkの動作に関わる設定ファイルとなる。

↓デフォルトで作成されるhandler.js

handler.js
'use strict';

module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: event,
    }),
  };

  callback(null, response);

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

Lambdaファンクションのテストデプロイ

とりあえず上記のデフォルト関数をAWS側にデプロイ(sls deploy)してみる。

serverless.ymlserviceブロックの値を適当なものに変更。また、AWSの東京リージョンを使うので、providerブロックのregionを追加。(デフォルトだと us-east-1になってしまうらしい)

serverless.yml
service: hogehoge-functions # NOTE: update this with your service name

provider:
  name: aws
  runtime: nodejs4.3
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello

handler.jsにてexportしたhello関数をデプロイする、というのがなんとなく見て分かる。

続いて、package.jsonにデプロイ用のタスクも追加。ここから、Serverlessの操作にはAWSのAPIキーが必要となる。Administrator Access権限を与えたIAMユーザを作成し、APIキーとシークレットアクセスキーを入手。configキーに設定しておく。

※今回はテストなので無視したが、フルアクセスのIAMユーザを使うのはキケンなので、権限の精査が必要になりそう。

参考: Serverless Framework利用時のアカウント毎のIAM権限について考えてみた - Qiita

package.json
{
  "config": {
    "aws_access_key_id": "XXXXX",
    "aws_secret_access_key": "XXXXXXXXXXXXXXX"
  },
  "scripts": {
    "deploy-lambda": "cross-env AWS_ACCESS_KEY_ID=$npm_package_config_aws_access_key_id AWS_SECRET_ACCESS_KEY=$npm_package_config_aws_secret_access_key sls deploy",
  },
  "devDependencies": {
    "cross-env": "^3.1.3",
    "serverless": "^1.3.0"
  }
}

タスクの中で環境変数を指定するので、cross-envを利用している。

ようやくデプロイの実行。

$ npm run deploy-lambda -- -f hello 

> hello-serverless@1.0.0 deploy-lambda /Users/Aono/works/hello-serverless
> cross-env AWS_ACCESS_KEY_ID=$npm_package_config_aws_access_key_id AWS_SECRET_ACCESS_KEY=$npm_package_config_aws_secret_access_key sls deploy "-f" "dispatcher"

Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (13.07 MB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............
Serverless: Stack update finished...

Service Information
service: hogehoge-functions
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  None
functions:
  hogehoge-functions-dev-hello: arn:aws:lambda:ap-northeast-1:xxxxxx:function:hogehoge-functions-dev-hello

これで完了。AWSのコンソールを見ると、"hogehoge-functions-dev-helloLambda"という名前のファンクションが登録されている。(※今回は触れないが、Serverlessでは本番環境用とテスト環境用を分けてデプロイすることができる。テスト用のdevがデフォルト)

また、ログを見ると、ServerlessがCloudFormationやS3を利用していることが分かる。こちらもAWSのコンソールから確認が可能。
(作業ディレクトリの./serverless以下に設定に利用したjsonができてたりもする)

テスト実行

Serverlessを利用することで、ローカル環境からLambdaをテスト実行(sls invoke)することもできる。package.jsonにさらにタスクを追加。

package.json
{
  "scripts": {
    "invoke-lambda": "cross-env AWS_ACCESS_KEY_ID=$npm_package_config_aws_access_key_id AWS_SECRET_ACCESS_KEY=$npm_package_config_aws_secret_access_key sls invoke"
  }
}

実行してみると、hello関数が動いてるのが分かる。

$ npm run invoke-lambda -- -f hello

> hello-serverless@1.0.0 invoke-lambda /Users/Aono/works/hello-serverless
> cross-env AWS_ACCESS_KEY_ID=$npm_package_config_aws_access_key_id AWS_SECRET_ACCESS_KEY=$npm_package_config_aws_secret_access_key sls invoke "-f" "hello"

{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}"
}

ログの確認

関数実行時のログもローカルから確認できる(sls logs)。

package.json
{
  "scripts": {
    "logs-lambda": "cross-env AWS_ACCESS_KEY_ID=$npm_package_config_aws_access_key_id AWS_SECRET_ACCESS_KEY=$npm_package_config_aws_secret_access_key sls logs"
  }
}
$ npm run logs-lambda -- -f hello

> hello-serverless@1.0.0 logs-lambda /Users/Aono/works/hello-serverless
> cross-env AWS_ACCESS_KEY_ID=$npm_package_config_aws_access_key_id AWS_SECRET_ACCESS_KEY=$npm_package_config_aws_secret_access_key sls logs "-f" "hello"

START RequestId: xxxxxxxxxxxxxxxxx Version: $LATEST
END RequestId: xxxxxxxxxxxxxxxxx
REPORT RequestId: xxxxxxxxxxxxxxxxx Duration: 2.22 ms   Billed Duration: 100 ms     Memory Size: 1024 MB    Max Memory Used: 7 MB

コード内のconsole.logなどで出力した内容を見れたりするので、デバッグ時には欠かせないかな。

SNSと連携

本題。

Serverless Frameworkでは、SNSトピックの購読も提供されている
https://serverless.com/framework/docs/providers/aws/events/sns/

serverless.ymlを以下のように設定すれば、SNSトピックをLambdaのトリガーにした状態でデプロイできる。このデプロイによって、"lambda-test"という名前のSNSトピックも同時に新規作成。

serverless.yml
functions:
  dispatcher:
    handler: dispatcher.dispatch
    events:
      - sns: lambda-test

さっきのhandler.jsをコピって、適当にdispatcher.jsとexportする関数dispatchを作成。

dispatcher.js
'use strict';

module.exports.dispatch = (event, context, callback) => {
  // 中身はhandler.jsといっしょ
};

SNSトピックが作成され、新しくデプロイされたLambdaファンクションのトリガーになっていることがコンソールを見ると分かる。

スクリーンショット 2016-12-18 17.00.31.png

このSNSトピックをCloudWatchのアラームと連携させる。試しに、とあるテスト環境のELBのHealthyHostCountに関するアラームの通知先に設定。

ロードバランサー下には1つのEC2が配置してある。正常なホスト数の合計が1を下回ったら警告を通知するようにする。わざとEC2で動いているアプリケーションサーバをストップすると、CloudWatchから通知を受けたSNSを通して、Lambdaが起動する。(LambdaのコンソールのMonitoringなどから確認できる)

チャットワークに通知

起動トリガーとなっているSNSの通知内容(イベント)を、Lambdaを使ってチャットワークに通知する。もうイベントのsubscription自体はできているので、あとはそれを元にファンクションの中身を書く。先ほどのdispatcher.jsを変更。

(Lambdaはイベント駆動であるがゆえに、ロジックを書きながらデバッグしていくのが面倒。何か良い方法あるのだろうか。。)

dispatcher.js
'use strict';

const https = require('https');
const querystring = require('querystring');

module.exports.dispatch = (event, context) => {
  const record = event.Records[0];

  if (record.Sns) {
    const message = JSON.parse(record.Sns.Message);
    const alarmStatus = message.NewStateValue;
    const postMessge = alarmStatus +
      ": " +
      message.Trigger.Namespace +
      " " +
      message.Trigger.MetricName +
      "\n" +
      message.NewStateReason;

    const postData = querystring.stringify({
      body: postMessge
    });
    const requestOptions = {
      host: 'api.chatwork.com',
      port: 443,
      method: 'POST',
      path: '/v1/rooms/xxxxxxxx/messages',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': postData.length,
        'X-ChatWorkToken': 'xxxxxxxxxxxxxxxx'
      }
    };

    const req = https.request(requestOptions, res => {
      if (res.statusCode === 200) {
        context.succeed(res);
      } else {
        context.fail(res.statusCode);
      }
    });

    req.on('error', e => {
      console.log("RequestError: " + e.message);
      context.fail(e.message);
    });

    req.write(postData);
    req.end();
  }
};

SNSから受けたイベントをパースしてメッセージ分を作成し、チャットワークのAPIにPOST。ちなみに、event.Recordsの中身はこんなんが来る。(内容は省略してます)

{ EventSource: 'aws:sns',
  EventVersion: '1.0',
  EventSubscriptionArn: 'arn:aws:sns:ap-northeast-1:xxxxxxxxxx,
  Sns:
   { Type: 'Notification',
     MessageId: 'xxxxx',
     TopicArn: 'arn:aws:sns:ap-northeast-1:xxxxx',
     Subject: 'OK: "lambda-test" in Asia Pacific - Tokyo',
     Message: <通知内容データ>,
     Timestamp: '2016-12-18T05:41:20.824Z',
     ... }
}

これでデプロイし直せば設定おわり。CloudWatchのアラームが来るタイミングで、以下のようにチャットワーク上で通知が来る。

スクリーンショット 2016-12-18 15.42.54.png

おわり

簡単ではあるが、いったんお試し終わり。

Serverless Frameworkを利用することで、Lambdaファンクションの簡単なデプロイ & AWS各種サービス(今回のSNSだけでなく、API GatewayやS3 etc.)との連携が簡単に実行できるのが分かった。イベント連携などを統合してやってくれるのはありがたい。
また、Serverlessプロジェクトのファイル群をGitで管理すれば、Lambdaファンクションなどの変更履歴を管理することが可能になる。これもフレームワークの恩恵ですね。

Lambda周りは、AWSがre:Inventでもめちゃくちゃ力入れてる感を出してたので、ちゃんと学んでギョームで作ってるシステムのアーキテクチャにも取り入れていきたいところ。

ysk_1031
Software Engineer at Atrae, Inc. iOS, Android, Webの開発など色々やっています. 最近はyentaというアプリを作ったりしてます
atrae
People Techカンパニーとして、転職サイトGreen, ビジネスマッチングアプリyenta, 組織改善プラットフォームwevoxなどのサービスを運営。全ての社員が誇りを持てる組織と事業の創造にこだわり、関わる人々がファンとして応援したくなるような魅力ある会社であり続けることを目指しています。
https://atrae.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした