LoginSignup
4
6

More than 5 years have passed since last update.

RDS Multi-AZのフェイルオーバーをZABBIXで検知できるようにする

Posted at

概要

RDSのMulti-AZ構成で、DBのフェイルオーバーが発生したことを検知したいという要望があったので実装してみました。
仕組みとしては、RDSでフェイルオーバー時に発生するイベントサブスクリプションからAmazon SNSを呼び出して、Lambda関数を実行。Lambda関数でZABBIXに送信という形で考えてみました。

flow.jpg

設定

AWSの設定

Lambda用ロールの作成

IAM => ロール => ロールの作成 からLambdaで利用するロールを作成します。

信頼されたエンティティの種類を選択
=> AWSサービス
このロールを使用するサービスを選択
=> Lambda
ポリシー
=> AWSLambdaBasicExecutionRole

Lambda関数の作成

実行ロール
=>先ほど作成したロールを選択
ランタイム
=>Python2.7

lambda_function.py
ZabbixSender.py
という2つのファイルを作成します。

lambda_function.py
import os
import json
import logging
from ZabbixSender import ZabbixSender

ZBX_SERVER = os.environ['ZBX_SERVER']
ZBX_HOST = os.environ['ZBX_HOST']
ZBX_ITEM = os.environ['ZBX_ITEM']

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

def lambda_handler(event, context):
    message = json.loads(event['Records'][0]['Sns']['Message'])
    val = 0
    if message['Event Message'] == "Multi-AZ instance failover started":
        val = 1

    try:
        sender = ZabbixSender(ZBX_SERVER)
        sender.add(ZBX_HOST, ZBX_ITEM, val)
        sender.send()
        logger.info("Execution Succeeded.")
    except Exception, e:
        logger.error(e)
        raise(e)

フェイルオーバー発生時に、EventMessageに Multi-AZ instance failover started という文字列が含まれて送信されてくるので、この文字列を検知した場合に、変数に 1 をセットして送信します。
それ以外は全て 0 が入るので、フェイルオーバー完了時は 0 として送信されます。

ZabbixSender.py
import socket
import struct
import time
import json

class ZabbixSender:

    log = True

    def __init__(self, host='127.0.0.1', port=10051):
        self.address = (host, port)
        self.data    = []

    def __log(self, log):
        if self.log: print log

    def __connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            self.sock.connect(self.address)
        except:
            raise Exception("Can't connect server.")

    def __close(self):
        self.sock.close()

    def __pack(self, request):
        string = json.dumps(request)
        header = struct.pack('<4sBQ', 'ZBXD', 1, len(string))
        return header + string

    def __unpack(self, response):
        header, version, length = struct.unpack('<4sBQ', response[:13])
        (data, ) = struct.unpack('<%ds'%length, response[13:13+length])
        return json.loads(data)

    def __request(self, request):
        self.__connect()
        try:
            self.sock.sendall(self.__pack(request))
        except Exception as e:
            raise Exception("Failed sending data.\nERROR: %s" % e)

        response = ''
        while True:
            data = self.sock.recv(4096)
            if not data:
                break
            response += data

        self.__close()
        return self.__unpack(response)

    def __active_checks(self):
        hosts = set()
        for d in self.data:
            hosts.add(d['host'])

        for h in hosts:
            request = {"request":"active checks", "host":h}
            self.__log("[active check] %s" % h)
            response = self.__request(request)
            if not response['response'] == 'success': self.__log("[host not found] %s" % h)

    def add(self, host, key, value, clock=None):
        if clock is None: clock = int(time.time())
        self.data.append({"host":host, "key":key, "value":value, "clock":clock})

    def send(self):
        if not self.data:
            self.__log("Not found sender data, end without sending.")
            return False

        self.__active_checks()
        request  = {"request":"sender data", "data":self.data}
        response = self.__request(request)
        result   = True if response['response'] == 'success' else False

        if result:
            for d in self.data:
                self.__log("[send data] %s" % d)
            self.__log("[send result] %s" % response['info'])
        else:
            raise Exception("Failed send data.")

        return result

ZABBIXにデータを送信するためのライブラリを作ってらっしゃる方がいましたので使わせて頂きました!
ありがとうございます :pray:
http://blog.father.gedow.net/2015/12/08/aws-lambda-python-send-metric-value-to-zabbix/

環境変数を3つ用意します

環境変数
ZBX_HOST ZABBIXに登録されているホスト名
ZBX_ITEM RDS_failover.failover(監視アイテムとキー)
ZBX_SERVER ZABBIXサーバーのホスト名

SNSの作成

トピックの作成

トピック名:detectFailover

サブスクリプションの作成

プロトコル => AWS Lambda
エンドポイント => 作成したLambda関数のエンドポイント
「サブスクリプションの作成」をクリック

RDSの設定

RDSのダッシュボードから「イベントサブスクリプション」=>「イベントサブスクリプションの作成」をクリック
名前:サブスクリプションの名前
ターゲット:ARN
ARN:作成したトピックを選択
ソース
ソースタイプ:インスタンス
インスタンスを含める:個別に選択 インスタンス
指定インスタンスのプルダウンから、フェイルオーバーを検知したいRDSインスタンスを選択
含まれるイベントのカテゴリ:特定のイベントカテゴリを選択します。
特定のイベントのプルダウンから、「フェイルオーバー」を選択

「作成」をクリック

ZABBIXの設定

監視対象のホスト(RDS)がすでに登録されていれば、そのホストの設定を開きます。
ない場合は新規に作成

アイテムの作成

名前:RDS_failover
キー:RDS_failover.failover
タイプ:Zabbix トラッパー
データ型:数値(整数)
データの形式:10進数

トリガーの作成

名前:RDS-Multi-AZ-failover
条件式:下記のように入力します。

{ホスト:RDS_failover.failover.last()}=1 & {ホスト:RDS_failover.failover.nodata(300)}=0

Lambdaで、フェイルオーバーを検知した時に 1 がZABBIXに送信されるので、1の値がセットされた時にアラートが発生するようにします。
また、フェイルオーバー発生時しか値が飛んでこないので、5分間何も更新が無い時に復旧させます。

テスト

検証できる環境なら、実際にフェイルオーバーをさせてアラートが鳴るかテストします。
RDSから対象のインスタンスの詳細を開き、
アクション=>再起動 をクリック
「フェイルオーバーし再起動しますか?」にチェックを入れて「再起動」をクリックするとフェイルオーバーが走ります。
しばらく待つと、ZABBIXからアラートが発生するようになります。(多少時間差があるようです)
このまま5分待つと自動復旧になります。
ZABBIXの最新データを見ると、RDS_failover.failover のアイテムに 1 がセットされていることが分かります。

4
6
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
4
6