LoginSignup
18
14

More than 5 years have passed since last update.

AWS lambdaを使ってWebサイトの死活監視を行う

Posted at

以前mimamoというウェブサイトの死活監視をHTTPのステータスコードレベルでのチェックをしてくれるサービスがありました。
ただやはりマネタイズが出来ないからでしょうか、残念ながらクローズしてしまいました。

railsとかでさくっと作っても良かったんですけどサーバを持つのが嫌だったのでタイトルにある通りサーバレスでHTTPステータスの監視を行うようにしました。

アーキテクチャ

cloudcraft.png

左側の縦のラインがHTTPのステータスチェックを行う箇所で、右側のラインがSNSからlambdaへ通知しslackやメールで通知します。
DBなど持ちたくなかったので、S3にcsvファイルが置いてありチェック対象のURLが記述してあります。

CSVファイル

以下のドメインはあくまでも例です。

list.csv
https://404.jp,https://200.jp

HTTPステータスチェックlambda

lambdaで使用出来るモジュールは限定されておりnpm installで取得したライブラリごとzip化してアップロード出来るけどそうするとデプロイにコストがかかってしまうため極力使わないようにコードを書いています。

時間単位で起動するlambdaを作成

スクリーンショット 2016-12-14 9.44.48.png

Configure function

スクリーンショット 2016-12-14 9.48.05.png

Lambda function code

コード内にSNSのarnがenvironment variableとして登録しているのでSNSを作成したらtopicArnとして登録してください。

haiken.js
'use strict';

exports.handler = (event, context, callback) => {
    let AWS = require('aws-sdk');
    let s3 = new AWS.S3();
    s3.getObject({Bucket: 'haiken', Key: 'list.csv'}, function(err, listCsv){
      let csv = listCsv.Body.toString();
        csv.split(',').forEach(function(url){
          let https = require('https');
            https.get(url, function(res){
             if(res.statusCode != 200){
               let sns = new AWS.SNS();
               sns.publish({
                 Message: url + ' is error',
                 Subject: 'error notification',
                 TopicArn: process.env.topicArn
               }, (err, data) => {
                   console.log(err);
               });

             }
            }).on('error', (err) => {console.log(err)});

        });
    })
}

Lambda function handler and role

このlambdaからはS3とSNSにアクセスするためroleを追加します。
大抵のroleであれば Create new role from templateを選択すればS3やSQSへのアクセス権は得られますがSNSがなかったため、新しいroleを作成してChoose an existing roleを選択し、作成したroleを選択する。

スクリーンショット 2016-12-14 10.09.06.png

通知用lambda

ブループリントを使用して新しく作成する

slackに通知するlambdaのブループリント(cloudwatch-alarm-to-slack)があったのでそれを使用しました。

スクリーンショット 2016-12-14 10.15.15.png

Lambda function code

基本的にはHTTPステータスチェック用lambdaと内容は同じで、通知用lambdaでは、slackのwebhook用URLをEnvironment variablesに登録する。

スクリーンショット 2016-12-14 10.26.40.png

haiken_notification.js
'use strict';

const AWS = require('aws-sdk');
const url = require('url');
const https = require('https');

const kmsEncryptedHookUrl = process.env.kmsEncryptedHookUrl;
const slackChannel = process.env.slackChannel;
let hookUrl = process.env.hookUrl;

function postMessage(message, callback) {
    const body = JSON.stringify(message);
    const options = url.parse(hookUrl);
    options.method = 'POST';
    options.headers = {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
    };

    const postReq = https.request(options, (res) => {
        const chunks = [];
        res.setEncoding('utf8');
        res.on('data', (chunk) => chunks.push(chunk));
        res.on('end', () => {
            if (callback) {
                callback({
                    body: chunks.join(''),
                    statusCode: res.statusCode,
                    statusMessage: res.statusMessage,
                });
            }
        });
        return res;
    });

    postReq.write(body);
    postReq.end();
}

function processEvent(event, callback) {
    console.log(event.Records[0].Sns.Message);
    const message = event.Records[0].Sns.Message;

    const slackMessage = {
        channel: slackChannel,
        text: message,
    };

    postMessage(slackMessage, (response) => {
        if (response.statusCode < 400) {
            console.info('Message posted successfully');
            callback(null);
        } else if (response.statusCode < 500) {
            console.error(`Error posting message to Slack API: ${response.statusCode} - ${response.statusMessage}`);
            callback(null);  // Don't retry because the error is due to a problem with the request
        } else {
            // Let Lambda retry
            callback(`Server error when processing message: ${response.statusCode} - ${response.statusMessage}`);
        }
    });
}

exports.handler = (event, context, callback) => {
    if (hookUrl) {
        // Container reuse, simply process the event with the key in memory
        processEvent(event, callback);
    } else if (kmsEncryptedHookUrl && kmsEncryptedHookUrl !== '<kmsEncryptedHookUrl>') {
        const encryptedBuf = new Buffer(kmsEncryptedHookUrl, 'base64');
        const cipherText = { CiphertextBlob: encryptedBuf };

        const kms = new AWS.KMS();
        kms.decrypt(cipherText, (err, data) => {
            if (err) {
                console.log('Decrypt error:', err);
                return callback(err);
            }
            hookUrl = `https://${data.Plaintext.toString('ascii')}`;
            processEvent(event, callback);
        });
    } else {
        callback('Hook URL has not been set.');
    }
};

SNS

上記のlambdaから直接通知用のslackAPIを叩いたりしてもいいですが、そうするとメールで通知したり他の通知方法を追加する時にコードを修正する必要が出て来るため極力notification patternを利用して実装します。
さらにオブジェクト指向よろしくlambdaも処理時間に上限が決まってたりするため1コード1ロールくらいに収めた方が良さそうです。

Create Topic

TopicはSubscribeの入れ物で、subscribeがどこに通知するかを登録するものです。

スクリーンショット 2016-12-14 10.40.22.png

Subscribe to Topic

TopicArnはSubscribe to Topicから作成すれば自動で入力されtげいます。
EndpointはProtocolによって入力する値が変わります(HTTPだったらURLとか)が、lambdaの場合は選択ボックスで作成したlambdaの一覧が出て来るので、実行したいlambdaを選択する。

スクリーンショット_2016-12-14_10_37_12.png

slack

haiken.jsを実行するとこんな感じで通知される。
slack以外に通知したい場合はSNSで通知先を登録してやればメールが届くようにも出来たりします。

スクリーンショット_2016-12-14_9_05_53.png

現場から以上です。

18
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
14