k.sasakiです。Oracle Cloud Infrastructure Advent Calendar 2024のDay13記事です。
監視やイベントのメール通知が、ユーザーフレンドリーな文面ではなく、JSONオブジェクトそのままの形で送られてくる場合があります。エラーの情報のほか、監視のメールとしては不要なメタ情報が含まれており、読みづらいため、非人道的にさえ思えます。
本記事では、今年を代表する非人道的なキャラクターであるハリソン山中にならい、プリミティブでフィジカルでフェティッシュにこの課題を解決していく、運用者の方むけの記事です。
目次
クラウドの通知で発生するJSONデータをZabbixに流す
クラウドの監視やイベントなどの通知の仕組みを全く利用しないというケースは、ほとんど見られません。
例えば、Zabbixを使用していても、クラウド側の機能でバックアップを実行する場合は、バックアップイベントの検知もクラウドで実装する。というような具合です。
こういったケースで、Zabbixなどの監視サービスにクラウド側から発生した監視アラートやイベントのデータを渡すことで、Zabbixのコンソールを使用して、通知メールの文面を必要な情報に整形したり、メンテナンスのカレンダー登録、詳細な正規表現を使用した監視機能を使用することができるようになります。
NotificationsからFuncionsを経由してZabbixトラッパーへ
データの流れは以下の通りです。
OCI Notifications
NotificationsからFunctionsをコールするように設定します。
トピックのサブスクリプションとして、作成したファンクションを指定するので、先にファンクションを作ってデプロイしておく必要があります。
他のサービスからNotificationsに連携する部分については割愛しますので、過去の記事や、OCIのドキュメントをご覧ください。
【OCI】君たちはどう通知るか – OCIログイン失敗イベントの通知
ボリューム・バックアップが失敗した場合に通知するイベントの使用
例えば、BaseDBの自動バックアップ失敗のイベントをEventsから、Notificationsに連携した場合、以下のような、最もプリミティブなJSONデータのメールが届きます。(idなど一部情報は置き換え)
{
"eventType" : "com.oraclecloud.databaseservice.automaticbackupdatabase.end",
"cloudEventsVersion" : "0.1",
"eventTypeVersion" : "2.0",
"source" : "DatabaseService",
"eventTime" : "2024-11-29T23:02:12Z",
"contentType" : "application/json",
"data" : {
"compartmentId" : "<compartmentId>",
"compartmentName" : "01_sasaki",
"resourceName" : "Automatic Backup",
"resourceId" : "<resourceId>",
"availabilityDomain" : "QiBz:AP-TOKYO-1-AD-1",
"freeformTags" : { },
"definedTags" : { },
"additionalDetails" : {
"timeCreated" : "2024-11-29T23:02:12Z",
"timeUpdated" : "2024-11-29T23:02:26Z",
"lifecycleState" : "FAILED",
"lifecycleDetails" : "Database <dbSystemId> backup failed due to configuration discrepancies.Please contact Oracle Support with work request: <eventid> for further investigation",
"dbSystemId" : "<dbSystemId>",
"dbHomeId" : "<dbHomeId>",
"dbUniqueName" : "<dbUniqueName>",
"dbVersion" : "19.23.0.0.0",
"databaseEdition" : "ENTERPRISE_EDITION",
"autoBackupsEnabled" : "true",
"recoveryWindow" : "9",
"backupType" : "Incremental",
"databaseId" : "<databaseId>",
"isCdb" : true
}
},
"eventID" : "<eventID>",
"extensions" : {
"compartmentId" : "<compartmentId>"
}
}
Functions
つづいて、Functionsです。
今回は、Oracle公式のOracle Functions ハンズオンでCloudShellから操作しました。
プライベートサブネットのZabbixマシンにデータを送る場合は、サービスゲートウェイの通信許可が必要らしいです。
また、CloudShellからAuthTokenを使用してレジストリにログインする箇所について、
以下のように/oracleidentitycloudservice/というドメイン名の記載が必要でした。
docker login -u '<テナント名>/oracleidentitycloudservice/<アカウント名>' nrt.ocir.io
チュートリアルが完了したら、今回はpythonで作成したので、pythonで初期化を行い、proxy-zabbix/func.pyのファイルにコードを書き込みます。
cd ..
fn init --runtime python proxy-zabbix
cd proxy-zabbix
nano func.py
pythonコードは、加工などはせず、ファンクションからZabbixトラッパーへ、TCPコネクションを貼ってデータを全部流すという、最もフィジカルな実装です。
*データの順序性は重要視されないためStreamsは採用なし
トラッパーへのデータの送信のために、トラッパー用のヘッダと、Zabbix側で設定したホスト名や、トラッパーアイテムのパラメータとして指定する、Key情報が必要です。
protocol_header = b"ZBXD\x01"
"host": "Zabbix server"
"key": "oci.notification.message"
import logging
import time
import socket
import struct
import sys
import json
from io import BytesIO
# ログ設定
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger = logging.getLogger()
def send_to_zabbix(host, port, zabbix_payload):
"""Zabbixトラッパーにデータを送信"""
try:
# JSONデータを作成
json_data = json.dumps(zabbix_payload)
protocol_header = b"ZBXD\x01"
data_length = struct.pack('<Q', len(json_data))
payload = protocol_header + data_length + json_data.encode('utf-8')
# TCP接続を確立してデータ送信
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
logger.info(f"Connecting to Zabbix server at {host}:{port}")
sock.connect((host, port))
sock.sendall(payload)
response = sock.recv(1024)
logger.info(f"Response from Zabbix: {response.decode('utf-8')}")
except Exception as e:
logger.error(f"Failed to send data to Zabbix: {e}")
raise
def handler(ctx, data):
"""OCI Functionsハンドラ"""
logger.info("Function triggered")
try:
# BytesIOデータを文字列に変換
if isinstance(data, BytesIO):
logger.info("Input data is of type BytesIO. Decoding to string.")
data = data.getvalue().decode("utf-8")
# データが空の場合を確認
if not data.strip():
logger.warning("Received empty payload or whitespace-only data")
return json.dumps({"error": "Empty or invalid payload received"})
logger.info(f"Raw input data: {data}")
# Zabbixに送信するデータを準備
zabbix_payload = {
"request": "sender data",
"data": [
{
"host": "Zabbix server", # Zabbixのホスト名
"key": "oci.notification.message", # Zabbixのアイテムキー
"value": data, # メッセージ全体をそのまま渡す
"clock": int(time.time()) # 現在時刻をUnixエポック形式で
}
]
}
# Zabbixに送信
logger.info(f"Zabbix payload: {zabbix_payload}")
send_to_zabbix("172.168.0.150", 10051, zabbix_payload) # ZabbixサーバーのIPアドレス
logger.info("Data successfully sent to Zabbix")
return json.dumps({"message": "Data sent to Zabbix Trapper successfully"})
except Exception as e:
logger.error(f"Error processing the function: {e}")
return json.dumps({'error': str(e)})
Zabbixトラッパー
Zabbixトラッパーはアイテムの一種で、デフォルトだと10051ポートでLISTENして、
そこに入ってきたメッセージを受け取るという動作をします。
Functionで触れたkeyについてはZattibトラッパーのパラメータとして設定します。
oci.notification.message
動作確認を行う際は、Zabbixトラッパー側の設定がうまくいっていることから確認した方がいいので、zabbix_senderコマンドもおすすめです。
動作確認
ホストにアイテムを設定してメッセージが発行すると、アイテムのヒストリとして確認できます。(画像は最新データから、アイテムのヒストリを選択した画面)
うまくいかない場合は、以下の順番で構成要素が多くなるので、動作確認を行った構成より一つ手前構成で動作確認をお勧めします。
- zabbix_trapperからメッセージ発行
- Functionsからメッセージ発行
- Notificationsからメッセージ発行
- MonitoringやEventsからメッセージ発行
さいごに
以上で完了です。
Zabbixで受け取ったデータを整形して、運用しやすいように整形して、他の漢詩と馴染むよう設定できます。
これをどうフェティッシュにしていくかは、みなさん次第です!
最後までご覧いただきありがとうございました。
以下、私なりのフェティッシュな通知(アクションを起こさずにはいられない通知)ですので、ご査収ください。
赤子とシステムアラートは泣くのが仕事! ってね。
俺たちの仕事はこれからだ。
// JSONをいい具合に整形したデータ
可用性だの、稼働率だの、監視だの入っても結局あなたが動くかどうかです。
誰かが動くと思わず、あなたが動いてください。
// JSONをいい具合に整形したデータ