Help us understand the problem. What is going on with this article?

Route 53ヘルスチェック結果を元に稼働率を計算してslackにpostする with Serverless Framework

はじめに

Webサービスの稼働率を計算して定期的にモニタリングしたい事案があり、Route 53ヘルスチェックのメトリクスをLambdaで集計してslackに投げる仕組みをServerless Frameworkで作ってみたのでご紹介します。

仕様

  • CloudWatch Eventsのcron式により定期的に発火(下記サンプルの通り進めてもらうと毎週月曜9:05JSTに動きます)
  • Route 53ヘルスチェック結果をCloudwatchメトリクスから取得
  • 稼働率を計算してslackにpostする

稼働率の定義

こちらの内容からパク インスパイアされております。リスペクト。
Route 53のヘルスチェック情報で月間合計ダウンタイムと稼働率を計算してみた | DevelopersIO

2/3以上のヘルスチェッカーが異常として記録した時間帯をダウンタイムとし、
アップしていた計測ポイント / 全計測ポイント
を稼働率としています。

設定手順

Route 53ヘルスチェック作成

特に難しいところはありませんので、下記ページ等を参考に設定してください。

※ご自身の管理下にあるドメインに対して設定してくださいね!

SlackのIncoming Webhook URL生成

こちら等を参考に。

Incoming Webhook URLをSSMパラメータストアに保存

Webhook URLを平文でコードに書くのは憚られるので、SSMパラメータストアに暗号化して保存します。
※面倒だったら飛ばしても構いません。逆に他のパラメータも平文で書きたくなければ同様の手順で対応してください。

1. KMSでキーを作成

  • 適当な名前をつける
    スクリーンショット 2019-10-13 16.31.00.png

  • 必要に応じてタグを設定
    スクリーンショット 2019-10-13 16.31.07.png

  • 後ほどserverless deployを実行するIAMユーザまたはロールにキーへのアクセス許可を設定

    • キーの管理者権限とdeployする権限が異なる場合は必要に応じて調整してください スクリーンショット 2019-10-14 3.51.58.png スクリーンショット 2019-10-17 1.36.44.png
  • 確認して完了

以上で暗号化キーの作成完了です。

2. SSMパラメータストアにWebhook URLを保存

  • KMSキーID に先ほど作成したキーを指定するのがポイント スクリーンショット 2019-10-13 16.40.05.png

Serverless Framework

1. Serverless Frameworkをインストール

$ npm install -g serverless

※参考
Serverless Getting Started Guide

なおこの記事を書くにあたって動作確認したのは以下の状態です。

$ serverless -v                                                                                                                                      19-10-17 1:47:40
Framework Core: 1.54.0
Plugin: 3.1.2
SDK: 2.1.2
Components Core: 1.1.1
Components CLI: 1.2.3

2. 必要に応じてcredential設定

※参考
Serverless Framework Commands - AWS Lambda - Config Credentials

3. 以下の内容でserverless.ymlとhandler.pyを適当なディレクトリに配置する

  • こちらのリポジトリをcloneしてきていただいても大丈夫です。

  • serverless.ymlにて、以下のパラメータを各自の環境に合わせて更新が必要になります

    • HookUrl
      • 先ほど作成したslackのWebhook URL
      • 下の例のように書けば、パラメータストアに暗号化して保存した文字列を復号してLambdaFunctionの環境変数にセットされます。
    • slackChannel
      • postするチャンネル名
    • HealthcheckId
      • 先ほど作成したRoute 53ヘルスチェックのリソースID
serverless.yml
service: uptime-percentage-calculator

frameworkVersion: ">=1.2.0 <2.0.0"

provider:
  name: aws
  runtime: python3.7
  stage: production
  region: us-east-1
  timeout: 300
  iamRoleStatements:
    - Effect: "Allow"
      Action: "cloudwatch:GetMetricStatistics"
      Resource: "*"

functions:
  cron:
    handler: handler.run
    environment:
      HookUrl: ${ssm:SlackWebhook~true}
      slackChannel: "#notice"
      HealthcheckId: "your_healthcheck_id"
    events:
      - schedule: cron(5 0 ? * MON *)
handler.py
import boto3
import datetime
import json
import logging
import os

from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

SLACK_CHANNEL = os.environ['slackChannel']
HOOK_URL = os.environ['HookUrl']
HEALTHCHECK_ID = os.environ['HealthcheckId']

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def get_metrics_1day(date):
    starttime = date.replace(hour=0, minute=0, second=0, microsecond=000000)
    endtime = date.replace(hour=23, minute=59, second=59, microsecond=999999)

    return boto3.client('cloudwatch', region_name='us-east-1').get_metric_statistics(
        Namespace='AWS/Route53',
        MetricName='HealthCheckPercentageHealthy',
        Dimensions=[
            {
                'Name': 'HealthCheckId',
                'Value': HEALTHCHECK_ID
            }
        ],
        StartTime=starttime,
        EndTime=endtime,
        Period=60,
        Statistics=[
            'Average'
        ]
    )


def extract_healthcheck_results_per_min(metrics):
    datapoints = map(lambda x: x['Datapoints'], metrics)
    healthcheck_results_per_day = map(
        lambda x: [d.get('Average') for d in x], datapoints)
    return sum(healthcheck_results_per_day, [])


def calculate_uptime_percentage(healthcheck_results):
    if not healthcheck_results:
        logger.error("Metrics not available yet.")
        return False

    uptimes = list(filter(lambda x: x > 66.6, healthcheck_results))
    return round(len(uptimes)/len(healthcheck_results), 5)*100


def run(event, context):
    startdate = (datetime.datetime.now() - datetime.timedelta(days=7))
    datelist = [startdate + datetime.timedelta(days=day) for day in range(7)]

    metrics = map(get_metrics_1day, datelist)
    healthcheck_results = extract_healthcheck_results_per_min(metrics)
    uptime_percentage = calculate_uptime_percentage(healthcheck_results)

    if uptime_percentage is False:
        return "Abnormal end."

    message = '''
    * * Uptime percentage of last week ({startdate} ~ {enddate})*
    {uptime_percentage} %
    '''.format(
        uptime_percentage=uptime_percentage,
        startdate=datelist[0].strftime("%Y/%m/%d"),
        enddate=datelist[-1].strftime("%Y/%m/%d")
    ).strip()

    slack_message = {
        'channel': SLACK_CHANNEL,
        'text': message
    }

    req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", slack_message['channel'])
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

ちょっとハマったところ

Route 53のこのメトリクスはus-east-1(バージニア北部)からしか取得できませんでした。
一生懸命東京リージョンからgetしようとして悩んでしまった。

デプロイ

上記ファイルを配置したパスで以下のコマンドを実行します。

$ serverless deploy

うまくいくと、us-east-1にLambdaFunctionがデプロイされます。

結果

きました。
スクリーンショット 2019-10-17 1.21.08.png

まとめ

だいぶ前にServerless Frameworkを少しだけさわった時の印象で、破壊的な更新がどんどん入ってくるちょっと怖い奴というイメージだったのですが、久々に使ってみたらだいぶ安定感のあるツールになっていました(個人の記憶と感想)。
これなら軽くスクリプト書くくらいの感覚でLambdaFunctionを書くのもいいなと思いました。ありがとう、いいツールです。
以上です。

参考文献

Route 53のヘルスチェック情報で月間合計ダウンタイムと稼働率を計算してみた | DevelopersIO
CloudWatch — Boto 3 Docs 1.9.244 documentation
Serverless Getting Started Guide
Serverless Framework Commands - AWS Lambda - Config Credentials

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした