以前から挑戦したかったAWSの課題のひとつを解決してみようと思います。
AWS EC2やRDSなどのステータスを監視してアラームを上げてくれる、大変便利なCloudWatchですが、管理コンソール以外からアラームを確認する方法としては、SNSトピックに通知した後に自分でSNS連携のアプリケーションを作るなどして対応する必要がありました。
今回はこのアラームを、Slackの任意のチャンネルにポストすることをゴールにしてみます。
対象読者
- CloudWatchでサービスのモニタリングを行いたい人
- アラームをSlackのエンジニアチャンネルにリアルタイムで通知したい人
- AWS Lambda って取っつきにくそうだと思ってる人
- EC2インスタンスで同様のことを実装している人
なぜ Lambda なのか
EC2ではオーバースペックすぎる
私も今回はじめてAWS Lambdaを使ったのですが、知る前はEC2を使ってこれを実装しようと考えていました。
EC2のマイクロインスタンスを起動し、一定時間ごとにaws-cliからSNSトピックを監視し、新たなアラームがあれば通知する…これだけのためにインスタンスを起動しなくてはいけない、ああ結構大変そうだ…と億劫になっていました。
またアラームは頻繁に上がってくるものではなく、数日に1度、監視がほぼ不要なサービスでは1ヶ月に1度もお目に掛かることはないかもしれません(それはそれで良いことなのですが)。
つまり普段は何もしないインスタンスを一つ起動しておかなければならず、少額とは言えEC2の利用料もかかります。これは勿体ない。
AWS Lambda はイベントドリブンなコード実行サービス
詳しい紹介はまた別記しようと思っていますが、主要な特徴は次の3つです。
- サーバーレスで任意のコードが実行できる
- AWS内外の様々なイベントに応じてリアルタイム処理が可能(イベントドリブン)
- 使用した分だけの料金体系で、待機時間に対して課金が発生しない
つまり、
- EC2インスタンスやVPCの構築、保守の工数がかからず、
- SNSトピック定期監視のコードが不要でリアルタイムに、
- アラームが上がる度のコード実行時間に対してのみ課金される
今回のニーズにとても合致した、最高のサービスと言えるでしょう。
やってみる
CloudWatchのアラームは何を対象としたものでもいいので、今回はRDSのCPU使用率を対象に、閾値は80%以上の設定にしてみます。CloudWatchではSNSにトピックを通知するだけなので、このアラームの設定はEC2でもいいですし、CPUではなくメモリやディスク容量など何でも良いでしょう。
SlackのWebHook URLを取得
Slackの管理画面から、Incoming WebHooksを新規に追加します。
登録したら、「Setup Instructions」に書かれている「Webhook URL」を控えておきます。
また、今回はアイコンやチャンネル、Bot名などはここで設定したものを使いますので、今回は適当に次の設定にしておきました(WebHook URLはダミーです)。
SNSトピックの作成
まず最初に、CloudWatchから通知を受けるSNSトピックを作成します。
AWS SNSを開き、Create Topicをクリックします。
Topic nameを「cloudwatch-alerms」と設定して、「Create topic」をクリック。
Lambda Functionの作成
次にAWS Lambdaのコンソールを開き、新規関数を作成します。
Step 1: Select blueprint
ここでは既存のテンプレートを使って関数を作ることができますが、今回はすべてコードを書くので右下の「Skip」でスキップします。
Step 2: Configure function
ここでは実際のコードを書いていきます。
「Name」には「cloudwatch-to-slack」を、「Runtime」には「Node.js」を選択します。なお、執筆時点ではnodeの他にJava 8とPython 2.7が選択できるようになっていました。

コード入力欄に次のコード(gist)をペーストして、5行目の hookUrl を控えてある自分のSlack WebHook URLに書き換えてください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
var url = require('url'); var https = require('https'); // TODO: !!! Must be changed following Slack WebHook URL !!! var hookUrl = 'https://hooks.slack.com/services/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'; var processEvent = function(event, context) { var message = JSON.parse(event.Records[0].Sns.Message); // Format Slack posting message var text = "<!channel> *" + message.AlarmDescription + "* state is now `" + message.NewStateValue + "`\n" + "```" + "reason: " + message.NewStateReason + "\n" + "alarm: " + message.AlarmName + "\n" + "time: " + message.StateChangeTime + "```" ; var slackMessage = { text: text }; postMessage(slackMessage, function(response) { if (response.statusCode < 400) { console.info('Message posted!'); context.succeed(); } else if (response.statusCode < 500) { console.error("4xx error occured when processing message: " + response.statusCode + " - " + response.statusMessage); context.succeed(); // Don't retry when got 4xx cuz its request error } else { // Retry Lambda func when got 5xx errors context.fail("Server error when processing message: " + response.statusCode + " - " + response.statusMessage); } }); }; var postMessage = function(message, callback) { var body = JSON.stringify(message); var options = url.parse(hookUrl); options.method = 'POST'; options.headers = { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), }; var postReq = https.request(options, function(res) { var chunks = []; res.setEncoding('utf8'); res.on('data', function(chunk) { return chunks.push(chunk); }); res.on('end', function() { var body = chunks.join(''); if (callback) { callback({ body: body, statusCode: res.statusCode, statusMessage: res.statusMessage }); } }); return res; }); postReq.write(body); postReq.end(); }; exports.handler = function(event, context) { if (hookUrl) { processEvent(event, context); } else { context.fail('Missing Slack Hook URL.'); } }; |
次にこの関数を実行する際のAWS Roleを設定します。
「Lambda function handler and role」にある、「Role」を「Create new role -> * Basic execution role」に設定します。
新しいタブでIAM Roleの許可を求めるページが開きますので、右下の「許可」をクリックします。
設定できたら、AWS Lambdaの設定画面で右下の「Next」をクリックします。
Step 3: Review
問題なければ右下の「Create function」をクリックします。
Lambda イベントソースの設定
次に、作成したLambda FunctionにSNSトピックをサブスクライブさせます。
「Event sources」タブの「Add event source」をクリック。
「Event source type」で「SNS」を選択し、「SNS topic」は先に作っておいた「cloudwatch-alarms」を選択。すぐ有効にする「Enable now」のチェックを入れて、右下の「Submit」をクリック。
これでこのLambda Functionは、cloudwatch-alarmsのSNSトピックをリアルタイムに受け取り、nodeのコードでアラームが処理されるようになりました。
CloudWatchの設定
CloudWatch管理画面からメトリックスを開き、「アラームの作成」からアラーム設定画面を開きます。
ここでは、1分間の最大CPU使用率が80%を超えたときにアラームを発報するように設定しました。
重要なのは左下の「アクション」で、状態が「警告」と「OK」の両方のケースで、SNSトピック「cloudwatch-alarms」に通知する設定にします。
テストしてみる
今回、監視対象のCPU使用率は閾値以下の水準でしたので、「アラームの作成」ボタンをクリックした後、初回のデータが検出されると同時に「OK」のステータスとなり、SNSに通知されます。
このトピックへの通知イベントにより、「cloudwatch-to-slack」のLambda functionが呼び出され、Slackにメッセージが投稿されているはずです。チェックしてみましょう。
ポストされていました!
アラート時に正しく通知されるかどうかを確かめるには、AWS Cliを使うとテスト用のアラームを発報することもできますが、手軽に行うのであれば CloudWatch の閾値を下げるとよいでしょう。
CPU使用率が0.1%を超えたとき、のように条件を変更すると、次のようにアラームが発報されます。
費用感
ランニングコストの話です。AWSを使うと不安になりますよね。調べておきましょう。
CloudWatch のコスト
アラームごとに$0.1/月で、10個まで無料です。数台のモニタリングであれば10個で十分事足りそうですね。
SNS のコスト
Lambda関数への配信は無料とのこと!
Lambda のコスト
Lambdaでは、リクエスト回数とメモリ使用量×利用時間で料金が計算されます。
リクエストの料金は、最初の100万回が無料、それ以降100万回ごとに$0.2です。これは無料枠で十分すぎます。
今回のLambdaではメモリ128MBを選択しているので、100ミリ秒ごとに$0.000000208、ただし3,200,000秒/月まで無料で使用できます。
最大実行時間を3秒と設定しているので、最大の時間で計算したとして月あたり106万通知は無料で処理できる計算です。現実的に考えて、これだけの回数アラームが有効になることはありませんから、Lambdaのコストも無視できることになります。素晴らしい!
ほぼ無料で利用できる!
実際にはSlackへの通信等で僅かに料金が発生しますが、CloudWatchのアラームが10個以下であれば無料で使えると言ってもよいでしょう。
この用途だけにLambdaを利用するのであれば、考慮すべきは実質CloudWatchのコストのみということですね。仮にアラームを100個設置したとしても、トータルコストは$1/月に収まるでしょう。
仮にEC2で実装するとEC2マイクロインスタンスでも毎月数千円、またSNSへのAPIコールにも課金されてしまいます。大きな差ですね。
まとめ
新しいサービスの採用によって開発コストもランニングコストも抑えることができ大満足です。
実際にやってみると、コードを書く時間を含めて2時間程度で実装することができました。EC2のインスタンスを起動して行うとなると、多分この数倍の時間が必要になったことでしょう。
これだけのサービスでもAWSのCloudWatch、SNS、Lambdaといくつかのサービスを組み合わせて動いていますが、このように必要な機能ごとにサービスを組み合わせて使えるのがAWSの本当に素晴らしいところだと思います。
AWS Lambdaは本当に面白いサービスで、イベントドリブンで必要なコードを、インフラを全く意識することなく実行できるのが面白いです。S3バケットなどとも連動することができ、色々なシーンで活躍できそうな可能性を感じています。私のお気に入りサービスの一つになりました。
所感
AWSの新しいサービスに、敬遠せずにどんどん手を出していきたいと決意を新たにする良い機会でした。今回もし古い知識だけでEC2で実装していたら…と思うとゾッとします。またひとつ賢くなれた気がします。
2時間と決めて執筆していたところ3時間かかってしまいました。もう少し効率良く技術系のナレッジを共有できるようになりたいですね。自分へのご褒美に美味しいお肉でも食べてきます :p















