監視について「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/
使い方は超シンプルで、リンク先で書かれている通りにコードを書くだけです。
構成図
Python版Zabbix SenderをAWS Lambdaで動かして、Zabbixマネージャーにメトリクス値を送ることを考えました。構成図は以下の通りです。
コード
Lambda関数を組み立てていきます。
Event
CloudWatch Eventsを使ってLambdaを起動しますので、Eventで監視対象やIAM Roleの値を与えます。おまけかもしれませんが、しれっとクロスアカウントにも対応させてしまいます。
{
"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を実行します。
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式には以下を入力します。
*/5 * * * ? *
ターゲット
もちろん Lambda関数
を指定します。 入力の設定
では 定数(JSONテキスト)
を選択し、先ほどのEventで記載したJSONを入力します。
ここまで実施すれば、Serverless Zabbix Senderが動き始めます。
VPC Lambdaとして動かす
VPC LambdaでServerless 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になりました。
最後に
Serverless Zabbix Senderが動き始めて、Zabbixマネージャーでメトリクス値が次々に更新される様子を眺めつつ、これが全てServerlessで動いて送られているんだと考えると、なかなかに爽快な気分です。しかも、Lambdaで実行していますから、よほど大規模かつ高頻度でない限りは、無料の範囲内でなんとかできてしまいます。もう、どこかのサーバのリソースに相乗りする必要も、お金を出してZabbix Sender専用サーバなどと頑張る必要もありません。Serverless Zabbix Senderの後ろには、AWSの強大なコンピューティングリソースが控えており、どれだけ大規模になろうとも、確かに支えてくれます。Serverlessのパワーを頼ることにより、Zabbixマネージャーは、自身に本来割り当てられるはずのリソースを存分に使えるようになります。Serverlessの確かなパワーが、Zabbixを使った監視と運用をリッチにしてくれます。Serverlessは素晴らしいです。これに限らず、色んな場面で活用していくべきでしょう。