TL;DR;
AWS LambdaとScheduled Eventを利用した、サーバレスのSlackボットをつくることができるnpmモジュールと、
それを使ったボットの作り方を紹介します。
hubotの代替を意図しています。できるだけサーバ保守などの手間をかけず、安価にSlackボットを運用したい方におすすめです。
本質はLambdaにデプロイされる単一のNode.jsアプリなので、多くの人がhubotでやってることはだいたいできると思います。
GitHub
https://github.com/mumoshu/lambda_bot
npm
https://www.npmjs.com/package/lambda_bot
背景
社内向けのSlackボットをいくつか運用しています。
現状のボットはhubotベースで、hubotの実行環境の整備や保守はEC2インスタンス上の温かみのある手作業で行っています。結果的に、ボットの開発環境をつくるのが面倒(他のエンジニアが開発に参加しづらい)、早朝・深夜はほとんど使われない割に、止めて良いわけでもないので必要以上にEC2インスタンスの利用料金がかかっている、などの問題が発生しています。選択肢はいくつかあると思いますが、例えばAWS Lambdaベースで作り直すことができればこれらの問題が解決しそうです。
ボットの主な振る舞いと実装
弊社のSlackボットの主な振る舞いは、
- 誰かの発言に応答する
- hubot、Slack Incoming WebHooks APIを利用するWebアプリなどで実装
- ある日時に特定の発言をする
- hubot+hubot-cron、cron+bash+Slack APIなどで実装
後者については、UTC(Unreliable Town Clock)とLambdaを組み合わせればできそうでしたが、ボットとはいえUnreliableなものに頼るのはいかがなものか…ということで、結局先送りにしていました。
つくってみた
先日参加したAWS re:Invent 2015でAWS LambdaのScheduled Eventが発表されて、UTCを使わなくてもサーバレスで弊社のSlackボットの主な振る舞いを実装できそうな環境が揃ったので、早速つくってnpmモジュールとして公開しました。このnpmモジュールを利用すると、Lambdaで動くSlackボットをつくることができます。
GitHub
https://github.com/mumoshu/lambda_bot
npm
https://www.npmjs.com/package/lambda_bot
※
開催期間中にAPI GatewayがTokyoリージョンでも使えるようになったので、Oregon等のAPI GatewayとLambdaを使ったり、Oregon等のAPI GatewayとTokyoのLambdaを繋いだりしなくて良くなったのも怠惰な自分的には後押しになりましたが、それはまた別の話
lambda_botの構成
lambda_botは以下の内容を含むzipファイルとして、AWS Lambdaにデプロイされます。
- AWS LambdaのHandlerを定義するjsファイル
- 上記が必要とする環境変数を定義する.envファイル
- 1が必要とするnodeモジュール(./node_modules/以下)
1はどのように振る舞うbotかを定義するファイルそのものなので、bot毎に作る必要があります。
2はどのSlackチャンネルで動く、どんな名前のボットかを決めるものなので、やはりbot毎に作る必要があります。
3は1から利用するnpmモジュールをいつも通り npm install --save
しておけばOKです。
lambda_botのつくり方
大まかには以下の流れでつくることができます。
- ボット自体のnpmモジュールを作って、
- ボットをjsで記述して、
- ボットが利用する環境情報(しかもGit等で管理したくないもの)を.envファイルに記述して、
- lambda_botのCLIでLambdaへデプロイして、
- API GatewayとSlack Integrationを温かみのある手作業でセットアップ
npmモジュールをつくる
$ mkdir my_lambda_bot
$ cd my_lambda_bot
$ npm init
*省略*
$ npm install --save lambda_bot node-env-file
コードを書く
lambda_botのnpmモジュールにコード例があるので、それをコピーして始めます。
$ cp node_modules/lambda_bot/example_bot.js my_lambda_bot.js
lambda_botの実体はLambdaで動作するNode.jsアプリなので、適切な exports.handler
を定義する必要があります。
lambda_botはSlackボットとして振る舞う exports.handler
を簡単に定義するためのライブラリ、と思っていただくと良いかもしれません。
lambda_botを使うと、exports.handler
を以下のように定義できます。
var LambdaBot = require('lambda_bot'),
env = require('node-env-file');
env(__dirname + '/.env');
var bot = new LambdaBot({
iconEmoji: process.env['SLACK_ICON_EMOJI'],
userName: process.env['SLACK_USER_NAME'],
channelName: process.env['SLACK_CHANNEL_NAME'],
slackIncomingWebhookURL: process.env['SLACK_INCOMING_WEBHOOK_URL']
});
// SLACK_CHANNEL_NAME環境変数で指定したチャンネルで foo を含む発言を見かけると、bar と発言します
// hubotのhearやsendと同じ意味ですが、hubotとは違いreturnが必須なので注意。
// res.sendはメッセージを送り終わるとfulfilされるPromiseを返します。
bot.hear(/foo/, function(res) {
return res.send('bar');
});
// 「名前 hi」のように名前をつけて「hi」と呼びかけると、呼びかけたユーザに対して「@ユーザ名 hi」のように返答します
// 名前はSLACK_USER_NAME環境変数で指定したものです。
bot.respond(/hi/, function(res) {
return res.reply('hi');
});
// 「午前10時に「It's 10am.」と発言します。
// Lambda > Event Sources > Scheduled Event > cronで下記のようなScheduled Eventを作成しておく必要があります。
bot.on('10am', function(res) {
return res.send("It's 10am.");
});
exports.handler = bot.createHandler();
SlackのIncoming Webhook
lambda_botからSlackへメッセージを送るために使うSlackのIncoming WebHookを新規作成します。
Webhook URLは後に作成する.envファイルに記述する必要があるので、控えておきます。
.envファイルを書く
Slackのチャンネル名やIncoming WebHookのURLなど、Git等にコミットしたくないものは .env
ファイルに書きます。このファイルに書く情報を共有したいときは、S3バケット等にServer-side Encryptionを利用して置いておくとよいのではないかと思いますが、それはまた別の話。
$ cat > .env
SLACK_INCOMING_WEBHOOK_URL='https://hooks.slack.com/services/***/***/***'
SLACK_CHANNEL_NAME='#を含まないチャンネル名'
SLACK_USER_NAME='@を含まないユーザ名'
SLACK_ICON_EMOJI=':beer:'
AWS Lambdaへlambda_botをデプロイする
$ SLACK_CHANNEL_NAME='デプロイ完了メッセージを送るSlackチャンネル` \
SLACK_USER_NAME='デプロイ完了メッセージの送信要求をボットへ送るSlackユーザ` \
LAMBDA_FUNCTION_NAME='lambda_bot等' \
LAMBDA_HANDLER='my_lambda_bot.handler' \
node_modules/lambda_bot/bin/lambda_bot deploy
my_lambda_bot.handler
の my_lambda_bot
部分は、今回つくるボットのjsファイル my_lambda_bot.js
のベースネーム部分に対応しています(対応していないと、Lambda Functionの呼び出し時にエラーになります)。
API Gatewayの設定
API Gatewayで任意の名前のAPIを作成します。
APIからLambda Functionへのつなぎ方は自由なのですが、例えば /
リソースの POST
メソッドをLambda Functionにつなぎます。
Integration Requestで先ほどデプロイしたLambda Function名(今回はlambda_bot)を指定します。
「API Gatewayに以下のLambda Functionの呼び出し権限を与えますがよろしいですか?」という趣旨のダイアログが表示されるので、OKで権限を与えます。
Integration RequestのMapping Templateには、AWSフォーラムの以下のスレッドで紹介されているものを使います。このMapping Templateが必要な理由は、2015/11/6現在、API GatewayとLambdaをそのまま繋ぐだけでは、Content-Type
がapplication/x-www-formurlencoded
なリクエストメッセージの内容をJSON形式でLambdaに渡すことができないからです。
API、リソース、メソッドの作成が終わったら、適当なステージにデプロイしておきます。今回はproductionというステージにデプロイします。
SlackのOutgoing Webhookを作成
lambda_botがSlackメッセージを受信するために使うOutgoing WebHookを新規作成します。
「URL(s)」欄には、上記でデプロイしたAPI GatewayのステージのURLを入力しておきます。
今回はAPI Gatewayのproductionというステージにデプロイしたので、URL(s)欄に入力するURLのパス部分にもproductionという文字列があるはずです。
動作確認
指定したSlackチャンネルで以下のように振る舞うはずです。
-
foo
と発言するとbar
と返ってくる -
lambda_bot hi
などと発言すると@あなた hi
と返ってくる - 月-金の10時になると(前述のLambda Scheduled Eventの設定内容による) ボットが
It's 10am.
と発言する
まとめ
AWS LambdaとScheduled Eventを利用したサーバレスのSlackボットをつくることができるnpmモジュールと、
それを使ったボットの作り方を紹介しました。
できるだけサーバ保守などの手間をかけず、安価にSlackボットを運用したい方は、ぜひ使ってみてください。
プルリクエストお待ちしています