Seite wählen

NETWAYS Blog

Check-Plugins für Monitoring – Professionelle Entwicklung mit NETWAYS

This entry is part 4 of 4 in the series So geht IT Consulting

Open Source Monitoring Lösungen wie Icinga sind mächtige Werkzeuge zur Überwachung der IT-Infrastruktur. Doch wie bei vielen „historisch aus Nagios gewachsenen“ Varianten, ist die reine Installation der Software nur der erste Schritt. Die volle Bandbreite der Möglichkeiten zeigt sich erst, wenn Check-Plugins hinzugefügt werden. Dadurch lassen sich (fast) alle Monitoringwünsche erfüllen. Und da es sich bei der Open Source Monitoring-Community um eine lang bestehende und sehr aktive Gruppe von Menschen handelt gibt es bereits umfassende Plugin-Sammlungen, die Heute den Standard bilden.

Sollte bei dem vielfältigen Angebot dennoch einmal nicht das richtige dabei sein oder du benötigst ein auf deinen speziellen Use Case zugeschnittenes Check-Plugin, hilft dir unsere interne Entwicklung weiter. Wir konzipieren und entwickeln dein Check-Plugin. Welche Programmiersprache wir dabei nutzen und wieso wir als Open Source Consulting Dienstleister überhaupt selbst entwickeln, erklärt dir unser Technical Service Manager und Entwickler Philipp Dorschner.

Aller Anfang ist schwer!

Wir bei NETWAYS Professional Services bieten neben unserem klassischen Kerngeschäft Open Soure-Consulting und Operations seit etwas über zwei Jahren auch Entwicklungsarbeiten an. Dies war jedoch nicht immer so. Einige meiner Kolleg:innen können sich noch an die Anfänge zurückerinnern, als sich bei einem Consultingtermin vor Ort oft neue und spezielle Herausforderungen herauskristallisierten, welche mit den „Standardmitteln“ nicht abgedeckt werden konnten. Kolleg:innen mit Programmiererfahrung konnten zwar dank ihrer Erfahrung ein paar Anforderungen umsetzen, dennoch musste man dabei immer den Spagat zwischen Consulting und Entwicklung bewältigen.
War das Problem so wichtig, dass die Zeit vom eigentlichen Consulting weggeht oder lässt man es wohl oder übel sein?

Ein weiterer Aspekt war die „Vermischung“ von Icinga und NETWAYS. Aufgrund der gemeinsamen Vergangenheit war es für Außenstehende oftmals verwirrend, dass NETWAYS und Icinga bereits seit mehr als vier Jahren keine zusammenhängende Unternehmung mehr ist. Die örtliche Nähe spielte dabei natürlich eine Rolle, aber das entwicklungstechnische stand im Vordergrund. Hin und wieder fragten Kunden während des Consulting an, ob wir nicht nachfragen können ob Icinga nicht ein spezielles Check Plugin für sie entwickeln könne, da wir ja sowieso „nebenan sitzen und zusammengehören würden“. Dadurch, dass Icinga das bestmögliche unternimmt um das Produkt Icinga mit immer neuen Features zu erweitern, sind die Ressourcen von Icinga für individuell angepasste Check Plugins nicht vorhanden. Da die NETWAYS aber als langjähriger Icinga Partner mit Plugins und anderen Entwicklungsarbeiten dem Icinga Projekt zuarbeitet und zu dessen Weiterentwicklung beiträgt, gelang manchmal dieser Spagat.

Wirft man einen Blick auf die oben genannten Punkte, wird klar, dass eine eigene Entwicklung bei NETWAYS durchaus Sinn ergibt. Somit wurde entschieden, dass wir bei NETWAYS selbst fortan kleinere bis mittelgroße Entwicklungsarbeiten für Kunden übernehmen.

Was genau bieten wir an?

Entwicklung im Allgemeinen ist natürlich ein sehr großes Spektrum und muss daher differenziert werden. Unser Hauptaugenmerk liegt auf Check-PluginsBei einem Check Plugin handelt sich um eine Softwarekomponente, die von einem Monitoring-System wie Icinga verwendet wird, um die Verfügbarkeit und Leistung von IT-Systemen und/oder Diensten zu überwachen. Dabei ist zu beachten, dass ein Check Plugin meist eine sehr spezifische Funktionalität wie z.B. das Überprüfen einer AWS EC2 Instanz erfüllt. Neben diesem spezifischem Beispiel gibt es jedoch viele weitere Möglichkeiten und Szenarien, die Check Plugins erfüllen kann.

Individuelle Beratung und Entwicklung

Wird Entwicklungsarbeit angefordert, startet der Consultant vor Ort oder unsere Sales-Abteilung zunächst einen internen Prozess. Hierbei werden die Rahmeninformationen gesammelt, um einen groben Überblick der eigentlichen Entwicklungsarbeit zu bekommen. Um überhaupt abzuschätzen, welche Unterstützung wir dir bei deinem Projekt geben können, wird im nächsten Schritt zusammen mit einem Techniker eine Machbarkeitsanalyse durchgeführt.
In manchen Fällen stellt sich leider heraus, dass die Anforderung den Rahmen der von uns möglichen Entwicklungsarbeit überschreiten. In solchen Fällen setzen wir uns direkt mit den Auftraggeber:innen zusammen und arbeiten gemeinsam an einer zufriedenstellenden Lösung.

Sind die Rahmenbedingungen abgesteckt und mit dem Techniker evaluiert, geht es an die Plugin-Programmierung. Die Wahl der Programmiersprache fällt bei uns auf GoLang. Der Vorteil einer kompilierbaren Programmiersprache wie GoLang ist, dass das Binary welches am Ende „herausfällt“ theoretisch auf fast allen Systemen lauffähig ist.

Die Entwicklung selbst findet bevorzugt auf GitHub statt. Das sollte keine Überraschung sein, denn wir bei NETWAYS leben den Open Source-Gedanken bei all unseren Projekten. Zudem können die Auftraggeber:innen so immer den aktuellen Entwicklungsstand einsehen. Wenn die Anforderungen des Plugins erfüllt und eine releasefähige Version vorhanden ist, durchläuft es unseren internen QA Prozess, um letzte Probleme auszumerzen.
Ein anschließender Test des Plugins zusammen mit dem Kunden schließt das Projekt ab.

Interessiert? Einfach anfragen!

Jetzt bist du an der Reihe! Nutzt du in deinem Unternehmen Icinga und benötigst ein auf deine Bedürfnisse zugeschnittenes Check-Plugin? Du hast andere Softwarelösungen wie z.B. Prometheus oder Elasticsearch und suchst nach einer zu deinen Wünschen angepassten Schnittstelle? Dann setze dich jetzt unverbindlich mit meinen Kolleg:innen in Verbindung. Gemeinsam lassen wir deine Vorstellungen Realität werden!

Neues zum go-check

Lang ist es her, dass ein Blogpost über das hauseigene NETWAYS go-check geschrieben wurde. Seitdem hat sich das go-check immer weiterentwickelt und wurde mit vielen verschiedenen Funktionen erweitert, sodass die Pluginentwicklung noch einfacher von der Hand geht.

Um einen kurzen Umriss zu geben, bei dem go-check handelt es sich um GO Package, mit dem es möglich ist, schnell und einfach, Icinga Plugins zu entwickeln.

Ein Beispiel dafür wäre das Evaluieren von Thresholds und anschließendem Anhängen von Performance data im richtigen Format:

package main

import (
	"github.com/NETWAYS/go-check"
	"github.com/NETWAYS/go-check/perfdata"
)

func main() {
	config := check.NewConfig()
	config.Name = "check_new"
	config.Readme = `example Plugin Readme`
	config.Version = "1.0.0"

	return_code := check.Unknown

	plugin_output := ""

	crit_thresh := config.FlagSet.StringP("crit-example", "c", "20", "Critical threshold")

	warn_thresh := config.FlagSet.StringP("warn-example", "w", "10", "Warning threshold")

	example_val := config.FlagSet.Float64("example-val", 0.0, "Example value")

	config.ParseArguments()

	crit, err := check.ParseThreshold(*crit_thresh)
	if err != nil {
		check.ExitError(err)
	}

	warn, err := check.ParseThreshold(*warn_thresh)
	if err != nil {
		check.ExitError(err)
	}

	if crit.DoesViolate(*example_val) {
		return_code = check.Critical
		plugin_output = "Value is critical!"
	} else if warn.DoesViolate(*example_val) {
		return_code = check.Warning
		plugin_output = "Value is warning!"
	} else {
		return_code = check.OK
		plugin_output = "Everything is fine"
	}

	p := perfdata.PerfdataList{{
		Label: "example_label",
		Value: *example_val,
		Warn:  warn,
		Crit:  crit,
		Uom:   "Mb",
	}}

	example_perf := perfdata.Perfdata{
		Label: "another_perfdata",
		Value: 1.0,
		Warn:  &check.Threshold{Upper: 50.0},
		Crit:  &check.Threshold{Upper: 100.0},
	}

	p.Add(&example_perf)

	check.ExitRaw(return_code, plugin_output, "|", p.String())
}

Nach dem Kompilieren des Programms können beispielhaft Parameter mitgegeben werden:

go build -o check_test main.go

./check_test -c 20 -w 9 --example-val 10
WARNING - Value is warning! | example_label=10Mb;9;20 another_perfdata=1;50;100

Darüber hinaus gibt es folgende weitere Funktionen:

  • Konvertierung von IEC zu SI Einheit
  • Performance data in Human-readable Format
  • Http-Mock, welcher http-requests und results „dokumentiert“ und in XML speichert
  • Evaluierung des „schlechtesten“ Ergebnisses (Value) in einem Array

Somit bietet go-check ein schnelles und komfortables Package für die Entwicklung eines Icinga 2 Check Plugins. Für weitere Releases und Aktualisierungen, einfach bei dem offiziellen NETWAYS Organisation vorbei schauen.

Ein Ausblick – Traefik Provider OpenStack

Bei unseren Online-Trainings von NETWAYS, werden jedem einzelnen Teilnehmer eine oder mehrere explizite VMs zur Verfügung gestellt, welche in der OpenStack-Umgebung von NWS gehostet werden. Hierbei benötigen die VMs jeweils öffentliche IPs, welche leider nicht unendlich zu Verfügung stehen. Aus dieser Prämisse heraus entstand die Idee, einen sog. reverse Proxy zu benutzen, welcher Anfragen von Clients an Webserver weiterleitet – in diesem Fall an die entsprechende VM.

Das Problem an dieser Stelle ist, dass es für Traefik noch keinen Provider für OpenStack gibt. Daher wurde ein experimenteller openstack-provider entwickelt, welchen ich hier kurz vorstelle.

 

Funktionsweise des openstack-providers

Der openstack-traefik-provider benutzt den http-provider von Traefik um die VMs/Instanzen innerhalb eines OpenStack-Projektes zu suchen/finden. Damit Traefik die Informationen über die Instanzen erhält, wird ein OpenStack-Go-Client innerhalb eines Docker-Containers gestartet, welcher anschließend über die URL http://openstack:8080/traefik (diese URL kann im Code selbst angepasst werden), alle Informationen des Projektes über ein HTTP-Response zur Verfügung stellt. Traefik selbst pollt alle 10 sek. von dieser Adresse, um die Routen, Middlewares, etc. zu aktualisieren.

 

Beispielhafte Konfiguration

Jeglicher HTTPS-Traffic soll am Proxy terminiert werden, sodass innerhalb des internen Netzwerks HTTP benutzt wird:

main.go
[...]
settings := discovery.DefaultSettings
	settings.DefaultRule = "Host(`{{ .Name }}.trainig.netways.de`)"
	settings.DefaultEnable = true
	settings.AddressType = "floating"
	settings.DefaultLabels = map[string]string{
		// HTTP
		"traefik.http.routers.{{ .Name }}-http.service":                   "{{ .Name }}-http",
		"traefik.http.routers.{{ .Name }}-http.rule":                      "Host(`{{ .Name }}.trainig.netways.de`)",
		"traefik.http.routers.{{ .Name }}-http.entrypoints":               "http",
		"traefik.http.services.{{ .Name }}-http.loadBalancer.server.port": "80",
		"traefik.http.routers.{{ .Name }}-http.middlewares":               "http-to-https",

		// HTTPS
		"traefik.http.routers.{{ .Name }}-https.service":                   "{{ .Name }}-https",
		"traefik.http.routers.{{ .Name }}-https.rule":                      "Host(`{{ .Name }}.trainig.netways.de`)",
		"traefik.http.routers.{{ .Name }}-https.entrypoints":               "https",
		"traefik.http.services.{{ .Name }}-https.loadBalancer.server.port": "80",
		"traefik.http.routers.{{ .Name }}-https.tls":                       "true",

		// Redirect HTTP to HTTPS
		"traefik.http.middlewares.http-to-https.redirectscheme.scheme":    "https",
		"traefik.http.middlewares.http-to-https.redirectscheme.permanent": "true",
	}
[...]

 

Anschließend muss über den Befehl docker-compose up der Docker-Container mit der neuen Konfiguration gestartet werden und die Routen + Middleware sollten im Frontend zu sehen sein:

 

Da das Projekt sich derzeit noch in Entwicklung befindet, würde ich mich über Vorschläge, PullRequests oder Kritik von der Open Source Community sehr freuen!

Sobald das Projekt abgeschlossen ist, werde ich auch davon berichten!

 

Python – Generator

Wer schon einmal eine etwas größere Datei, bspw. 1Gb, mit dem Editor VIM geöffnet hat, der weis, wie lange dies dauern kann. Das kommt daher, dass diese Datei zunächst komplett in den Arbeitsspeicher geladen werden muss. So ähnlich verhält es sich, wenn in Python eine Variable „befüllt“ wird, welche anschließend Speicherplatz im Arbeitsspeicher belegt. Bei der heutigen Hardware stellen 1 Gb große Variablen kein Problem dar, aber was passiert wenn diese deutlich größer sind und zusätzlich eine gute Performance benötigt wird? In diesem Fall empfiehlt es sich, auf einen Generator zurückzugreifen. Ein Generator liefert lapidar gesagt die Ergebnisse einer Funktion „häppchenweise“ und nicht als riesiges Stück zurück. Syntaktisch unterscheidet sich ein Generator von einer „normalen“ Funktion nicht großartig, wie hier zu sehen:

def a_function():
  yield x
  yield y
  yield z

Bei der Funktionsdefinition wird lediglich das Schlüsselwort return durch yield ersetzt. Der große Unterschied zu einer „normalen“ Funktion dabei ist, dass sich die Funktion mit yield nicht sofort beendet. Somit erhält man, vereinfacht gesagt, ein Objekt (Iterator) als Rückgabewert, über das man iterieren kann.

Zur besseren Veranschaulichung eines Generators werde ich eine exemplarische Datenbankabfrage aufzeigen:

Hinweis: Ich habe eine Beispieldatenbank genommen.

„normale“ Funktion
SELECT-Statement und anschließende Rückgabe des gesamten Ergebnisses:

def select_list():
    result = []

    db = mysql.connector.connect(host='127.0.0.1',
                                 user='thomas',
                                 password="4nD3r5on",
                                 db='employees')

    cursor = db.cursor()

    cursor.execute('select * from employees')

    for row in cursor:
        result.append(row)

    return result

Die Funktion „select_list()“ liefert eine Liste zurück:

print(select_list())

[...]
(10016,'1961-05-02','Kazuhito','Cappelletti','M','1995-01-27'),
(10017,'1958-07-06','Cristinel','Bouloucos','F','1993-08-03'),
(10018,'1954-06-19','Kazuhide','Peha','F','1987-04-03'),
[...]

„generator“ Funktion
Im Gegensatz zum obigen Beispiel wird keine Liste mit den Werten befüllt, sondern jeder Wert einzeln durch yield zurückgegeben:

def select_generator():
    db = mysql.connector.connect(host='127.0.0.1',
                                 user='thomas',
                                 password="4nD3r5on",
                                 db='employees')

    cursor = db.cursor()

    cursor.execute('select * from employees')

    for row in cursor.fetchmany(1000):
        yield row

Die Funktion „select_generator()“ liefert keine Liste zurück, sondern einen Generator:

print(select_generator())

<generator object select_generator at 0x10ba165d0>

Durch anschließendes Iterieren des Generatorsobjektes werden die gewünschten Werte ausgegeben:

for row in select_generator():
        print(row)

[...]
(10016,'1961-05-02','Kazuhito','Cappelletti','M','1995-01-27'),
(10017,'1958-07-06','Cristinel','Bouloucos','F','1993-08-03'),
(10018,'1954-06-19','Kazuhide','Peha','F','1987-04-03'),
[...]

Wo liegt nun der Vorteil eines Generators? Das zeigt sich erst bei Benchmark-Tests, welche im folgenden Beispiel die Performance der jeweiligen Funktion aufzeigt. Die SELECT-Statements sind bei beiden Beispielen dieselbigen, werden aber nicht ausgegeben, es wird nur eine Variable initialisiert:
select_list()

print('Memory (Before): {} Mb'.format(
    psutil.Process(os.getpid()).memory_info().rss / 1000000))
    
t1_start = perf_counter()
test_list = select_list()
t1_stop = perf_counter()
    
print('Memory (After) : {} Mb'.format(
    psutil.Process(os.getpid()).memory_info().rss / 1000000))

print("Elapsed time: {0} seconds".format(t1_stop-t1_start))

select_generator()

print('Memory (Before): {} Mb'.format(
    psutil.Process(os.getpid()).memory_info().rss / 1000000))

t1_start = perf_counter()
test_generator = select_generator()
t1_stop = perf_counter()

print('Memory (After) : {} Mb'.format(
    psutil.Process(os.getpid()).memory_info().rss / 1000000))

print("Elapsed time: {0} seconds".format(t1_stop-t1_start))

select_list():

Memory (Before): 11.776 Mb
Memory (After) : 124.960768 Mb
Elapsed time: 8.311030800000001 seconds

 

select_generator():

Memory (Before): 11.698176 Mb
Memory (After) : 11.718656 Mb
Elapsed time: 4.869999999934649e-07 seconds

Die Zahlen sprechen für sich. Beim Generator bleibt der Verbrauch des Arbeitsspeichers so gut wie unverändert, da dieser beim Ausführen des Codes nicht „alle Werte speichert“, sondern jeden einzelnen Wert ab dem Schlüsselwort yield zurückgibt. Dadurch ergibt sich auch die immense Geschwindigkeit.
An dieser Stelle ist noch zu erwähnen, dass alle Vorteile eines Generators verloren gehen, wenn man diesen in ein Liste umwandelt.

Quelle: https://media.giphy.com/media/yUrUb9fYz6x7a/giphy.gif

Go – Automatische Package Updates

Durch die Einführung des GO-Module, welches ein eigenes Dependency Management für das GO-Ökosystem ist, erleichtert es dem Programmierer, die importieren Go-Packages im Projekt aktuell zu halten. Dabei muss der Programmierer diese Updates immer manuell auslösen, was über folgende Befehle gemacht werden kann:

go get -u ./...
go mod tidy

Dabei wird der aktuelle Stand aller importierten Packages heruntergeladen. Das Problem hierbei ist – wie oben erwähnt – dass der Programmierer dies manuell auslösen muss. Bei einem Projekt ist das kein Problem – sind es aber mehrere Projekte, können womöglich manche Updates vergessen werden oder es ist dem Programmierer nicht bewusst, dass Updates anstehen. Aus diesem Grund bietet sich eine Automatisierung an, genauer gesagt handelt es sich hierbei um den Dependabot.

Was kann der Dependabot?

Der Dependabot selbst ist auch ein Dependency Management Tool, welches in GitHub integriert werden kann. Dependabot sucht automatisch nach Manifestdateien und prüft dort die eingetragenen Versionen der Package-Abhängigkeiten. Wurde ein Update gefunden, erstellt Dependabot automatisch einen Pull Request, um die Packages zu aktualisieren.

Um eine Integration in GitHub zu erhalten, muss lediglich eine dependabot.yml im Ordner .github des Repositories angelegt werden:

.github/dependabot.yml

In dieser Datei kann nun das Verhalten des Bots konfiguriert werden:

version: 2
  updates:
  - package-ecosystem: gomod
    directory: "/"
  schedule:
    interval: weekly
    time: '10:00'
  open-pull-requests-limit: 10
  • package-ecosystem: In diesem Fall gomod. Kann je nach Programmiersprache angepasst werden
  • directory: Der Ort der Manifestdatei. In diesem Fall die go.mod
  • schedule.interval: Der Zyklus, wie oft auf Updates geprüft werden soll
  • open-pull-requests-limit: Limitiert die Anzahl der Pull Requests, die Dependabot für das jeweilige Repository erstellt

Die komplette Liste an Konfigurationseinstellungen kann auf der Projektseite von Dependabot gefunden werden.

Anschließend überprüft Dependabot alle Abhängigkeiten und erstellt selbstständig Pull Request mit den aktuellen Packages:

Der Pull Request kann im Anschluss gemerged werden und der aktuelle Stand der Packages ist gesichert.

Du möchtest mehr dazu wissen?

Wenn ich mit diesem Blog das Interesse an Versionsverwaltungssoftware geweckt habe, kann ich nur die NETWAYS-Trainings empfehlen. Dort werden die Grundlagen einer Versionskontrolle für Softwareprojekte auf Basis von Git beigebracht.

 

Bildquelle: Dependabot-Core