Zabbix PowerShell JSON und LLD per system.run
Aus znilwiki
Changelog:
- 12.10.2023 erste Version
Vorwort
Ich wollte bei einem Kunden eine Überwachung eines Failoverclusters einrichten - und wollte weder extra dafür ein Skript schreiben noch den Agenten per UserParameter um Funktionen erweitern.
Ich musste etwas probieren, aber dann war es doch ganz leicht (wenn man es erst einmal verstanden hat).
Konfiguration des ZabbixAgenten
Ich setze hier den ZabbixAgent2 in der Version 6.0 ein, der normale Agent (also nicht 2) nutzt die gleichen Einstzellungen.
In der Konfigurationsdatei für den Agenten müssen/sollten folgende Optionen gesetzt sein:
DenyKey=system.run[rmdir] AllowKey=system.run[*]
Das erlaubt jeden Befehl per system.run
außer dem Befehl rmdir
.
Alternativ könnte ihr auch nur PowerShell erlauben: AllowKey=system.run[powershell]
Nach der Änderung müsstet Ihr den Dienst des Agenten einmal neu starten.
Konfiguration des RAW-Items
Hier die einzelnen Schritte bis zum fertigen RAW-Item.
Als RAW-Item bezeichne ich das Item welches später das JSON holt. Dieses JSON nutzen wird dann sowohl für das Discovery / LLD als auch um die anderen Werte in Items zu schreiben. Etwas was ich an Zabbix wirklich genial finde, wir stellen nur eine Frage an das Zielsystem und füllen duzende Items mit der Antwort.
PowerShell-Befehl austüfteln
Habt Geduld - ich erkläre den Aufbau des PowerShell Befehls nicht ohne Grund so genau!
Zunächst habe ich auf dem Zielsystem natürlich den PowerShell-Befehl ausgeklingelt. In diesem Fall will ich die vorhandenen Clusterknoten ermitteln und den Status dann per Items abfragen.
Gestartet habe ich zunächst mit dem Befehl
Get-ClusterNode -Cluster FILECLUSTER
Ausgabe:
Name State Type ---- ----- ---- FILESERVER-A Up Node FILESERVER-B Up Node
Das ist noch etwas Dünn, wir wollen alle Informationen, deshlab
Get-ClusterNode -Cluster FILECLUSTER| Select-Object *
Ausgabe:
BuildNumber : 20348
Cluster : FILECLUSTER
CSDVersion :
Description :
DrainStatus : NotInitiated
DrainTarget : 4294967295
DrainErrorCode : 0
DynamicWeight : 0
Id : 1
MajorVersion : 10
MinorVersion : 0
Name : FILESERVER-A
NeedsPreventQuorum : 0
NodeHighestVersion : 720900
NodeInstanceID : 00000000-0000-0000-0000-000000000001
NodeLowestVersion : 720900
NodeName : FILESERVER-A
NodeWeight : 1
FaultDomain : {Site:Site 192.168.0.0/24, Rack:, Chassis:}
Model : VMware7,1
Manufacturer : VMware, Inc.
SerialNumber : VMware-42 19 37 83 8b 83 e6 18-ad b3 73 bf da eb cb e1
State : Up
StatusInformation : Normal
Type : Node
UniqueID : ad2d4545-37b6-4c2c-b78f-9ecf72cd459c
DetectedCloudPlatform : None
BuildNumber : 20348
Cluster : FILECLUSTER
CSDVersion :
Description :
DrainStatus : NotInitiated
DrainTarget : 4294967295
DrainErrorCode : 0
DynamicWeight : 1
Id : 2
MajorVersion : 10
MinorVersion : 0
Name : FILESERVER-B
NeedsPreventQuorum : 0
NodeHighestVersion : 720900
NodeInstanceID : 00000000-0000-0000-0000-000000000002
NodeLowestVersion : 720900
NodeName : FILESERVER-B
NodeWeight : 1
FaultDomain : {Site:Site 192.168.0.0/24, Rack:, Chassis:}
Model : VMware7,1
Manufacturer : VMware, Inc.
SerialNumber : VMware-42 19 2f 2a ed fa 97 ca-39 76 ff 79 0f fc 0e 78
State : Up
StatusInformation : Normal
Type : Node
UniqueID : 5f4e5ff5-1452-47fb-b9f8-b919d7815a39
DetectedCloudPlatform : None
Super, eine Frage, alle Antworten, z.B. der State
Damit wir das in Zabbix besser weiter verarbeiten können brauchen wir ein JSON, also Umwandeln:
Get-ClusterNode -Cluster FILECLUSTER| Select-Object * | ConvertTo-Json
Die Ausgabe ist sehr umfangreich, auch die Unterobjekte wie Cluster
und FaultDomain
werden noch mal extra aufgeschlüsselt:
"NodeWeight": 1, "FaultDomain": [ "Site:Site 192.168.0.0/24", "Rack:", "Chassis:" ], "Model": "VMware7,1", "Manufacturer": "VMware, Inc.",
Das brauche ich in diesem Fall gar nicht. Die Tiefe beim Aufschlüssel lässt sich begrenzen, also
Get-ClusterNode -Cluster FILECLUSTER | Select-Object * | ConvertTo-Json -Depth 1
und schon ist die Ausgabe wieder kürzer:
[
{
"BuildNumber": 20348,
"Cluster": "FILECLUSTER",
"CSDVersion": "",
"Description": "",
"DrainStatus": 0,
"DrainTarget": 4294967295,
"DrainErrorCode": 0,
"DynamicWeight": 0,
"Id": "1",
"MajorVersion": 10,
"MinorVersion": 0,
"Name": "FILESERVER-A",
"NeedsPreventQuorum": 0,
"NodeHighestVersion": 720900,
"NodeInstanceID": "00000000-0000-0000-0000-000000000001",
"NodeLowestVersion": 720900,
"NodeName": "FILESERVER-A",
"NodeWeight": 1,
"FaultDomain": "Site:Site 192.168.0.0/24 Rack: Chassis:",
"Model": "VMware7,1",
"Manufacturer": "VMware, Inc.",
"SerialNumber": "VMware-42 19 37 83 8b 83 e6 18-ad b3 73 bf da eb cb e1",
"State": 0,
"StatusInformation": 0,
"Type": 0,
"UniqueID": "ad2d4545-37b6-4c2c-b78f-9ecf72cd459c",
"DetectedCloudPlatform": 0
},
{
"BuildNumber": 20348,
"Cluster": "FILECLUSTER",
"CSDVersion": "",
"Description": "",
"DrainStatus": 0,
"DrainTarget": 4294967295,
"DrainErrorCode": 0,
"DynamicWeight": 1,
"Id": "2",
"MajorVersion": 10,
"MinorVersion": 0,
"Name": "FILESERVER-B",
"NeedsPreventQuorum": 0,
"NodeHighestVersion": 720900,
"NodeInstanceID": "00000000-0000-0000-0000-000000000002",
"NodeLowestVersion": 720900,
"NodeName": "FILESERVER-B",
"NodeWeight": 1,
"FaultDomain": "Site:Site 192.168.0.0/24 Rack: Chassis:",
"Model": "VMware7,1",
"Manufacturer": "VMware, Inc.",
"SerialNumber": "VMware-42 19 2f 2a ed fa 97 ca-39 76 ff 79 0f fc 0e 78",
"State": 0,
"StatusInformation": 0,
"Type": 0,
"UniqueID": "5f4e5ff5-1452-47fb-b9f8-b919d7815a39",
"DetectedCloudPlatform": 0
}
]
Das könnte man nun schon so in Zabbix übernehmen. Aber mich störte noch eines:
Aus
State : Up
wird im JSON ein
"State": 0,
0 heißt in diesem Fall wohl Up - aber ich weis gar nicht welche anderen Werte es gäbe. Und hätte deshalb an dieser Steller lieber den Text.
Dieses Umwandeln macht er aber nur wenn man nach JSON wandelt, würde man nach CSV wandeln passiert das nicht. Das machen wir uns zu nutze:
Get-ClusterNode -Cluster FILECLUSTER| Select-Object * | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json -Depth 1
Wir wandeln das Ergebnis also zurerst ins CSV-Format, dann wieder zurück und zuletzt doch ins JSON Format.
Und schon haben wir den Text statt der Zahlen:
[
{
"BuildNumber": "20348",
"Cluster": "FILECLUSTER",
"CSDVersion": "",
"Description": "",
"DrainStatus": "NotInitiated",
"DrainTarget": "4294967295",
"DrainErrorCode": "0",
"DynamicWeight": "0",
"Id": "1",
"MajorVersion": "10",
"MinorVersion": "0",
"Name": "FILESERVER-A",
"NeedsPreventQuorum": "0",
"NodeHighestVersion": "720900",
"NodeInstanceID": "00000000-0000-0000-0000-000000000001",
"NodeLowestVersion": "720900",
"NodeName": "FILESERVER-A",
"NodeWeight": "1",
"FaultDomain": "System.String[]",
"Model": "VMware7,1",
"Manufacturer": "VMware, Inc.",
"SerialNumber": "VMware-42 19 37 83 8b 83 e6 18-ad b3 73 bf da eb cb e1",
"State": "Up",
"StatusInformation": "Normal",
"Type": "Node",
"UniqueID": "ad2d4545-37b6-4c2c-b78f-9ecf72cd459c",
"DetectedCloudPlatform": "None"
},
{
"BuildNumber": "20348",
"Cluster": "FILECLUSTER",
"CSDVersion": "",
"Description": "",
"DrainStatus": "NotInitiated",
"DrainTarget": "4294967295",
"DrainErrorCode": "0",
"DynamicWeight": "1",
"Id": "2",
"MajorVersion": "10",
"MinorVersion": "0",
"Name": "FILESERVER-B",
"NeedsPreventQuorum": "0",
"NodeHighestVersion": "720900",
"NodeInstanceID": "00000000-0000-0000-0000-000000000002",
"NodeLowestVersion": "720900",
"NodeName": "FILESERVER-B",
"NodeWeight": "1",
"FaultDomain": "System.String[]",
"Model": "VMware7,1",
"Manufacturer": "VMware, Inc.",
"SerialNumber": "VMware-42 19 2f 2a ed fa 97 ca-39 76 ff 79 0f fc 0e 78",
"State": "Up",
"StatusInformation": "Normal",
"Type": "Node",
"UniqueID": "5f4e5ff5-1452-47fb-b9f8-b919d7815a39",
"DetectedCloudPlatform": "None"
}
]
Damit wäre der Befehl an dieser Stelle schon fertig. Diesen Befehl können wir problemlos in unser Item einfügen.
PowerShell-Befehl hat Kommas oder andere Sonderzeichen
Man könnte nun auch das Select-Object
anpassen das nur die nötigsten Informationen gesammelt werden:
Get-ClusterNode -Cluster FILECLUSTER | Select-Object NodeName,State | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json -Depth 1
Sammelt nur die Daten von NodeName,State
, die Ausgabe wäre:
[
{
"NodeName": "FILESERVER-A",
"State": "Up"
},
{
"NodeName": "FILESERVER-B",
"State": "Up"
}
]
Aber es gibt dann doch ein Problem. Wir wollen den PowerShell-Befehl ja in unser Item unter system.run[ ... ]
einfügen. Und dort wird das Komma für die Unterscheidung der Parameter in Zabbix genutzt. Das habe ich eine Weile probiert - das escapen geht an dieser Stelle nicht und auch mit " ... "
hatte ich Probleme. Dies Lösung in diesem Fall wäre folgende:
$myContent = "Get-ClusterNode -Cluster FILECLUSTER | Select-Object NodeName,State | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json -Depth 1" $base64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($myContent)) write-host $base64
Da kommt dann "Kauderwelsch":
RwBlAHQALQBDAGwAdQBzAHQAZQByAE4AbwBkAGUAIAAtAEMAbAB1AHMAdABlAHIAIABCAEUAUgBMAEkATgBDAEwAVQBTAFQARQBSACAAfAAgAFMAZQBsAGUAYwB0AC0ATwBiAGoAZQBjAHQAIABOAG8AZABlAE4AYQBtAGUALABTAHQAYQB0AGUAIAB8ACAAQwBvAG4AdgBlAHIAdABUAG8ALQBDAHMAdgAgAHwAIABDAG8AbgB2AGUAcgB0AEYAcgBvAG0ALQBDAHMAdgAgAHwAIABDAG8AbgB2AGUAcgB0AFQAbwAtAEoAcwBvAG4AIAAtAEQAZQBwAHQAaAAgADEA
Der aber als gültiger PowerShell-Befehl genutzt werden kann (z.B. in einer Eingabeaufforderung):
powershell.exe -noprofile -nologo -encodedCommand "RwBlAHQALQBDAGwAdQBzAHQAZQByAE4AbwBkAGUAIAAtAEMAbAB1AHMAdABlAHIAIABCAEUAUgBMAEkATgBDAEwAVQBTAFQARQBSACAAfAAgAFMAZQBsAGUAYwB0AC0ATwBiAGoAZQBjAHQAIABOAG8AZABlAE4AYQBtAGUALABTAHQAYQB0AGUAIAB8ACAAQwBvAG4AdgBlAHIAdABUAG8ALQBDAHMAdgAgAHwAIABDAG8AbgB2AGUAcgB0AEYAcgBvAG0ALQBDAHMAdgAgAHwAIABDAG8AbgB2AGUAcgB0AFQAbwAtAEoAcwBvAG4AIAAtAEQAZQBwAHQAaAAgADEA"
Ergibt die gleiche Ausgabe wie zuvor.
Gefunden habe ich die Idee dazu hier: https://serverthings.com/zabbix-running-powershell-scripts-on-hosts-with-system-run/
JSON verkleinern
ConvertTo-Json
liefert ja ein schön lesbares JSON. Es geht aber auch kompakter per
ConvertTo-Json -Compress
dann wird alles hintereinander geschrieben ohne neue Zeilen, also alles in einer Zeile:
Get-ClusterNode -Cluster FILECLUSTER | Select-Object NodeName,State | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json -Depth 1 -Compress
Ausgabe:
[{"NodeName":"FILESERVER-A","State":"Up"},{"NodeName":"FILESERVER-B","State":"Up"}]
Das eigentlich Item
Der Typ ist Zabbix agent oder Zabbix agent (actice), zum Testen ist der Agent ohne active einfacher weil wir den Wert dann vom Server aus anfordern können.
Der Key ist:
system.run[powershell.exe -noprofile -nologo "Get-ClusterNode -Cluster {$CLUSTERNAME}| Select-Object * | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json -Compress"]
Ich habe hier den Clusternamen als Makro gesetzt, das geht also ohne Probleme und wird von Zabbix ersetzt.
Der Typ ist Text, wir brauchen ansonsten keine Preprocessing oder anderes.
Nach der eingestellten Zeit oder per "Execute now" am Host sollte unser JSON eintreffen:
Das Discovery-Item
Auf Basis dieses JSON, welches in meinem Fall alle 10 Minuten aktualisiert wird, bauen wir uns ein Discovery:
Es ist also ein Dependent item welches auf unser RAW-Item zeigt.
Den Key können wir uns selbst beliebig ausdenken. Wichtig ist nun das Preprocessing:
Für eine LLD brauchen wir dessen speziellen Makronamen {#......}
, diese enthält unser JSON aber nicht.
Das JSON sieht so aus (Auschnitt):
[
{
"BuildNumber": "20348",
"Cluster": "FILECLUSTER",
"CSDVersion": "",
"Description": "",
"DrainStatus": "NotInitiated",
"DrainTarget": "4294967295",
"DrainErrorCode": "0",
"DynamicWeight": "0",
"Id": "1",
"MajorVersion": "10",
"MinorVersion": "0",
"Name": "FILESERVER-A",
"NeedsPreventQuorum": "0",
"NodeHighestVersion": "720900",
"NodeInstanceID": "00000000-0000-0000-0000-000000000001",
"NodeLowestVersion": "720900",
"NodeName": "FILESERVER-A",
"NodeWeight": "1",
"FaultDomain": "System.String[]",
"Model": "VMware7,1",
"Manufacturer": "VMware, Inc.",
"SerialNumber": "VMware-42 19 37 83 8b 83 e6 18-ad b3 73 bf da eb cb e1",
"State": "Up",
"StatusInformation": "Normal",
"Type": "Node",
"UniqueID": "ad2d4545-37b6-4c2c-b78f-9ecf72cd459c",
"DetectedCloudPlatform": "None"
}
]
Für die Items brauche ich die Werte von Id, NodeName, State, StatusInformationen, UniqueID und Cluster.
Diese ziehen wir mit einem JavaScript aus dem JSON und wandeln diese in die LLD-Makros um:
//2023-10-11 Bernhard.Linz@datagroup.de / Bernhard@znil.de
output = JSON.parse(value).map(function(mycluster){
return {
"{#CLUSTERNODEID}": mycluster.Id,
"{#CLUSTERNODENAME}": mycluster.NodeName,
"{#CLUSTERNODESTATE}": mycluster.State,
"{#CLUSTERNODESTATUSINFORMATION}": mycluster.StatusInformation,
"{#CLUSTERNODEUNIQUEID}" : mycluster.UniqueID,
"{#CLUSTERNODECLUSTER}": mycluster.Cluster
}})
return JSON.stringify({"data": output})