18
16

More than 5 years have passed since last update.

Serverless Zabbix Sender for AWS

Last updated at Posted at 2018-12-21

監視について「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は素晴らしいです。これに限らず、色んな場面で活用していくべきでしょう。

18
16
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
18
16