07.052014

RabbitMQ mit Zabbix überwachen

RabbitMQ ist ein vielseitig einsetzbarer Queueserver, der das AMQP-Protokoll implementiert. AMQP-Client Bibliotheken gibt es in vielen verbreiteten und weniger verbreiteten Programmiersprachen. Nun lehren uns Hogan und Limoncelli das nur ein überwachter Service wirklich ein Service ist.

Zabbix ist ein vielseitiges Werkzeug zur Aufzeichnung und Überwachung von Netzwerkdiensten und -systemen. Im folgenden entwickeln wir mit Hilfe von JSON.sh und awk ein kleines Bashscript, dass die Erfassung von RabbitMQ Metriken über das HTTP-API realisiert. Wir Überwachen zwei Metriken über alle Queues: Speicherverbrauch und Anzahl unverarbeiteter Nachrichten. Der Zabbixagent wird auf dem selben Host laufen wie RabbitMQ, daher wird unser Script den guest-Zugang nutzen:

curl --silent http://guest:guest@localhost:15672/api/queues

Die unformatierte JSON Ausgabe parsen wir mit JSON.sh:

curl --silent http://guest:guest@localhost:15672/api/queues | \
	JSON.sh -l

Dann können wir die unverarbeiteten Nachrichten in den Queues aus der strukturierten Ausgabe von JSON.sh heraus filtern:

curl --silent http://guest:guest@localhost:15672/api/queues | \
	JSON.sh -l | \
	grep "\[.*,\"messages\"\]"

Um die Summe der Nachrichten über alle Queues zu bilden nutzen wir einfach awk:

curl --silent http://guest:guest@localhost:15672/api/queues | \
	JSON.sh -l | \
	grep "\[.*,\"messages\"\]" | \
	awk '{sum += $2} END {print sum}'

Für weitere aufzusummierende Daten könnten wir natürlich einfach diese Zeile wiederholen, schöner ist es aber, wir extrahieren eine allgemeine Funktion aus dieser Zeile und nutzen diese, um ein Script zu schreiben, dass wir dem Zabbixagent als Metriksammler übergeben können:

function sumCall() {
    local list="$1"
    local item="$2"
    curl --silent http://guest:guest@localhost:15672/api/${list} | ${INSTP}/JSON.sh -l \
        | grep "\[.*,\"${item}\"\]" | awk '{s+=$2} END {print s}'
}

Neben dem aufzusummierenden 'item' bekommt diese Funktion auch die uns interessierende Liste als Parameter, so können wir sie auch zur Überwachung von Nodes einsetzen. Der Aufruf dieser Funktion, um wie oben die Summe aller Nachrichten zu erhalten, ist:

sumCall queues messages

Als Funktion:

function rmq_messages() {
	sumCall queues messages
}

Die Summe der aktuell benötigten Bytes im Hauptspeicher erhalten wie dann so:

function rmq_memory() {
	sumCall queues memory
}

In einem Script könnten wir die Metriken über eine case-Anweisung auswählen:

case $param in
  messages)
    rmq_messages
    ;;
  memory)
    rmq_memory
    ;;
esac

Dann müssten wir aber für jeden neuen Test auch die case-Anweisung erweitern -- das lässt sich mit einer Hilfsfunktion eleganter lösen. Die Funktion 'is_function' ist erfolgreich, wenn der übergebene Name eine aufrufbare Funktion ist:

function is_function() {
    declare -f -F "${1}" > /dev/null
    return $?
}

Wenn wir als Konvention festlegen, dass die Testfunktionen mit dem Präfix 'rmq_' beginnen, können wir die Tests mit einer weiteren Hilfsfunktion ausführen:

function call() {
	  local fn="rmq_${1}"
    is_function "$fn" && $fn
}

Unser Zabbix Testscript, dass davon ausgeht, dass JSON.sh ausführbar im selben Verzeichnis liegt, sieht nun vollständig so aus:

#!/bin/bash
#
# author thomas.regner@joocom.de
#
# rabbitmq metric checks
#

# so that we can find the json parser
INSTP="$(dirname $(readlink -f ${0}))"
export INSTP

#-----------------
# helper
#-----------------

# return 0 (true) iff $1 is a function
function is_function() {
    declare -f -F "${1}" > /dev/null
    return $?
}

# call rmq_$1 if possible
function call() {
    local fn="rmq_${1}"
    is_function "$fn" && $fn
}

# sum up matching results
function sumCall() {
    local list="$1"
    local item="$2"
    curl --silent http://guest:guest@localhost:15672/api/${list} | ${INSTP}/JSON.sh -l \
        | grep "\[.*,\"${item}\"\]" | awk '{s+=$2} END {print s}'
}

#----------------
# checks
#----------------
function rmq_messages() {
    sumCall queues messages
}

function rmq_memory() {
    sumCall queues memory
}

#
# will exit with 1 if rmq_$1 isn't a function
#
call "$1"

Weitere Checks können einfach als 'rmq_METRIK' Funktionen angelegt werden und stehen automatisch zur Verfügung. Eine Zabbixagent Konfiguration sieht z.B. so aus:

UserParameter=queues.messages, /usr/share/zabbix/bin/check_rabbitmq.sh messages
UserParameter=queues.memory, /usr/share/zabbix/bin/check_rabbitmq.sh memory

Wenn das Script als 'check_rabbitmq.sh' mit 'JSON.sh' in '/usr/share/zabbix/bin/' abgelegt wurde.