こんにちは、AWS担当のwakです。前回に続きAWS Lambdaの話をします。
良い角度を知っている猫
【インデックス(予定)】
- CloudWatchの監視結果をSlackに流す(AWS Lambdaバージョン)
- AWS LambdaでWebサイトの死活監視を行ってSlackに結果を流す(今回)
- DynamoDBにパラメーターを入れてハードコードをなくす
- CloudWatchの監視結果をTwilioに流して電話をかける
- SlackからEC2インスタンスを起動・停止するコマンドを作る
今回の目的について
前回に続いて、Lambda (Node.js)を使ってWebサイトの監視を行います。
- 任意のWebサイトにアクセスし、HTTP 200が返るかどうかを確かめる
- その結果をSlackで通知する
- この処理をN分おきに実行する(cron形式でスケジュールを設定可能)
これに似たサービスはいくらでもあるのですが、
といった利点があります。それではさっそく始めましょう。
Slackの準備をする
Slack側で通知を行うためにIncoming WebHooksの準備をします。こちらは前回と同じ手順で新規作成しても構いませんし、新規に作っても構いません。
Lambdaの準備をする
Management ConsoleからLambdaを選ぶと、最初にブループリント(テンプレートみたいなものですね)の選択画面が表示されます。試しに「https-request」を選ぶと、Node.jsでHTTPSアクセスを行うためのサンプルコードが入力された状態でコード編集画面へと遷移します。
コードを入力する
元から記入されているコードを全て削除し、置き換えます。完全な形のコードはこの記事の末尾に掲載しているのでコピペしてください。sendToSlack()
は前回と同じもので、Slackへ通知を行ってから処理を終了する関数です。
var https = require('https'); var http = require('http'); function sendToSlack(path, message, channel, context, otherParameters) { ... } exports.handler = function(event, context) { ... }
テスト実行する
event
の値は見ていないので、適宜コードを修正して監視対象のURLとページ名(Slack通知時に使われます)、Slackのエンドポイント・通知チャンネル名を変更してテスト実行してみましょう。設定がうまく済んでいれば通知が行われるはずです。
実行設定する
Event sourcesタブを開き、"Add event source"をクリックします。"Event source type"に"Scheduled Event"をセットすると、「5分ごと」「15分ごと」のようなプリセットの他に、cron形式で入力ができるようになります。
公式ドキュメント を読めば特に難しいことはないのですが、たとえば「月曜日から金曜日の毎時5分、15分、……55分」というスケジュールで定期実行するように設定するのであればこうなります。
cron(5/10 * ? * MON-FRI *)
が指定している内容で、パラメーターは順に
5/10
は毎時5分、15分、……、55分を示す書式です。0/5
なら0分、5分、10分、……になります。0
なら毎時0分になります。- 次の
*
は 全ての時(hour) を指定しています。 - 次の
?
は日を 指定しない ことを意味しています。ここで*
(全ての)を指定してしまうと、後の曜日指定と矛盾してしまうのでエラーになります(ちょっとハマりました)。 - 次の
*
は全ての月を指定しています。ここは良いでしょう。 - 次の
MON-FRI
は曜日です。MON,WED,FRI
のように列挙することもできます。 - 最後の
*
は全ての年です。
ですから、たとえば「毎日9時から18時の間、毎時0分と30分」であればcron(0,30 9-18 * * ? *)
になります(今度は曜日の方を「指定しない」としています)。
ここで登録したスケジュールは、AWS Lambdaとは独立したリソースとして保存・管理されます*1。別のLambda functionで使い回すこともできるようになっています。
まとめ
作ろうと思ったら簡単に実装ができてしまいました。次回はソースコードの中の値をDynamoDBに格納し、ハードコードを排除してみます。
コード
var https = require('https'); var http = require('http'); /* * Slackに通知を行う。 * path: Slackのエンドポイント。 "/services/xxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" の形式の文字列 * message : 送信するメッセージ * context : Lambdaのコンテキスト * otherParameters : icon_emoji, usernameなどをキーに持った配列(省略可能) */ function sendToSlack(path, message, channel, context, otherParameters) { context = context ? context : {succeed: function(){}, fail: function(){}, done: function(){}}; var options = { hostname: "hooks.slack.com", port: 443, path: path, method: 'POST' }; var req = https.request(options, function(res) { if (res.statusCode == 200) { context.done(); } else { var message = "通知に失敗しました. SlackからHTTP " + res.statusCode + " が返りました"; console.error(message); context.fail(message); } }); req.on('error', function(e) { var message = "通知に失敗しました. Slackから次のエラーが返りました: " + e.message; console.error(message); context.fail(message); }); var parameters = otherParameters ? otherParameters : {}; parameters.text = message; parameters.channel = channel; req.write(JSON.stringify(parameters)); req.write("\n"); req.end(); } exports.handler = function(event, context) { console.log(event); // 証明書エラーを無視する process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; var url = "https://www.google.co.jp"; var title = "Googleトップページ"; var path = "/services/xxxxxxxxx/xxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx"; var channelName_error = "#channelNameError"; // エラー時の通知先チャンネル名。必須 var channelName_ok = "#channelNameOk"; // OK時の通知先。空文字にするとOK時は通知が出なくなる var option = { icon_emoji : ":ghost:", username : "MYBOT" }; var agent = (url.indexOf("https://") === 0) ? https : http; agent.get(url, function(res) { console.log(res); var message = title + ": " + url + " にアクセスして HTTP " + res.statusCode + " が返ってきました"; var channel = (res.statusCode == 200) ? channelName_ok : channelName_error; if (!channel) { console.log(message + ". 通知はスキップします"); context.done(); return; } else { console.log(message); sendToSlack(path, message, channel, context, option); } }).on('error', function(e) { var message = url + "にアクセスできませんでした:\n" + e.message; console.error(message); sendToSlack(path, message, channelName_error, context, option); }); };
sendToSlack()
は前回からの使い回しです。- 前回に続き、処理はSlack通知をもって完了します。
- そのため、
context.succeed()
やcontext.fail
はsendToSlack()
の中だけで呼んでいます。 - それより前に呼んでしまうと、(非同期で)コンテンツを取得しに行く前に処理が終わってしまいます。
- そのため、
*1:そのため、スケジュール保存時に単独でARNが振られます