Serverless Zabbix Sender

Serverless Zabbix Sender:

監視について「AWSと言えばCloudWatch」と言いたいところですが、プロセスの生死監視や、アラート発報時のサービス再起動の自動化等がやりたくて、CloudWatchだけだと色々と不都合なことがあります。

そこで、CloudWatchのメトリクス値をZabbixに投げて何もかもZabbixで見るという方法があり、以前から様々な場所で語られています。メトリクス値をZabbixに投げさえすれば、アラートの発報は当然として、発報後の処理もアクション等を使って大概のことができます。

Zabbixにメトリクス値を投げるには「Zabbix Sender」を実行する方法が簡単ですが、AWSとの連携を考えると『CloudWatchのメトリクスを取得してZabbix Senderを実行する』というスクリプト等が必要です。すぐ思いつくのは、Zabbixマネージャーが動いているサーバのOS上でこのスクリプトを実行すること(リソースの相乗り)ですが、大規模になればなるほど、スクリプトがリソースを食い、Zabbixマネージャーの稼働に影響が出るなど、本末転倒な状況になりかねません。

Zabbixマネージャーを含む各サーバの稼働に影響を与えないように、Zabbix Senderを実行するにはどうしたら良いのか。悩む中で閃いたのが、ServerlessでZabbix Senderを動かしたらいいんじゃないかというアイデアで、このアイデアが「Serverless Zabbix Sender」の開発に繋がっていきました。


前置き: Zabbix Senderとは

OSのコマンドラインで実行する場合の解説は、以下の通りです。

Zabbix sender は、パフォーマンスデータをZabbix サーバで処理するために送信するコマンドラインユーティリティです。

このユーティリティは通常、稼働とパフォーマンスのデータを定期的に送信するために長時間動作するユーザースクリプトの中で使用されます。

データを1つ送信する場合

例)Zabbix senderを使用してZabbixサーバーに値を送信する場合:

shell> zabbix_sender -z zabbix -s "Linux DB3" -k db.connections -o 43

オプション:

z - Zabbixサーバのホスト (IPアドレスでの指定でも可)

s - 監視対象のホスト名 (Webインタフェースで登録されたホスト名)

k - アイテムキー

o - 送信する値
https://www.zabbix.com/documentation/2.2/jp/manual/concepts/sender
通常はこの方法なのですが、Serverless Zabbix Senderでは、サーバもOSも使えないので、別の方法を考えなくてはなりません。
Serverless Zabbix Senderでは、PythonでZabbix Senderを実行することにしました。


PythonでZabbix Sender

Pythonだし、きっと探せばあるだろう、と思って探してみました。
ありました。Python版Zabbix Senderです。
https://pypi.org/project/ZabbixSender/


image.png


使い方は超シンプルで、リンク先で書かれている通りにコードを書くだけです。


構成図

Python版Zabbix SenderをAWS Lambdaで動かして、Zabbixマネージャーにメトリクス値を送ることを考えました。構成図は以下の通りです。


image.png



コード

Lambda関数を組み立てていきます。


Event

CloudWatch Eventsを使ってLambdaを起動しますので、Eventで監視対象やIAM Roleの値を与えます。おまけかもしれませんが、しれっとクロスアカウントにも対応させてしまいます。

event
{ 
    "target" : { 
        "awsAccountId" : "xxxxxxxxxxxx", 
        "awsIamRoleName" : "getMetricsWithCAA", 
        "awsServiceNameSpace" : "AWS/RDS", 
        "awsMetricName" : "CPUUtilization", 
        "awsMetricsDemensionName" : "DBInstanceIdentifier", 
        "awsMetricsDemensionValue" : "Zabbix", 
        "awsRegion" : "ap-northeast-1", 
        "Period" : 300, 
        "Statistics" : "Sum" 
    }, 
    "zabbix": { 
        "zabbixManagerIpAddress" : "xx.xxx.xxx.xx", 
        "zabbixManagerPort" : 10051, 
        "targetZabbixHostName" : "ZabbixDB", 
        "targetZabbixItemKey" : "aws.rds.cpu" 
    } 
} 


target の定義

監視対象および、監視対象が存在する環境に関する情報を定義します。

Key 意味
awsAccountId AWSのアカウントID (12桁)
awsIamRoleName AssumeRoleするIAMロール名
awsServiceNameSpace サービスの名前空間 (AWS/RDS等)
awsMetricName メトリクス名 (CPUUtilization等)
awsMetricsDemensionName ディメンション名 (DBInstanceIdentifier等)
awsMetricsDemensionValue ディメンションの値 (任意の値)
awsRegion リージョン (ap-northeast-1等)
Period 期間
Statistics 統計 (詳しくはこちら)


zabbix の定義

Zabbix Managerに関する情報を定義します。

Key 意味
zabbixManagerIpAddress ZabbixマネージャーのIPアドレス
zabbixManagerPort Zabbixマネージャーの受信ポート
targetZabbixHostName Zabbixマネージャーに登録されているホスト名
targetZabbixItemKey Zabbixアイテムのキー


lambda_handler.py

AssumeRoleして、CloudWatchのメトリクス値を取得して、Zabbix Senderを実行します。

lambda_handler
from ZabbixSender import ZabbixSender, ZabbixPacket 
import boto3 
import datetime 
 
# AssumeRoleして、一時クレデンシャルを取得する関数 
def stsAssumeRole(awsAccountId, awsIamRoleName, awsRegion): 
    # 1. AssumeRoleするためのClient作成、基礎情報の定義 
    awsIamRoleArn = "arn:aws:iam::" + awsAccountId + ":role/" + awsIamRoleName 
    awsSessionName = "CrossAccountZabbixSender" 
    awsStsClient = boto3.client('sts') 
 
    # 2. AssumeRole 
    response = awsStsClient.assume_role( 
        RoleArn=awsIamRoleArn, 
        RoleSessionName=awsSessionName 
    ) 
 
    # 3. 一時クレデンシャルの取得 
    awsSession = boto3.Session( 
        aws_access_key_id=response['Credentials']['AccessKeyId'], 
        aws_secret_access_key=response['Credentials']['SecretAccessKey'], 
        aws_session_token=response['Credentials']['SessionToken'], 
        region_name=awsRegion 
    ) 
 
    # 4. 一時クレデンシャルを返す 
    return awsSession 
 
# CloudWatch Metricsを取得する関数 
def getMetricStatistics(awsSession, target): 
    # 1. AWS CloudWatch用Clientを生成 
    awsClient = awsSession.client('cloudwatch') 
 
    # 2. eventで指定した監視ターゲットのメトリクスを取得 
    ## getMetricStatisticsはUTCで時間指定する必要があるため、UTCタイムゾーンを生成 
    UTC = datetime.timezone(datetime.timedelta(hours=0), 'UTC') 
    ## StartTimeとEndTimeで期間を決め、getMetricStatisticsを実行 
    metricStatistics = awsClient.get_metric_statistics( 
                            Namespace = target["awsServiceNameSpace"], 
                            MetricName = target["awsMetricName"], 
                            Dimensions=[ 
                                { 
                                    'Name': target["awsMetricsDemensionName"], 
                                    'Value': target["awsMetricsDemensionValue"] 
                                } 
                            ], 
                            StartTime = datetime.datetime.now(UTC) - datetime.timedelta(seconds=target["Period"]), 
                            EndTime = datetime.datetime.now(UTC), 
                            Period = target["Period"], 
                            Statistics = [target["Statistics"]] 
                    ) 
 
    # 3. metricStatisticsをdictごと返す 
    return metricStatistics 
 
# Zabbixにメトリクス値を送信する関数 
def zabbixSender(dataPoint, zabbix): 
    # 1. Zabbix Managerを示すオブジェクトを取得 
    zabbixManager = ZabbixSender(zabbix["zabbixManagerIpAddress"], zabbix["zabbixManagerPort"]) 
 
    # 2. Zabbix Senderで送るパケットを作成 
    zabbixPacket = ZabbixPacket() 
    zabbixPacket.add(zabbix["targetZabbixHostName"],zabbix["targetZabbixItemKey"], dataPoint) 
 
    # 3. Zabbix Senderでパケット(データポイント)を送信 
    zabbixManager.send(zabbixPacket) 
 
# lambda_handler 
def lambda_handler(event, context): 
    # 1. stsAssumeRoleを呼び、eventで指定したAWSアカウントから一時アクセスキー(awsSession)を取得 
    awsSession = stsAssumeRole(event["target"]["awsAccountId"],event["target"]["awsIamRoleName"],event["target"]["awsRegion"]) 
 
    # 2. 1.で得たawsSessionを利用し、CloudWatch Metricsを取得 
    metricStatistics = getMetricStatistics(awsSession, event["target"]) 
 
    # 3. 2.で得たmetricStatisticsからデータポイントだけを抜き出す 
    dataPoint = metricStatistics['Datapoints'][0][event["target"]["Statistics"]] 
 
    # 4. 3.で得たデータポイントをZabbix SenderでZabbix Managerに送る 
    zabbixSender(dataPoint, event["zabbix"]) 


トリガー

CloudWatch Eventsを設定して、一定間隔でこのServerless Zabbix Senderを実行するようにします。


イベントソース

画面の指示に従い作ればOKです。スケジュールCron式 の組み合わせです。例えば、5分間隔で実行する場合、Cron式には以下を入力します。

Cron式
*/5 * * * ? * 


ターゲット

もちろん Lambda関数 を指定します。 入力の設定 では 定数(JSONテキスト) を選択し、先ほどのEventで記載したJSONを入力します。

ここまで実施すれば、Serverless Zabbix Senderが動き始めます。


VPC Lambdaとして動かす

VPC LambdaServerless Zabbix Senderを実行し、プライベートIP間での通信でZabbixマネージャーにメトリクスを送信することもできます。Security Groupでしっかりアクセス制限もできるので現実的です。ただし、いくつか注意事項があります。まず、VPC Lambdaのコールドスタートを回避するために、実行間隔を長くしすぎないことです。次に、VPC Lambdaを実行するVPCのサイズを必ず大きめに確保し、VPC Lambda専用とすることです。VPC Lambdaに割り当てられるIPアドレスは自動で決まり、監視対象とメトリクスが増えれば増えるほどIPアドレスを消費しますから、他のVPCとは分け、リッチにIPアドレスを確保できるサイズにした方が良いでしょう。

VPC LambdaとしてServerless Zabbix Senderを実行する場合の構成図は以下の通りです。LambdaとZabbixマネージャー間の通信が、VPC Peeringになりました。



image.png



最後に

Serverless Zabbix Senderが動き始めて、Zabbixマネージャーでメトリクス値が次々に更新される様子を眺めつつ、これが全てServerlessで動いて送られているんだと考えると、なかなかに爽快な気分です。しかも、Lambdaで実行していますから、よほど大規模かつ高頻度でない限りは、無料の範囲内でなんとかできてしまいます。もう、どこかのサーバのリソースに相乗りする必要も、お金を出してZabbix Sender専用サーバなどと頑張る必要もありません。Serverless Zabbix Senderの後ろには、AWSの強大なコンピューティングリソースが控えており、どれだけ大規模になろうとも、確かに支えてくれます。Serverlessのパワーを頼ることにより、Zabbixマネージャーは、自身に本来割り当てられるはずのリソースを存分に使えるようになります。Serverlessの確かなパワーが、Zabbixを使った監視と運用をリッチにしてくれます。Serverlessは素晴らしいです。これに限らず、色んな場面で活用していくべきでしょう。

コメント

このブログの人気の投稿

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-04-30 23:37:32 RSSフィード2021-04-30 23:00 分まとめ(42件)

投稿時間:2023-02-05 02:09:04 RSSフィード2023-02-05 02:00 分まとめ(9件)