Quantcast
Viewing latest article 22
Browse Latest Browse All 25

Serverless Zabbix Sender for AWS

監視について「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 may be NSFW.
Clik here to view.
image.png

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

構成図

Python版Zabbix SenderをAWS Lambdaで動かして、Zabbixマネージャーにメトリクス値を送ることを考えました。構成図は以下の通りです。
Image may be NSFW.
Clik here to view.
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 may be NSFW.
Clik here to view.
image.png

最後に

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


Viewing latest article 22
Browse Latest Browse All 25

Trending Articles