LoginSignup
10
12

More than 5 years have passed since last update.

AWS Lambda関数の実行エラーをZabbix Serverに通知してみた

Last updated at Posted at 2017-06-07

はじめに

運用監視系をZabbixで一元管理していることもあり、AWS Lambda関数の実行エラーもZabbix Serverへ通知したかったのでやってみました。

環境

  • Zabbix Server Version: 3.0.7
  • AWS Lambda Runtime: Python2.7

Zabbix ServerにHost/Item/Triggerを追加

Host追加

[設定] > [ホスト] > [ホストの作成]

「ホスト」タブ

  • ホスト名: lambda-trapper
  • 新規グループ作成: Lambda
  • エージェントのインターフェース
    • IPアドレス: 127.0.0.1 (デフォルト)
    • 接続方法: IPアドレス (デフォルト)
    • ポート: 10050 (デフォルト)
  • 有効: On
  • その他項目: 空欄

Item追加

[設定] > [ホスト] > ホスト一覧のlambda-trapperの[アイテム] > [アイテムの作成]

  • 名前: ErrorLog
  • タイプ: Zabbixトラッパー
  • キー: errorlog
  • データ型: 文字列
  • ヒストリ保存期間(日): 90 (デフォルト)
  • 値のマッピングの使用: なし (デフォルト)
  • アプリケーションの作成: Logs
  • ホストインベントリフィールドの自動設定: なし (デフォルト)
  • 有効: On
  • その他項目: 空欄

Trigger追加

[設定] > [ホスト] > ホスト一覧のlambda-trapperの[トリガー] > [トリガーの作成]

「トリガー」タブ

  • 名前: Lambda function execution error
  • 条件式: {lambda-trapper:errorlog.str(example)}=0 and {lambda-trapper:errorlog.nodata(30)}=0
  • 障害イベントを継続して生成: On
  • 深刻度: 重度の障害
  • 有効: On
  • その他項目: 空欄

AWS Lambda関数にロギング処理追加

既存のAWS Lambda関数(実行エラーをトラップしたい関数)に、以下のようにロギング処理を追加します。

コード

lambda_function.py
# (省略)

import logging

# (省略)

#ロガー初期化
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    try:

        # (省略)

        logger.info("Execution succeeded.")
    except Exception, e:
        logger.error(e)
        raise(e)    #PythonのスタックトレースをCloudWatchログへ記録させたい場合に記載

ロギング処理を追加することで、AWS Lambda関数の実行結果がCloudWatchに出力されるようになります。
正常終了ログの例)

[INFO]  2017-03-01T08:18:31.149Z    a7afba37-fe57-11e6-a428-371557c5f4e7    Execution succeeded.

異常終了ログの例)

[ERROR] 2017-03-01T09:38:37.966Z    d8c7ede3-fe62-11e6-b58a-7dce3a95f14a    cannot concatenate 'str' and 'int' objects

Zabbix送信用のAWS Lambda関数を作成

CloudWatchからKickされるZabbix送信用のAWS Lambda関数を新しく作成します。
ちなみにZabbixSenderモジュールは、「ここ」で公開されていたコードを流用させて頂きました。

AWS Lambda関数名

cwl-to-zabbix

コード

handler.py
import os
import json
from StringIO import StringIO
import base64
import gzip
from ZabbixSender import ZabbixSender
import logging

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 sender(event, context):
    log_b64 = event['awslogs']['data']
    log_gz = StringIO(base64.b64decode(log_b64))
    jsonData = json.load(gzip.GzipFile(fileobj=log_gz, mode='rb'))
    status = jsonData['logEvents'][0]['extractedFields']['status']
    message = jsonData['logEvents'][0]['extractedFields']['event']
    timestamp = int(jsonData['logEvents'][0]['timestamp']) / 1000
    func = os.path.basename(jsonData['logGroup'])

    try:
        if status == "ERROR":
            errlog = func + ': ' + message
            sender = ZabbixSender(ZBX_SERVER)
            sender.add(ZBX_HOST, ZBX_ITEM, errlog, timestamp)
            sender.send()
        logger.info("Execution Succeeded.")
    except Exception, e:
        logger.error(e)
        raise(e)
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

環境変数

  • ZBX_SERVER = <ZabbixサーバのIPアドレス>
  • ZBX_HOST = lambda-trapper
  • ZBX_ITEM = errorlog

設定

  • ランタイム: Python 2.7
  • ハンドラ: handler.sender
  • ロール: 既存のロールを選択
  • 既存のロール: ※


予め以下のアクセス権を持つロールを作成し、そのロールを当てます。

  • ENIへのDescribe、Create、Delete権限(Zabbixサーバと通信するので、VPC接続が必要)
  • CloudWatchのLogGroup、LogStreamへのCreate権限(AWS Lambda関数からロギング出力するため)
  • CloudWatchのLogEventへのPut権限

サブスクリプションフィルタを作成

  1. AWS Lambda関数の一覧からZabbix送信用の関数(cwl-to-zabbix)をクリック
  2. [トリガー]タブをクリックし、[トリガーの追加]をクリック
  3. イベントソース欄(破線の枠内)をクリックし、ソース一覧から[CloudWatch Logs]を選択
  4. 次のように項目を埋めて[送信]をクリック
    • ロググループ: (既存AWS Lambda関数のロググループを選択) ※
    • フィルタの名前: LambdaStream_cwl-to-zabbix
    • フィルタパターン: [status="ERROR", timestamp=*Z, request_id="*-*", event]

※予め前項のロギング処理を追加したAWS Lambda関数のロググループを選択してください。

まとめ的なもの

  • Zabbix送信用AWS Lambda関数自体がコケた場合については考慮していません。
  • ZabbixSenderモジュールを公開されている外道父さんに感謝!
  • サーバレスは面白いな。
10
12
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
10
12