Seite wählen

NETWAYS Blog

Ansible – should I use omit filter?

When we talk about Ansible, we more and more talk about AWX or Tower. This Tool comes in handy when you work with Ansible in a environment shared with colleagues or multiple teams.
In AWX we can reuse the playbooks we developed and share them with our colleagues on a GUI Platform.

Often we need a bit of understanding how a playbook is designed or if a variable need to be defined for the particular play. This can be much more tricky when sharing templates to people unaware of your work.

This is where the omit filter can be used. The easiest way to explain this, if the variable has no content or isn’t defined, omit the parameter.

The following example is an extract from the documentation:


- name: touch files with an optional mode
  file:
    dest: "{{ item.path }}"
    state: touch
    mode: "{{ item.mode | default(omit) }}"
  loop:
    - path: /tmp/foo
    - path: /tmp/bar
    - path: /tmp/baz
      mode: "0444"

In AWX we can create surveys, those are great to ask a few questions and provide a guide on how to use the underlying play. But often we need to choose between two variables whether one or another action should happen. Defined by the variable in use. If we leave one of both empty, Ansible will see those empty as defined but „None“ (Python null) as content.

With the omit filter we can remove the parameter from the play, so if the parameter is empty it won’t be used.

The following code is the usage of icinga2_downtimes module which can create downtimes for hosts or hostgroups but the parameters cannot be used at the same time. In this case I can show the variable for hostnames and hostgroups in the webinterface. The user will use one variable and the other variable will be removed and therefore no errors occur.


- name: schedule downtimes
  icinga2_downtimes:
    host: 
    username: icinga_downtime
    password: "{{ icinga_downtime_password }}"
    hostnames: "{{ icinga2_downtimes_hostnames | default(omit) }}"
    hostgroups: "{{ icinga2_downtimes_hostgroups | default(omit) }}"
    all_services: "{{ icinga2_downtimes_allservices | default(False) }}"

The variables shown in the AWX GUI on the template.

This filter can be used in various other locations to provide optional parameters to your users.

If you want to learn more about Ansible, checkout our Ansible Trainings or read more on our blogpost.

Thilo Wening
Thilo Wening
Manager Consulting

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.

JSON in bequem

Wenn man mit diversen Tools arbeitet, die wir in diesem Blog immer wieder bearbeitet, stößt man unweigerlich irgendwann auf JSON formatierte Texte. Während es sicher für den einen oder anderen IT-God kein Problem ist, das JSON im Hirn zu parsen und JSON-formatierter Text höchstens eine angenehme Erleichterung zu Bytecode ist, möchte ich hier kurz zweieinhalb Wege für Sterbliche vorstellen, um im
Wirrwarr von Klammern nicht die Übersicht zu verlieren.
Dabei bringen manche Dienste bereits eine Möglichkeit mit, den Output einfacher lesbar zu machen, andere verlassen sich dabei ganz auf externe Tools. Warum er nicht immer „einfach“ gehalten wird, lässt sich ganz einfach damit erklären, das JSON eingentlich eh nur entworfen wurde, um von Maschinen verarbeitet zu werden und Menschen in den Wahnsinn zu treiben. Da sorgen Zeilenumbrüche und Einrückungen nur dafür, dass unnötig viele Daten übertragen werden.
Die REST API von Elasticsearch bietet je nach Endpunkt mal schön formatiertes, mal unformatierters JSON an.

# curl localhost:9200
{
  "name" : "y2G7v2X",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "1KYRb5mUQ8CaTSJDM-6djQ",
  "version" : {
    "number" : "6.3.2",
    "build_flavor" : "default",
    "build_type" : "rpm",
    "build_hash" : "053779d",
    "build_date" : "2018-07-20T05:20:23.451332Z",
    "build_snapshot" : false,
    "lucene_version" : "7.3.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

Dagegen sieht jeder Unterpunkt entsprechend unübersichtlich aus.

# curl localhost:9200/_cluster/health
{"cluster_name":"elasticsearch","status":"green","timed_out":false,"number_of_nodes":2,"number_of_data_nodes":2,"active_primary_shards":341,"active_shards":682,"relocating_shards":0,"initializing_shards":0,"unassigned_shards":0,"delayed_unassigned_shards":0,"number_of_pending_tasks":0,"number_of_in_flight_fetch":0,"task_max_waiting_in_queue_millis":0,"active_shards_percent_as_number":100.0}

Für Elasticsearch (und mittlerweile auch für Icinga 2!) gibt’s hier aber Abhilfe durch den Schalter ?pretty.

# curl localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "elasticsearch",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 2,
  "number_of_data_nodes" : 2,
  "active_primary_shards" : 341,
  "active_shards" : 682,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

Da man diverse JSON Monster aber auch mal als Datei vorliegen hat oder nicht alle Dienste solche Komfortfunktionen bieten, braucht man immer wieder externe Hilfe. Dafür gibt’s die Allroundlösung, die überall funktioniert, wo Python installiert ist, also quasi bei jedem ernst zu nehmenden Betriebssystem.

# curl -s localhost:9200/_cluster/health | python -m json.tool
{
    "active_primary_shards": 341,
    "active_shards": 682,
    "active_shards_percent_as_number": 100.0,
    "cluster_name": "elasticsearch",
    "delayed_unassigned_shards": 0,
    "initializing_shards": 0,
    "number_of_data_nodes": 2,
    "number_of_in_flight_fetch": 0,
    "number_of_nodes": 2,
    "number_of_pending_tasks": 0,
    "relocating_shards": 0,
    "status": "green",
    "task_max_waiting_in_queue_millis": 0,
    "timed_out": false,
    "unassigned_shards": 0
}

Das war’s dann aber auch schon mit dem, was man einfach auf diese Weise machen kann. Oft reicht das ja auch aus. Wer aber gern etwas mehr an Möglichkeiten haben will, sollte sich unbedingt mal jq installieren.

# curl -s localhost:9200/_cluster/health | jq
{
  "cluster_name": "elasticsearch",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 2,
  "number_of_data_nodes": 2,
  "active_primary_shards": 341,
  "active_shards": 682,
  "relocating_shards": 0,
  "initializing_shards": 0,
  "unassigned_shards": 0,
  "delayed_unassigned_shards": 0,
  "number_of_pending_tasks": 0,
  "number_of_in_flight_fetch": 0,
  "task_max_waiting_in_queue_millis": 0,
  "active_shards_percent_as_number": 100
}

Soweit so fad. Ausser, dass ich auf meiner Shell noch schöne farbliche Hervorhebungen sehe und Ihr im Blog hier nicht. 😛
Interessant wird’s dann aber, wenn man mit jq anfängt, den Output auch gleich zu filtern. Dazu ein Auszug eines API Outputs von Icinga 2.

{
  "results": [
    {
      "attrs": {
        "__name": "canis",
        "acknowledgement": 0.0,
        "acknowledgement_expiry": 0.0,
[...]
    },
    "joins": {},
    "meta": {},
    "name": "canis",
    "type": "Host"
  },
[...]

Wenn man den Output dann durch einen jq Aufruf inkl. Filtern schickt, kann man auf die eigentlich interessanten Daten filtern. Ich hab‘ mir freundlicherweise genehmigt, die folgenden Beispiele aus dem Icinga 2 Buch zu entnehmen.

$ curl ... |jq '{name: .results[].name}'
{
  "name": "canis"
}
{
  "name": "fornax"
}
{
  "name": "virgo"
}
[...]

Diese Filter kann man dann beliebig erweitern.

$ curl ... |jq '{name: .results[].name, address: .results[].attrs.address}'
{
  "name": "sculptor"
  "address": "172.16.2.11"
}
{
  "name": "fornax"
  "address": "172.16.1.11"
}
...

Eine umfassende Anleitung für jq gibt’s auch. Je nach Bedarf kann es sich aber auszahlen, sich erstmal diverse Tutorials anzusehen, da hier oft der Zugang etwas leichter gemacht wird.

Thomas Widhalm
Thomas Widhalm
Manager Operations

Pronomina: er/ihm. Anrede: "Hey, Du" oder wenn's ganz förmlich sein muss "Herr". Thomas war Systemadministrator an einer österreichischen Universität und da besonders für Linux und Unix zuständig. Seit 2013 ist er bei der NETWAYS. Zuerst als Consultant, jetzt als Leiter vom Operations Team der NETWAYS Professional Services, das unter anderem zuständig ist für Support und Betriebsunterstützung. Nebenbei hat er sich noch auf alles mögliche rund um den Elastic Stack spezialisiert, schreibt und hält Schulungen und macht auch noch das eine oder andere Consulting zum Thema. Privat begeistert er sich für Outdoorausrüstung und Tarnmuster, was ihm schon mal schiefe Blicke einbringt...

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:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
t = threading.Thread(target=func)
t.daemon = True # Avoids that python hangs during shutdown
t.start()
[/sourcecode]
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:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
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
[/sourcecode]
Kann ganz einfach, mit durchschlagendem Erfolg, so umgebaut werden:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
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
[/sourcecode]
Der „CleanupThread“ sollte selbstverständlich keine Arbeit verrichten die unkontrolliert blockt. Denn dann sieht das ganze am Ende wieder so aus:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
def _sigterm(self, signum, frame):
t = threading.Thread(target=self._cleanup, name=’CleanupThread‘)
t.daemon = True
t.start()
[/sourcecode]
Und der ganze Spaß geht von vorne los..

Johannes Meyer
Johannes Meyer
Lead Developer

Johannes ist seit 2011 bei uns und inzwischen, seit er 2014 die Ausbildung abgeschlossen hat, als Lead Developer für Icinga Web 2, Icinga DB Web sowie alle möglichen anderen Module und Bibliotheken im Web Bereich zuständig. Arbeitet er gerade mal nicht, macht er es sich bei schlechtem Wetter am liebsten zum zocken oder Filme/Serien schauen auf dem Sofa gemütlich. Passt das Wetter, geht's auch mal auf eines seiner Zweiräder. Motorisiert oder nicht.

Graphite installation unter Debian 8.4

Ich hab mich mit der Aktualiserung unserer Schulungsunterlagen diese Woche beschäftigt, dies erfolgt bei uns auf kontinuierlicher Basis.
Was mir dabei auffiel bei unseren Graphing Unterlagen war dass das Webfrontend von Graphite wenn man es unter einer aktuellen Version von Debian oder Ubuntu installiert den Zugriff auf die Weboberfläche verweigert. Ich warf ich einen genaueren Blick auf das Setup um zu sehen wo der Fehler lag.
Die Ursache war relativ schnell ausgemacht.
Unter Debian Jessie 8.4 und Ubuntu 15.10 in den aktuellen Versionen gibt es kleine Veränderungen in der Apache2-Konfiguration unter Apache 2.4.10 und der Installation von Graphite.
Der bisherige Installationsweg für eine Graphite Installation mit Webfrontend war bisher wie folgt:

# apt-get install apache2 libapache2-mod-wsgi python-django python-django-tagging fontconfig python-tz python-pip python-dev python-twisted python-cairo
# pip install carbon whisper graphite-web
# pip install --upgrade twisted
# cp /opt/graphite/conf/carbon.conf.example /opt/graphite/conf/carbon.conf
# cp /opt/graphite/conf/storage-schemas.conf.example /opt/graphite/conf/storage-schemas.conf
# /opt/graphite/bin/carbon-cache.py start
# /opt/graphite/bin/carbon-cache.py status
# cp /opt/graphite/examples/example-graphite-vhost.conf /etc/apache2/sites-available/graphite.conf
# cp /opt/graphite/conf/graphite.wsgi.example /opt/graphite/conf/graphite.wsgi
# cp /opt/graphite/webapp/graphite/local_settings.py.example \ /opt/graphite/webapp/graphite/local_settings.py
# python /opt/graphite/webapp/graphite/manage.py syncdb
# chown www-data. -R /opt/graphite/storage
# a2dissite 000-default
# a2ensite graphite
# a2enmod wsgi
# service apache2 restart
# chmod +x /etc/init.d/carbon-cache // Damit 'carbon-cache.py start' mit Systemstart erfolgt

Die *kleine* aber wichtige Änderung ist folgende pip install graphite-web fügt nicht folgendes hinzu:
Anscheinend vergisst die pip graphite-web den Eintrag in der graphite-vhost.conf welcher auf das webapp Verzeichnis den Zugriff erlaubt.

// < Directory "/opt/graphite/webapp/" >
// Options +ExecCGI
// Order deny,allow
// Allow from all
// Require all granted
// < /Directory >

Wenn man dies wie manuell ergänzt dann erhält am auch Zugriff auf die Weboberfläche.
Hier ein Kurzdurchlauf bei der Graphite installation auf einem frischen Debian Jessie 8.4.

# apt-get install python python-pip python-dev python-cairo python-django \
python-django-tagging apache2 libapache2-mod-wsgi python-twisted \
python-memcache python-pysqlite2 python-simplejson
# pip install whisper
# pip install carbon
# pip install graphite-web
# cp /opt/graphite/webapp/graphite/local_settings.py.example \ /opt/graphite/webapp/graphite/local_settings.py
# cp /opt/graphite/conf/graphite.wsgi.example /opt/graphite/conf/graphite.wsgi
# cp /opt/graphite/conf/carbon.conf.example /opt/graphite/conf/carbon.conf
# cp /opt/graphite/conf/storage-schemas.conf.example /opt/graphite/conf/storage-schemas.conf
# cp /opt/graphite/conf/storage-aggregation.conf.example \
/opt/graphite/conf/storage-aggregation.conf
# cd /opt/graphite/webapp/graphite && python manage.py syncdb
# chown -R www-data:www-data /opt/graphite/storage/
# vim /etc/apache2/sites-available/graphite.conf // hier erfolgt das manuelle Hinzufuegen von des Ordners in der Config
# a2dissite 000-default.conf
# a2ensite graphite.conf
# service apache2 restart
# vim /etc/systemd/system/carbon-cache.service // Erstellen eines Service-Files fuer den Start von carbon-cache.py start
# systemctl enable carbon-cache.service
# systemctl start carbon-cache.service
# systemctl reload apache2.service

Somit ist das Problem gelöst und die Graphite-Web Oberfläche aufrufbar.
Ich werde demnächst noch ein Video fuer Ubuntu 15.10 nachreichen da hier die Pfade abweichen von Debian und dies nicht 1:1 übertragbar ist.
Bis Demnächst!
Und übrigens: die nächste Graphing Schulung findet schon ganz bald statt, also noch fix anmelden!

David Okon
David Okon
Senior Systems Engineer

Weltenbummler David hat aus Berlin fast den direkten Weg zu uns nach Nürnberg genommen. Bevor er hier anheuerte, gab es einen kleinen Schlenker nach Irland, England, Frankreich und in die Niederlande. Alles nur, damit er sein Know How als IHK Geprüfter DOSenöffner so sehr vertiefen konnte, dass er vom Apple Consultant den Sprung in unser Professional Services-Team wagen konnte. Er ist stolzer Papa eines Sohnemanns und bei uns mit der Mission unterwegs, unsere Kunden zu glücklichen Menschen zu machen.

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:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
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)
[/sourcecode]
Ja sogar Basic Authentication beherrscht es:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
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)
[/sourcecode]
Ist das nicht toll? Ich nehme stark an, dass da noch mehr geht, aber für den Moment bin ich vollauf zufrieden. 🙂

Johannes Meyer
Johannes Meyer
Lead Developer

Johannes ist seit 2011 bei uns und inzwischen, seit er 2014 die Ausbildung abgeschlossen hat, als Lead Developer für Icinga Web 2, Icinga DB Web sowie alle möglichen anderen Module und Bibliotheken im Web Bereich zuständig. Arbeitet er gerade mal nicht, macht er es sich bei schlechtem Wetter am liebsten zum zocken oder Filme/Serien schauen auf dem Sofa gemütlich. Passt das Wetter, geht's auch mal auf eines seiner Zweiräder. Motorisiert oder nicht.