2021.09.06
Zabbixを使用したRDSの監視
みなさん社内にある複数のサーバの監視はどのようにして行っていますか?
弊社ではZabbixを使用して一元管理での監視を行っています。
Zabbixの監視対象となるものとして基本的にはZabbixAgentがインストールできるものかSNMPに対応している機器である必要があります。
ところがAWSやSBクラウドで使用するRDSに関してはそのどちらも対応していません。
RDSはシステムの根幹となる部分ですので対象から外す訳にも行かずクラウド業者が用意しているCloudWatchなどでこれだけ見るのも面倒です。
そこでどうするか。ZabbixからCloudWatchのAPIを叩いてステータス値を取得して監視を行います。
今回はその紹介となります。
・AWSの場合
AWSのEC2インスタンスの監視をzabbixへ統合監視するために、zabbixのLLD(ローディスカバリー)機能を使ってみた!
を参考に設定を行います。
まずAPIを叩くためのスクリプトの作成。
参考サイトの例ではうまく行かないのでこちらで修正したものを掲載します。
#!/bin/env python3 import boto3 import json import argparse import os import socket import struct import time import calendar from datetime import datetime from datetime import timedelta class Metric: def __init__(self, name="", namespace="", unit="", dimensions=[]): self.name = name self.namespace = namespace self.unit = unit self.dimensions = dimensions class AwsZabbix: def __init__(self, region, access_key, secret, identity, hostname, service, timerange_min, zabbix_host, zabbix_port): self.zabbix_host = zabbix_host self.zabbix_port = zabbix_port self.identity = identity self.hostname = hostname self.service = service self.timerange_min = timerange_min self.id_dimentions = { 'ec2':'InstanceId', 'rds':'DBInstanceIdentifier', 'elb':'LoadBalancerName', 'ebs':'VolumeId', 'billing': 'Currency' } self.client = boto3.client( 'cloudwatch', region_name=region, aws_access_key_id=access_key, aws_secret_access_key=secret ) self.sum_stat_metrics = [ {'namespace': 'AWS/ELB', 'metricname': 'RequestCount'}, {'namespace': 'AWS/ELB', 'metricname': 'HTTPCode_Backend_2XX'}, {'namespace': 'AWS/ELB', 'metricname': 'HTTPCode_Backend_3XX'}, {'namespace': 'AWS/ELB', 'metricname': 'HTTPCode_Backend_4XX'}, {'namespace': 'AWS/ELB', 'metricname': 'HTTPCode_Backend_5XX'}, {'namespace': 'AWS/ELB', 'metricname': 'HTTPCode_ELB_4XX'}, {'namespace': 'AWS/ELB', 'metricname': 'HTTPCode_ELB_5XX'} ] def __get_metric_list(self): resp = self.client.list_metrics( Dimensions = [ { 'Name': self.id_dimentions[self.service], 'Value': ('USD' if self.service == "billing" else self.identity) } ] ) metric_list = [] for data in resp["Metrics"]: metric = Metric(name=data["MetricName"], namespace=data["Namespace"], dimensions=data["Dimensions"]) if self.service == "elb": for dimension in data["Dimensions"]: if dimension["Name"] == "AvailabilityZone": metric.name = data["MetricName"] + "." + dimension["Value"] metric_list.append(metric) return metric_list def __get_metric_stats(self, metric_name, metric_namespace, servicename, timerange_min, stat_type="Average", period_sec=300): if self.service == "billing": dimensions = [ { 'Name': self.id_dimentions[self.service], 'Value': 'USD' } ] if servicename != "billing": dimensions.insert(0, { 'Name': 'ServiceName', 'Value': servicename } ) else: dimensions = [ { 'Name': self.id_dimentions[self.service], 'Value': self.identity } ] if self.service == "elb": split_metric_name = metric_name.split(".") if len(split_metric_name) == 2: metric_name = split_metric_name[0] dimensions.append( { 'Name': 'AvailabilityZone', 'Value': split_metric_name[1] } ) stats = self.client.get_metric_statistics( Namespace=metric_namespace, MetricName=metric_name, Dimensions=dimensions, StartTime=datetime.utcnow() - timedelta(minutes=timerange_min), EndTime=datetime.utcnow(), Period=period_sec, Statistics=[stat_type], ) return stats def __set_unit(self, metric_list): ret_val = [] for metric in metric_list: servicename = self.service if self.service == "billing": metric.unit = 'USD' else: stats = self.__get_metric_stats(metric.name, metric.namespace, servicename, self.timerange_min) for datapoint in stats["Datapoints"]: metric.unit = datapoint["Unit"] break ret_val.append(metric) return ret_val def __get_send_items(self, stats, metric): send_items = [] datapoints = stats["Datapoints"] datapoints = sorted(datapoints, key=lambda datapoints: datapoints["Timestamp"], reverse=True) for datapoint in datapoints: servicename = '' send_json_string = '{"host":"", "key":"", "value":"", "clock":""}' send_item = json.loads(send_json_string) if self.hostname == "undefined": send_item["host"] = self.identity else: send_item["host"] = self.hostname if self.service == "billing": for dimension in metric.dimensions: if dimension["Name"] == "ServiceName": servicename = dimension["Value"] send_item["key"] = 'cloudwatch.metric[%s.%s]' % (metric.name, servicename) else: send_item["key"] = 'cloudwatch.metric[%s]' % metric.name send_item["value"] = self.__get_datapoint_value_string(datapoint) send_item["clock"] = calendar.timegm(datapoint["Timestamp"].utctimetuple()) send_items.append(send_item) break return send_items def __get_datapoint_value_string(self, datapoint): if "Average" in datapoint: return str(datapoint["Average"]) elif "Sum" in datapoint: return str(datapoint["Sum"]) else: return "" def __send_to_zabbix(self, send_data): send_data_string = json.dumps(send_data) zbx_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: zbx_client.connect((self.zabbix_host, self.zabbix_port)) except Exception: print("Can't connect to zabbix server") quit() b1, b2 = ('<4sBQ'.encode('utf-8'), 'ZBXD'.encode('utf-8')) header = struct.pack(b1, b2, 1, len(send_data_string)) send_data_string = send_data_string.encode('utf-8') send_data_string = header + send_data_string try: zbx_client.sendall(send_data_string) except Exception: print('Data sending failure') quit() response = '' while True: data = zbx_client.recv(4096) if not data: break response += data.decode(encoding='utf-8') print(response[13:]) zbx_client.close() def send_metric_data_to_zabbix(self): now = "%.9f" % time.time() sec = now.split(".")[0] ns = now.split(".")[1] send_data = json.loads('{"request":"sender data","data":[],"clock":"%s","ns":"%s" }' % (sec, ns)) metric_list = self.__get_metric_list() all_metric_stats = [] servicename = self.service for metric in metric_list: if self.service == "billing": for dimension in metric.dimensions: if dimension["Name"] == "ServiceName": servicename = dimension["Value"] target_metric_info = {'namespace': metric.namespace, 'metricname': metric.name} for sum_stat_metric in self.sum_stat_metrics: # for support each region metrics (RequestCount, RequestCount.ap-northeast-1 etc.) if metric.name.find(sum_stat_metric['metricname']) == 0: # Only convert when finding the begging of string. target_metric_info['metricname'] = sum_stat_metric['metricname'] if target_metric_info in self.sum_stat_metrics: stats = self.__get_metric_stats(metric.name, metric.namespace, servicename, self.timerange_min, 'Sum') else: stats = self.__get_metric_stats(metric.name, metric.namespace, servicename, self.timerange_min) send_data["data"].extend(self.__get_send_items(stats, metric)) self.__send_to_zabbix(send_data) def show_metriclist_lld(self): lld_output_json = json.loads('{"data":[]}') metric_list = self.__get_metric_list() metric_list = self.__set_unit(metric_list) for metric in metric_list: lld_json_string = '{"{#METRIC.NAME}":"", "{#METRIC.UNIT}":"", "{#METRIC.NAMESPACE}":""}' lld_item = json.loads(lld_json_string) lld_item["{#METRIC.NAME}"] = metric.name lld_item["{#METRIC.NAMESPACE}"] = metric.namespace lld_item["{#METRIC.UNIT}"] = metric.unit lld_output_json["data"].append(lld_item) if self.service == "billing": lld_item["{#METRIC.SERVICENAME}"] = "" for dimension in metric.dimensions: if dimension["Name"] == "ServiceName": lld_item["{#METRIC.SERVICENAME}"] = dimension["Value"] print(json.dumps(lld_output_json)) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Get AWS CloudWatch Metric list json format.') parser.add_argument('-r', '--region', default=os.getenv("AWS_DEFAULT_REGION"), help='set AWS region name(e.g.: ap-northeast-1)') parser.add_argument('-a', '--accesskey', default=os.getenv("AWS_ACCESS_KEY_ID"), help='set AWS Access Key ID') parser.add_argument('-s', '--secret', default=os.getenv("AWS_SECRET_ACCESS_KEY"), help='set AWS Secret Access Key') parser.add_argument('-i', '--identity', required=True, help='set Identity data (ec2: InstanceId, elb: LoadBalancerName, rds: DBInstanceIdentifier, ebs: VolumeId)') parser.add_argument('-H', '--hostname', default='undefined', help='set string that has to match HOST.HOST. defaults to identity)') parser.add_argument('-m', '--send-mode', default='False', help='set True if you send statistic data (e.g.: True or False)') parser.add_argument('-t', '--timerange', type=int, default=10, help='set Timerange min') parser.add_argument('-p', '--zabbix-port', type=int, default=10051, help='set listening port number for Zabbix server') parser.add_argument('-z', '--zabbix-host', default='localhost', help='set listening IP address for Zabbix server') parser.add_argument('service', metavar='service_name', help='set Service name (e.g.: ec2 or elb or rds') args = parser.parse_args() aws_zabbix = AwsZabbix(region=args.region, access_key=args.accesskey, secret=args.secret, identity=args.identity, hostname=args.hostname, service=args.service, timerange_min=args.timerange, zabbix_host=args.zabbix_host, zabbix_port=args.zabbix_port) if args.send_mode.upper() == 'TRUE': aws_zabbix.send_metric_data_to_zabbix() else: aws_zabbix.show_metriclist_lld()
このスクリプトの場合は大変優秀でディスカバリルールを設定するだけでアイテム一覧を取得してきます。
あとは監視対象としたいアイテムをグラフとして載せるだけで設定完了です。
ディスカバリルールで実行するスクリプトは以下のような形式となります。
cloudwatch_zabbix.py[rds,-i,{HOST.HOST},-r,リージョン,-a,アクセスキー,-s,シークレットキー]
・SBクラウドの場合
こちらはSBクラウド自身が参考記事を載せていますのでそれを参考にします。
Alibaba Cloud環境でZabbix導入およびCloud Monitor連携してみた
参考サイトではサービス単体毎にスクリプトをわけないといけないのでこちらでパラメータを受け取る形式に変更したものを掲載します。
#! /usr/bin/env python3 #coding=utf-8 from aliyunsdkcore.client import AcsClient from aliyunsdkcms.request.v20190101.DescribeMetricListRequest import DescribeMetricListRequest import json import argparse parser = argparse.ArgumentParser(description='Get Alibaba CloudMonitor Value') parser.add_argument('service', help='ecs or rds') parser.add_argument('-a','--accesskey') parser.add_argument('-s','--secretkey') parser.add_argument('-r','--region') parser.add_argument('-i','--instanceid') parser.add_argument('-m','--metric') args = parser.parse_args() accessKeyId = args.accesskey accessSecretKey = args.secretkey region = args.region instanceId = args.instanceid client = AcsClient(accessKeyId, accessSecretKey, region) request = DescribeMetricListRequest() request.set_Dimensions("{'instanceId': '" + instanceId + "'}") request.set_accept_format('json') if args.service == 'ecs': request.set_Namespace('acs_ecs_dashboard') elif args.service == 'rds': request.set_Namespace('acs_rds_dashboard') #request.set_MetricName('CPUUtilization') request.set_MetricName(args.metric) response = client.do_action_with_exception(request) Datapoint = json.loads(json.loads(response)["Datapoints"])[-1] DatapointValue = Datapoint["Maximum"] print(DatapointValue)
こちらは個別にアイテムを追加していく方式となります。
アイテムは外部チェックで以下に示すキーで登録を行ってください。
アイテムの名称に関してはSBクラウドのドキュメントを参照ください。
cloudmonitor_zabbix.py[rds,-a,アクセスキー,-s,シークレットキー,-r,リージョン,-i,{HOST.HOST},-m,CPUUtilization]
これでAgentにもSNMPにも対応していないRDSに関してもZabbixで監視可能となります。
みなさんもお試しください。