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
タスクを追加。
{
"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
。
'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.yml
のservice
ブロックの値を適当なものに変更。また、AWSの東京リージョンを使うので、provider
ブロックのregion
を追加。(デフォルトだと us-east-1になってしまうらしい)
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
{
"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
にさらにタスクを追加。
{
"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
)。
{
"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トピックも同時に新規作成。
functions:
dispatcher:
handler: dispatcher.dispatch
events:
- sns: lambda-test
さっきのhandler.js
をコピって、適当にdispatcher.js
とexportする関数dispatch
を作成。
'use strict';
module.exports.dispatch = (event, context, callback) => {
// 中身はhandler.jsといっしょ
};
SNSトピックが作成され、新しくデプロイされたLambdaファンクションのトリガーになっていることがコンソールを見ると分かる。
このSNSトピックをCloudWatchのアラームと連携させる。試しに、とあるテスト環境のELBのHealthyHostCountに関するアラームの通知先に設定。
ロードバランサー下には1つのEC2が配置してある。正常なホスト数の合計が1を下回ったら警告を通知するようにする。わざとEC2で動いているアプリケーションサーバをストップすると、CloudWatchから通知を受けたSNSを通して、Lambdaが起動する。(LambdaのコンソールのMonitoringなどから確認できる)
チャットワークに通知
起動トリガーとなっているSNSの通知内容(イベント)を、Lambdaを使ってチャットワークに通知する。もうイベントのsubscription自体はできているので、あとはそれを元にファンクションの中身を書く。先ほどのdispatcher.js
を変更。
(Lambdaはイベント駆動であるがゆえに、ロジックを書きながらデバッグしていくのが面倒。何か良い方法あるのだろうか。。)
'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のアラームが来るタイミングで、以下のようにチャットワーク上で通知が来る。
おわり
簡単ではあるが、いったんお試し終わり。
Serverless Frameworkを利用することで、Lambdaファンクションの簡単なデプロイ & AWS各種サービス(今回のSNSだけでなく、API GatewayやS3 etc.)との連携が簡単に実行できるのが分かった。イベント連携などを統合してやってくれるのはありがたい。
また、Serverlessプロジェクトのファイル群をGitで管理すれば、Lambdaファンクションなどの変更履歴を管理することが可能になる。これもフレームワークの恩恵ですね。
Lambda周りは、AWSがre:Inventでもめちゃくちゃ力入れてる感を出してたので、ちゃんと学んでギョームで作ってるシステムのアーキテクチャにも取り入れていきたいところ。