Icinga2 API und BitBar auf MacOs

preview1Wir wollen APIs, warum? Weil sie schnell, einfach zu integrieren und zu bedienen sind. Nun hat Icinga2 eine API und es entstehen ganz viele Möglichkeiten diese zu nutzen. Wir bauen uns Dashboards mit Dashing oder zeigen den Status von Hosts in Foreman an.

Ich bin letztens über ein Tool BitBar gestolpert, dieses Tool kann mit einfachen Skripten die eigene “Mac OS X menu bar” erweitern. Hierfür braucht es nur die richtige Formatierung der Ausgabe und BitBar generiert ein weiteres Dropdown Menu.

Ich hab mir die Icinga2 API zu nutze gemacht und eine kleine Erweiterung gebaut um mir den Status von Icinga2 in meiner Menubar anzuzeigen.
Im Menu wird dann der globale Status entweder in grün oder rot, abhängig davon ob Hosts “down” und “unhandled” sind, angezeigt.
Der Aufruf dafür kann der Adresszeile im Browser entnommen werden.
/icingaweb2/monitoring/list/hosts?host_state=1&sort=host_severity&host_unhandled=1

Wenn wir am Ende dann ein “&format=json” an die URL hängen, haben wir ein gängiges Format um das Ergebnis in jeglichen Applikationen zu verwenden.
[{"host_icon_image":"","host_icon_image_alt":"","host_name":"web01","host_display_name":"web01","host_state":"1","host_acknowledged":"0","host_output":"DOWN","host_attempt":"1\/3","host_in_downtime":"0","host_is_flapping":"0","host_state_type":"1","host_handled":"0","host_last_state_change":"1474556541","host_notifications_enabled":"1","host_active_checks_enabled":"0","host_passive_checks_enabled":"1"},

Mehr dazu gibts auf Github unter icinga2_api_examples oder natürlich in der Icinga2 Dokumentation.

Thilo Wening

Autor: Thilo Wening

Thilo hat bei NETWAYS mit der Ausbildung zum Fachinformatiker, Schwerpunkt Systemadministration begonnen und unterstützt nun nach erfolgreich bestandener Prüfung tatkräftig die Kollegen im Consulting. In seiner Freizeit ist er athletisch in der Senkrechten unterwegs und stählt seine Muskeln beim Bouldern. Als richtiger Profi macht er das natürlich am liebsten in der Natur und geht nur noch in Ausnahmefällen in die Kletterhalle.

atexit, oder wie man Python-Dienste nicht beenden sollte

Wer schon einmal einen Dienst mit Python realisiert hat, wird bereits vor der Aufgabe gestanden haben die Aufgaben die er verrichtet sauber und geordnet zu beenden. Python bietet einem hier vielerlei Lösungen an, darunter auch das atexit Modul. Für schnelle und simple Aufräum-Arbeiten ist dieses Modul wunderbar geeignet, nicht jedoch wenn Thread-Synchronisation und externe Kommunikation im Spiel ist. Warum das so ist und was die saubere Alternative ist, darum geht es heute in diesem Blogpost.

Wie der Name des Moduls und dessen Dokumentation bereits sagt, werden registrierte Routinen ausgeführt kurz bevor der Interpreter angehalten wird. Klingt erst einmal nicht besonders problematisch, ist es doch gerade was man haben möchte. Und es funktioniert sogar, solange keine Threads laufen. Dann werden die registrierten Routinen nicht aufgerufen. Das liegt daran, dass “normale” Threads explizit beendet werden müssen, sonst verhindern diese den Stopp des Interpreters. Damit das nicht passiert, kommt man möglicherweise auf folgende Idee:

t = threading.Thread(target=func)
t.daemon = True  # Avoids that python hangs during shutdown
t.start()

Ganz schlecht. Das mag möglicherweise den gewünschten Effekt haben und die Aufräum-Routinen können ihre Arbeit verrichten. Aber tatsächlich ist dies nur eine Lösung für ein Symptom, das eigentliche Problem ist noch immer nicht gelöst. Das wird einem spätestens klar, wenn es nicht mehr nur darum geht Threads zu beenden, sondern auch noch mit anderen über das Netzwerk verbundenen Diensten/Klienten eine saubere Unterbrechung der Kommunikation einzuleiten.

Denn was genau passiert wenn der Interpreter angehalten wird?

  1. Der MainThread wird sofort angehalten
  2. Offene File-Handles werden geschlossen
  3. Es wird gewartet dass alle nicht-Daemon Threads beendet wurden
  4. Die atexit-Routinen werden ausgeführt
  5. Garbage Collection
  6. Der Prozess wird beendet

Der zweite Punkt lässt einige vielleicht bereits aufhorchen, denn sockets sind nichts anderes als File-Handles. Wer nun immer noch denkt er könne in einem daemon-Thread die Netzwerk-Kommunikation mit seinem Gegenüber sauber beenden, ist auf dem Holzweg. Zum Zeitpunkt zu dem die atexit-Routinen laufen, sind bereits alle sockets unwiderruflich geschlossen.

Angenommen der Stopp des Dienstes wird ganz normal über SIGTERM initiiert, so könnte man nun alle atexit-Routinen in einen Signal-Handler verlagern. Dadurch würden sie laufen noch bevor der Interpreter angehalten wird, allerdings kommt man so sehr schnell wieder in die Bredouille, je nachdem welche Art von Arbeit der MainThread verrichtet. Denn da Signal-Handler asynchron im MainThread ausgeführt werden, ist das Risiko für ein Deadlock sehr groß. Kommen wir also zur erwähnten, sauberen Alternative: Feuer mit Feuer bekämpfen.

Was vormals in etwa so aussah:

class SomeDaemon:
    def start():
        # ...
        signal.signal(signal.SIGTERM, self._sigterm)
        atexit.register(self._atexit)
        # ...

    def _sigterm(self, signum, frame):
        sys.exit(0)

    def _atexit(self):
        # Alle Aufräum-Arbeiten

Kann ganz einfach, mit durchschlagendem Erfolg, so umgebaut werden:

class SomeDaemon:
    def start():
        # ...
        signal.signal(signal.SIGTERM, self._sigterm)
        atexit.register(self._atexit)
        # ...

    def _sigterm(self, signum, frame):
        threading.Thread(target=self._cleanup, name='CleanupThread').start()

    def _cleanup(self):
        # Komplexe Aufräum-Arbeiten

    def _atexit(self):
        # Einfache Aufräum-Arbeiten

Der “CleanupThread” sollte selbstverständlich keine Arbeit verrichten die unkontrolliert blockt. Denn dann sieht das ganze am Ende wieder so aus:

    def _sigterm(self, signum, frame):
        t = threading.Thread(target=self._cleanup, name='CleanupThread')
        t.daemon = True
        t.start()

Und der ganze Spaß geht von vorne los..

Johannes Meyer

Autor: Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

Bottle – Klein aber fein

Vor einiger Zeit war es notwendig für ein Kunden-Projekt dessen Umgebung bei uns nachzubilden. Es war von vornherein klar, dass es keine 1:1 Nachbildung sein muss (kann) und somit war es nur notwendig einige wenige Bestandteile einer REST Api zu simulieren. Da REST rein HTTP basiert ist und die zu simulierenden Aktionen nur GET waren, kam uns erst gar nicht in den Sinn ein großartiges PHP-Projekt ins Leben zu rufen und entschieden uns daher für eine schnelle Lösung mittels eines Standalone Python Skriptes.

Mir war Bottle schon seit einigen Jahren bekannt und deshalb noch als Werkzeug für schnelle und einfache Lösungen in Erinnerung. Tatsächlich eingesetzt hatte ich es allerdings noch nie, weshalb dies die Chance war es endlich einmal zu tun. Gesagt getan, runtergeladen und ausgeführt. Da Bottle allein auf der Standard-Library von Python basiert und mit Python 2.6+ wie auch Python 3.2+ kompatibel ist, funktionierte das sofort ohne Probleme. Und es hält was es verspricht!

Allein das erste Beispiel auf seiner Webseite zeigt bereits wie schnell ein Web-Server mit dynamischem Routing und Inhalten aufgesetzt wird:

from bottle import route, run, template

@route('/hello/<name>')
def index(name):
    return template('<b>Hello {{name}}</b>!', name=name)

run(host='localhost', port=8080)

Ja sogar Basic Authentication beherrscht es:

from bottle import route, run, template, auth_basic

def authenticate(username, password):
    return username == 'max' and password == 'mustermann'

@route('/hello/<name>')
@auth_basic(authenticate)
def index(name):
    return template('<b>Hello {{name}}</b>!', name=name)

run(host='localhost', port=8080)

Ist das nicht toll? Ich nehme stark an, dass da noch mehr geht, aber für den Moment bin ich vollauf zufrieden. 🙂

Johannes Meyer

Autor: Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

Braintower SMS Gateway – Icinga Plugin verfügbar!

icinga_logo_200x69Trotz Weihnachten und dem bevorstehenden Jahreswechsel sind wir natürlich nicht untätig und stehts dabei unser Portfolio an Plugins weiter auszubauen. Heute möchte ich die Gelegenheit nutzen, um zum Jahresende ein nützliches Plugin für die Überwachung unserer Braintower Geräte mit Icinga vorzustellen.
Das kleine Python-Plugin bedient sich dabei der Statusseite des Gateways und liefert unter anderem Informationen über die Failed-Queue, die Signalstärke sowie die Uptime.

Will man das Gateways überwachen, ruft man das Plugin einfach mit folgenden Parametern auf:

./check_braintower -H 192.168.1.1 --signal-warning -85 --signal-critical -90

Die Werte der Parameter muss natürlich jeder auf seine Bedürfnisse anpassen 🙂

Als Ergebnis erhält man dann beispielsweise folgende Ausgabe:

BRAINTOWER OK - que: 0 failed: 0 signal: -83db total: 0 state: Idle load: 0;0.03;0.05 time: 1451320254 disk free: 647569408 uptime: 9 min, 0 users

Selbstverständlich kann man nicht nur die Signalstärke überwachen, sondern eine Vielzahl von Parametern. Mit –help erhält man hier eine schöne Erklärung über die Möglichkeiten:


usage: check_braintower [-h] -H HOSTNAME [-T TIMEOUT] [-Q QUEUE] [-F FAIL] [--signal-warning SIGNAL_WARNING] [--signal-critical SIGNAL_CRITICAL]

optional arguments:

-h, –help show this help message and exit
-H HOSTNAME, –hostname HOSTNAME The host address of the SMS Gateway
-T TIMEOUT, –timeout TIMEOUT Seconds before connection times out (default 10)
-Q QUEUE, –queue QUEUE The warning threshold for the amount of queued SMS (default 1)
-F FAIL, –fail FAIL The critical threshold for failed SMS (default 1)
–signal-warning SIGNAL_WARNING The warning threshold for the minimum signal strength (in db, default 0)
–signal-critical SIGNAL_CRITICAL The critical threshold for the minimum signal strength (in db, default 0)

 

Wer das Plugin ausprobieren möchte, kann dies natürlich gerne tun – wir freuen uns über Feedback! Verfügbar ist es auf Icinga Exchange und in unserem NETWAYS Git.

Christian Stein

Autor: Christian Stein

Christian kommt ursprünglich aus der Personalberatungsbranche, wo er aber schon immer auf den IT Bereich spezialisiert war. Bei NETWAYS arbeitet er als Senior Sales Engineer und berät unsere Kunden in der vertrieblichen Phase rund um das Thema Monitoring. Gemeinsam mit Georg hat er sich Mitte 2012 auch an unserem Hardware-Shop "vergangen".

Monthly Snap November: OSMC, Icon Fonts & Jaspersoft Studio

November presented an exciting 10th OSMC, featured Jasper reporting and offered DevOps tips for sys admins, developer and tinkerer.weekly snap

Beginning with events, our 10th Open Source Monitoring Conference with many special guests took place and Daniela reported on day one and Dirk summarized the other days of the conference while Jean-Marcel gave us an overview of his two favorite presentations.

Eric then described how to create Icon-Fonts and Alexander explained how to upgrade python-driven servers.

Lastly, Christoph looked at Jaspersoft Studio and Kay shared a guide to control home automation components with Rasperry PI and a web interface.

Stephanie Kotilge

Autor: Stephanie Kotilge

Steffi kümmert sich bei NETWAYS ums Office-Management und ist die gute Seele der Fabrik. Auch unsere Kunden kommen an ihr nicht vorbei und wollen dies natürlich auch nicht. Da sie ein überdurchschnittliches sprachliches Talent besitzt und neben Deutsch noch fliessend in Englisch und Französisch unterwegs ist, kümmert sie sich auch um einen Großteil der Übersetzungen. In diesem Sinn ... Merci

Jetzt wird der lahmen Schlange der Prozess gemacht

Python poweredEiner meiner Kollegen ist in diesem Beitrag bereits auf Python-betriebene Web-Server (WSGI) eingegangen. Ein mit make_server() erstellter Standard-WSGI-Server kann zur selben Zeit leider nur eine Anfrage bearbeiten, da weder Multithreading, noch Multiprocessing Verwendung finden. Mit dem Wissen wie es geht ist dies relativ einfach nachzurüsten und genau darauf will ich im folgenden eingehen.

Threads oder Prozesse?

Die Threadsicherheit von Python steht außer Frage. Die Umsetzung lässt allerdings zu wünschen übrig: Der Global Interpreter Lock verhindert gleichzeitiges Ausführen von Python-Bytecode in mehreren Threads. Somit sind Threads in Python mehr oder weniger für die Katz und es bleiben nur noch die Prozesse.

Diese können etwas umständlicher zu verwalten sein, bringen aber dafür zumindest einen wesentlichen Vorteil mit sich: Die einzelnen Prozesse sind voneinander komplett unabhängig.

Daraus folgt: Wenn ein Prozess abstürzt, bleibt der Rest davon unberührt. Daraus wiederum folgt: Wenn ein Prozess “amok läuft” kann er problemlos “abgeschossen” werden, um die System-Ressourcen nicht weiter zu belasten. (Im realen Leben undenkbar.)

Ein Server für alle Netzwerk-Schnittstellen

Wer weiß, dass seine Anwendung immer auf allen Netzwerk-Schnittstellen (oder nur auf einer) wird lauschen sollen, der hat ungleich weniger Aufwand bei der Implementierung:

from socket import AF_INET
from SocketServer import ForkingMixIn
from wsgiref.simple_server import make_server, WSGIServer

class ForkingWSGIServer(ForkingMixIn, WSGIServer):
    address_family = AF_INET

def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    yield 'Hallo Welt!'

if __name__ == '__main__':
    make_server('0.0.0.0', 8080, app, server_class=ForkingWSGIServer).serve_forever()

 

Erklärung

Nach der Python-üblichen Unmenge an Importen wird die Klasse ForkingWSGIServer definiert – ein WSGIServer kombiniert mit dem ForkingMixIn. Dies funktioniert einwandfrei, da WSGIServer von BaseHTTPServer.HTTPServer erbt und letztgenannter wiederum ein SocketServer.TCPServer ist. HTTPServer, WSGIServer und ForkingMixIn überschreiben völlig unterschiedliche Methoden und kommen sich somit nicht in die Quere.

Darauf folgt eine beispielhafte WSGI-Anwendung, die theoretisch durch alles erdenkliche ersetzt werden kann.

Schlussendlich wird ein ForkingWSGIServer erstellt und er wird angewiesen, für einen unbestimmten Zeitraum die beispielhafte WSGI-Anwendung auf Port 8080 anzubieten.

Arbeitsweise

Der Server wird für jede ankommende HTTP-Anfrage einen neuen Prozess starten und diesen sie bearbeiten lassen. Während dessen kann er bereits einen neuen Prozess für die nächste schon wartende Anfrage erzeugen, usw..

Den Server sauber herunterfahren

Wenn der oben beschriebene Server z.B. via Strg-C o.ä. beendet wird, werden alle aktiven Verbindungen abrupt abgebrochen. Wenn das unerwünscht ist, kann der Server wie folgt erweitert werden:

import os
import sys
from signal import signal, SIGTERM
if __name__ == '__main__':
    signal(SIGTERM, (lambda signum, frame: sys.exit()))
    
    try:
        make_server('0.0.0.0', 8080, app, server_class=ForkingWSGIServer).serve_forever()
    finally:
        try:
            while True:
                os.wait()
        except OSError:
            pass

 

Erklärung

Kurz vor dem Start des Servers wird ein Signal-Handler registriert. Dieser sorgt dafür, dass der Python-Interpreter sich sauber beendet (sys.exit()) wenn er ein TERM-Signal erhält.

Diese Art der Beendigung wird abgefangen, um vor dem tatsächlichen Stopp auf alle noch laufenden Kindprozesse zu warten.

Ergebnis

Strg-C funktioniert zwar immer noch nicht so wie es soll, aber dafür der kill-Befehl.

Ein Server pro Netzwerk-Schnittstelle

Obwohl die gerade beschriebene Implementation einfach und schnell zu bewerkstelligen ist, kommt es vor, dass eine Anwendung auf mehreren, aber nicht auf allen Netzwerk-Schnittstellen lauschen soll. Hierzu muss der Haupt-Prozess für jede Schnittstelle einen weiteren Prozess, den eigentlichen WSGI-Server, erstellen.

import os
import sys
from multiprocessing import Process
from signal import signal, SIGTERM
from socket import AF_INET, AF_INET6
from SocketServer import ForkingMixIn
from time import sleep
from wsgiref.simple_server import make_server, WSGIServer


def print_msg(prefix, msg):
    print >>sys.stderr, '<{0}> {1}'.format(prefix, msg)


class ForkingWSGIServer(ForkingMixIn, WSGIServer):
    address_family = AF_INET


class ForkingWSGI6Server(ForkingWSGIServer):
    address_family = AF_INET6


def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    yield 'Hallo Welt!'


def run_forking_server(host, port, address_family=AF_INET):
    pid = os.getpid()

    print_msg(pid, 'Serving on {0}:{1}..'.format(host, port))

    server_class = ForkingWSGI6Server if address_family == AF_INET6 else ForkingWSGIServer
    try:
        make_server(host, port, app, server_class=server_class).serve_forever()
    finally:
        print_msg(pid, 'Shutting down..')

        try:
            while True:
                os.wait()
        except OSError:
            pass

        print_msg(pid, 'Exiting..')


if __name__ == '__main__':
    signal(SIGTERM, (lambda signum, frame: sys.exit()))

    processes = []

    port = 8080

    for (address_family, host) in (
        (AF_INET, '127.0.0.1'), (AF_INET6, '::1')
    ):
        p = Process(target=run_forking_server, args=(host, port, address_family))
        p.daemon = False
        p.start()
        processes.append(p)

    prefix = '{0} (root)'.format(os.getpid())

    print_msg(prefix, 'Waiting for SIGTERM..')
    try:
        while True:
            sleep(86400)
    finally:
        print_msg(prefix, 'Shutting down..')

        for p in processes:
            print_msg(prefix, 'Terminating {0}..'.format(p.pid))
            p.terminate()

        for p in processes:
            print_msg(prefix, 'Joining {0}..'.format(p.pid))
            p.join()

        print_msg(prefix, 'Exiting..')

 

Erklärung

Nach Registrierung des uns schon bekannten Signal-Handlers wird für jede Schnittstelle ein Prozess erstellt. Danach legt sich der Haupt-Prozess schlafen und wartet darauf, von einem SIGTERM aufgeweckt zu werden. Wenn das eintritt, terminiert er alle seine Kindprozesse und wartet, bis sie sich beendet haben.

Ein jeder dieser Kindprozesse startet wie gehabt einen WSGI-Server und wartet auf die Beendigung seiner Kindprozesse wenn ein SIGTERM eintrifft.

Fazit

Der vorgestellte Code ist rein demonstrativer Natur und entsprechend minimalistisch aufgebaut. Der Ursprung aller Dinge ist klein und dementsprechend ist noch viel Luft nach oben. Viel Spaß beim ausprobieren!

Vorausgesetzte Python-Version: 2.6 oder 2.7

Alexander Klimov

Autor: Alexander Klimov

Alexander hat Ende 2013 mit einem Praktikum bei NETWAYS gestartet. Als leidenschaftlicher Programmierer und begeisterter Anhänger der Idee freier Software, hat er sich dabei innerhalb kürzester Zeit in die Herzen seiner Kollegen im Development geschlichen. Wäre nicht ausgerechnet Gandhi sein Vorbild, würde er von dort aus daran arbeiten, seinen geheimen Plan, erst die Abteilung und dann die Weltherrschaft an sich zu reißen, zu realisieren - tut er aber nicht. Stattdessen beschreitet er mit der Arbeit an Icinga Web 2 und seiner Ausbildung bei uns friedliche Wege.