Erhältlich ab: Oktober 2016
Das waren noch Zeiten, als unsere heißgeliebten monolithischen Enterprise-Anwendungen robust und sicher vor sich hinliefen. Heute dagegen baut man stark verteilte Systeme auf Basis von niedlichen, kleinen Services, die scheinbar permanent wegzubrechen drohen. Wie soll man das bitte noch im Griff behalten?
Beschäftigt man sich mit dem Thema Microservices und den zugehörigen Architekturansätzen, stößt man schnell auf das Thema Resilience. Reichte es früher aus, ein Softwaresystem stabil (reliable) zu gestalten, muss es heute belastbar und flexibel (resilient) sein. Warum ist das eigentlich so und was genau ist der Unterschied? Enterprise Tales auf Ursachenforschung ...
Für eine monolithische Anwendung ist es essenziell, dass sie das macht, was man von ihr erwartet: Nämlich ohne Fehler und ohne Ausfälle zu funktionieren. Denn fällt ein solches System aus, fällt per Definition alles aus. Entsprechend viel Energie wird darauf verwandt, ein solches System fehlerfrei (Development) und hoch verfügbar (Deployment) aufzubauen. Während der Entwicklung sorgen ausgefeilte QA-Szenarien dafür, die Software möglichst bugfrei zu bekommen. Ausfallsicherheit der Server erkauft man sich über Load Balancing und komplexe Cluster, oder man schiebt die Anwendung gleich mit entsprechenden SLAs in die Cloud.
Bei einem Microservice-basierten System ist die Ausgangslage ein wenig anders. Fällt einer der Services aus, können die anderen Services theoretisch weiterarbeiten, vorausgesetzt, das System ist auf die Ausfälle bzw. das Fehlverhalten einzelner Services vorbereitet [1]. Die Idee von Resilience ist es also, mit temporären Problemen sinnvoll umgehen zu können. Was genau in diesem Kontext sinnvoll ist, klären wir später. Zusätzlich sollte ein resilientes System in der Lage sein, sich schnell – und wenn möglich, automatisch – von einem aufgetretenen Problem zu erholen. Dabei sollte es wieder zur normalen Ausgangsposition zurückkehren, nachdem es in einen Ausnahmezustand gelaufen ist. Die Verwendung von Microservices löst dabei nicht automatisch das Problem. Ganz im Gegenteil: Es ist viel Energie notwendig, ein stark verteiltes System so aufzubauen, dass temporäre Ausfälle oder fehlerhafte Responses einzelner Services sauber abgefangen werden können, ohne dass dabei die Ergonomie der Anwendung leidet. Gleiches gilt für die automatische Wiederherstellung des Systems [2].
Dass verteilte Systeme, und dazu gehören nun einmal auch unsere Microservice-basierten Anwendungen, nicht immer so agieren, wie gewünscht, ist spätestens seit Peter Deutschs Auflistung der sieben größten Fehlannahmen bei verteilten Systemen bekannt. Diese hat James Gosling einige Jahre später um einen achten Punkt ergänzt:
Das Netzwerk ist immer verfügbar.
Die Latenz ist null.
Die Bandbreite ist unendlich.
Das Netzwerk ist sicher.
Die Topologie ändert sich nicht.
Es gibt einen Administrator.
Die Transportkosten sind null.
Das Netzwerk ist homogen.
Ein System wird niemals zu 100 Prozent fehlerfrei sein, insbesondere dann nicht, wenn es aus einer Menge von lose gekoppelten Services besteht. Ein System, das sich die Eigenschaft „Resilience“ auf die Fahne schreibt, sollte also flexibel mit diesen Problemen umgehen können und nicht nur resistent gegen Fehler im Programmcode sein. Denn das reicht in der Regel nicht aus. Gleichzeitig sollte es in der Lage sein, sich schnell – wenn möglich automatisch – von einem Ausnahmezustand zu erholen.
Unser Ziel ist es also, ein System aufzubauen, bei dem jeder Service Call potenziell fehlschlagen kann, ohne dass dies negative Implikationen auf das Gesamtsystem hat. Anders formuliert sollte das System auch dann mehr oder minder sinnvoll agieren, wenn ein Service nicht erreichbar ist oder fehlerhafte Daten und Formate liefert. Toleranz ist gefragt.
Wichtig dabei ist, dass wir von vornherein den Fehlerfall und sinnvolle Alternativen als Szenarien mit einplanen. Das gilt insbesondere auch für die Daten und Informationen, die am UI angezeigt werden sollen, oder für die Art, wie ein Fehler die User Experience beeinflusst. Wir brauchen einen fachlichen Plan B! Zu theoretisch? Ok, hier ein Beispiel: Nehmen wir einmal an, einem Nutzer eines Webshops sollen via Recommendation-Service Kaufvorschläge präsentiert werden. Leider ist der Service temporär nicht erreichbar. In diesem Fall wäre es sinnvoll, einen alternativen Service anzusprechen, der weniger qualifizierte, in diesem Fall also weniger personalisierte, Ergebnisse liefert oder auf Seiten des Clients mit gecachten und somit eventuell veralteten Daten arbeitet. In beiden Fällen erreichen wir wahrscheinlich nicht das optimale Ergebnis. Wahrscheinlich fällt das dem Nutzer aber gar nicht auf. Die Wahrscheinlichkeit, dass er ein für ihn passendes Produkt findet, ist zwar geringer, aber trotzdem haben wir dank unseres Fallback-Szenarios eine vertretbare User Experience erreicht.
Es stellt sich nun natürlich die Frage nach dem Aufwand. Ist dieser überhaupt noch vertretbar? Würde man alle Ausnahmebehandlungen (Timeouts oder Reaktion auf HTTP-Error-Codes) per Hand behandeln, sicherlich nicht. Aber zum Glück gibt es Tools, die sich genau auf diesen Aufgabenbereich spezialisiert haben. Mit ihrer Hilfe lassen sich Wenn-Dann-Szenarien für Ausnahmen konfigurieren. Der bekannteste Vertreter ist sicherlich Hystrix aus dem Netflix OSS Stack [3].
Unabhängig von den implementierten oder konfigurierten Alternativszenarien ist es essenziell, dass sich das System nach einem Ausnahmefall schnell wieder erholt. Fehler oder Ausfälle müssen also schnellstmöglich erkannt werden. Das System muss sich automatisch wiederherstellen können, z. B. durch den Neustart eines Service. Noch besser wäre es natürlich, wenn es gar nicht erst in einen Ausnahmezustand gerät. Ein verdichtetes Echtzeitmonitoring ist hier der Schlüssel zum Erfolg. Und dies nicht nur, um Fehlsituationen zu erkennen, sondern vor allem auch, um sie im Vorfeld zu vermeiden. Ziel des Monitorings ist es, negative Tendenzen zu erkennen und gezielt darauf zu reagieren. Dies gilt sowohl auf Ebene der Architektur, indem z. B. auf Änderungen der Antwortzeiten, des Durchsatzes oder der Circuit-Breaker-Status reagiert wird, als auch auf Ebene von Businessmetriken (weniger Anmeldungen, weniger Bestellungen oder Umsatzrückgang). Wie geht man nun aber vor, wenn es trotz Frühwarnsystemen doch zu einem Problem kommt? Auf jeden Fall gilt es, das Rad nicht neu zu erfinden, sondern auf etablierten Patterns aufzusetzen.
Wie so häufig in der Wunderwelt der IT, haben sich auch im Umfeld von resilienten Systemen etliche Patterns etabliert, von denen ich einige an dieser Stelle nennen möchte: Da wäre zunächst einmal Promises und Fallbacks aka „der fachliche Plan B“. Betrachtet man den Vertrag zwischen Consumer und Supplier als eine Art Versprechen, sollte man überlegen, wie man das Versprechen auch dann halten kann, wenn der ursprünglich angedachte Supplier nicht verfügbar ist. Im einfachsten Fall könnte man den Call wiederholen. Hierbei gilt es zu beachten, dass der Call eventuell von Seiten des Service schon bearbeitet wurde und nur die Antwort aussteht. Natürlich könnte man auch ein gecachtes Ergebnis statt der tatsächlichen Antwort nutzen oder einen alternativen Service gleicher oder ähnlicher fachlicher Qualität aufrufen. Auch die Verlagerung der Ergebnisberechnung in den Consumer oder ein zwischengeschaltetes System, das statischen Content ausliefert, ist ein mögliches Szenario. Bei allen genannten Fallback-Lösungen bekommen wir wahrscheinlich ein suboptimales Ergebnis. Dies ist für den Consumer in der Regel aber deutlich besser als kein Ergebnis, da das System so mit korrekten fachlichen Daten weiterarbeiten kann.
Wie bereits erwähnt, muss bei der Wiederholung einzelner Calls sichergestellt werden, dass sich das System weiterhin korrekt verhält. Seiteneffekte müssen ausgeschlossen werden. Man spricht in diesem Fall auch von Idempotenz. Was sich zunächst einfach anhört, ist in der Praxis ein nicht zu unterschätzendes Problem. Wiederhole ich zum Beispiel im Falle eines Timeouts einen Service-Call, der dafür sorgt, dass 50 Euro von meinem Konto auf ein anderes Konto transferiert werden, dann möchte ich natürlich nicht, dass die 50 Euro mehrfach von meinem Konto abgebucht werden. Eine Möglichkeit, die gewünschte Idempotenz sicherzustellen, liegt im Verzicht auf Delta-Messages. Anstatt also als Message „erhöhe um 50 Euro“ zu senden, würde ein „setze Kontostand auf 1 000 Euro“ helfen. Leider ist dies nicht immer so einfach möglich. Eine Alternative ist eine Infrastruktur, z. B. ein Messaging Bus, der in der Lage ist, Dubletten auszufiltern. Natürlich helfen auch eindeutige UUIDs dabei, Wiederholungstäter bei Requests oder Messages zu identifizieren.
Ein weiteres Mittel, ein System möglichst flexibel zu halten, sind Consumer Contracts. Sie helfen, nicht valide Responses des Suppliers zu vermeiden. Bei diesem Verfahren senden die verschiedenen Consumer ihre Erwartungen an die Schnittstelle an den Supplier. Es werden also die Teile einer Schnittstelle gesammelt, die tatsächlich von den verschiedenen Consumern genutzt werden. Der Supplier kann dabei ungestört Änderungen an der Schnittstelle vornehmen, solange keines der relevanten Attribute geändert wird. Anders formuliert ist eine Änderung der Schnittstelle nur dann ein Breaking Change, wenn sie mindestens einen der Consumer direkt betrifft. Wie nicht anders zu erwarten, gibt es das eine oder andere Tool, mit dem das Einhalten des Consumer-driven Contracts automatisch getestet werden kann [4].
Damit das Pattern Consumer Contracts funktioniert, muss noch eine weitere Voraussetzung erfüllt sein: Der Consumer darf nur die Teile validieren, die er tatsächlich benötig. Alles andere wird ignoriert. Das gilt z. B. auch für die Struktur der auszutauschenden Daten. Es gilt also das Credo: „Be conservative in what you send and be liberal in what you expect.“
Microservices ermöglichen es uns dank ihrer losen Kopplung, Systeme aufzubauen, die auch dann fachlich sinnvoll funktionieren, wenn einzelne Services nicht verfügbar sind oder fehlerhafte Responses oder Daten liefern. Leider bekommt man dieses Verhalten nicht geschenkt. Man muss schon einiges an Energie investieren, um am Ende ein flexibles System zu erhalten, das fachlich korrekt auf temporäre Ausfälle reagiert. Angefangen bei einfachen Load-Balance-Szenarien, über Timeouts, Circuit Breaker, Bulk Heads und Fallback-Services bis hin zu automatischer Service Discovery und Configuration Injection gibt es unzählige Ansatzpunkte zur Flexibilisierung eines verteilten Systems. Die Verwendung von Patterns, wie Promises und Fallbacks, Idempotenz oder Consumer Contracts, kann dabei ebenfalls sehr nützlich sein.
Der Schlüssel zum Erfolg sind neben einem Plan B das schnelle Erkennen und die automatische Regeneration des Systems. Wichtig ist dabei, dass Ausnahmezustände die Ausnahme bleiben und nicht zur Regel werden. Hier hilft umfangreiches Echtzeitmonitoring von technischen und fachlichen Kennzahlen. In diesem Sinne: Stay tuned ...
Lars Röwekamp ist Geschäftsführer der open knowledge GmbH und berät seit mehr als zehn Jahren Kunden in internationalen Projekten rund um das Thema Enterprise Computing.
[1] Microservices, Martin Fowler: http://martinfowler.com/articles/microservices.html
[2] Fault Tolerance at Netflix: http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
[3] Hystrix: https://github.com/Netflix/Hystrix
[4] Consumer-driven Contract mit PACT: http://docs.pact.io
Das Release von Docker 1.12 bietet direkt die Orchestrierung von Containern an. Mit Docker für Mac und Windows verbessert sich die Unterstützung für die Entwickler. Die Betaversion von Docker für AWS und Azure vereinfacht die Erstellung einer Produktion in der Cloud. Als besonderes Bonbon wird uns demnächst ein Marktplatz für Docker-Images in Aussicht gestellt. Wahrlich ein gigantischer Fortschritt, der den professionellen Einsatz von Docker erleichtert und die anhaltende Produktivität der Docker-Community erneut unter Beweis stellt.
Auf der DockerCon 2016 sind der Community viele Ergebnisse des letzten halben Jahres präsentiert worden. Für die Entwickler ist es mit Docker für Mac und Windows nun deutlich leichter geworden, auf der Basis von Docker tatsächlich zu implementieren. Die Bereitstellung aller möglichen Docker-Container für die Entwicklung ist nun wirklich einfach. Mit der Integration via xhyve auf dem Mac und mit Hyper-V für Windows schafft Docker nun eine fast vollständige Hostintegration. Keine Probleme mehr, Dateien auf dem Mac oder mit Windows zu ändern und diese Veränderung auch direkt mit einer Reaktion im Container zu verknüpfen. Endlich! Die gesamten Build- und Auto-Deployment-Werkzeuge greifen nun voll. Debugging ist direkt aus der Entwicklungsumgebung mit Software im Container möglich, da nun eine Netzwerkunterstützung des Hosts existiert. Sogar die direkte Integration eines VPN-Tunnels im Container ist gelöst. Die Zusammenarbeit der Unikernel-, Apple- und Microsoft-Entwickler trägt erste Früchte. Die Integration von Kitematic und das Autoupdate werden ebenfalls unterstützt. Eine einfache Veränderung der Konfiguration und des Managements ist inklusive.
Mit Docker für AWS und Azure wird es möglich, die eigenen Ergebnisse direkt auch in Produktion zu stellen. Um diesen Schritt real werden zu lassen, gibt es zwei neue Beschreibungsformate. Mit dem DAB-Format wird eine Menge von Services samt Storage und Netzwerken beschrieben. Aktuell kann dieses Format mit dem Werkzeug Docker Compose in der Version 1.8 halbwegs erzeugt werden. Mit dem Stack-Format sollen dann viele Services samt Infrastruktur für die Cloud beschrieben und installierbar gemacht werden.
In naher Zukunft werden wir unsere Software und Systeme dann im Docker Store anbieten und hoffentlich auch vermarkten können. Warum sollen wir diese Möglichkeit auch nicht für Serversoftware endlich hinbekommen? In den diversen Stores für Mobile- und Desktopcomputer hat diese Entwicklung ebenfalls gute Resultate gezeigt.
1. Docker 1.12
Integration von SwarmKit – Orchestrierung, Sicherheit und Clustering-Support
Erweiterungen des Dockerfiles (Shell, Healthcheck)
Verbesserung des Netzwerkstacks (Verschlüsselung)
Plug-in Manager
Experimentaler Support von MACvlan und IPvlan
Daemon Reload ohne Stoppen der Container
2. Docker für Mac und Windows
Die Version 1.0 ist nun mit Docker 1.12 verfügbar
Unterstützung der nativen Virtualisierung von Mac und Windows via xhyve und Hyper-V
Autoupdate (Stable und Development)
Komplette Integration des Hostfilesystems und Netzwerks inkl. VPN-Support
Neue Docker-Projekte zur Unterstützung der „Native Virtualisation“ [1]
HyperKit
DataKit
VPNKit
3. Docker für AWS und Azure (Beta)
4. Docker Store
5. Docker Cast und YouTube-Channel
6. Aktualisierungen für die Werkzeuge Docker Machine und Docker Compose sind ebenfalls erfolgt.
Wie es sich seit Anfang des Jahres in der Sourcecode-Basis der Docker-Projekte abzeichnete und durch zahlreiche Tweets und Blogartikel vermutet wurde, ist nun die direkte Orchestrierung von Containerbestandteilen der Docker Engine 1.12 verfügbar. Bisher war ein Containercluster nur mit zusätzlichen Projekten wie Kubernetes, Mesos oder Swarm möglich. So richtig einfach oder elegant war dieser zusätzliche Aufwand für die meisten DevOps-Entwickler nicht. Profis fanden die Reduktionen der Komplexität schon attraktiv, aber für die meisten Entwickler und Administratoren war der Zugang zu kompliziert. Mit der Integration des SwarmKits in die Docker Engine ist es nun wirklich einfach und nachvollziehbar [2]. Auf der letzten DevOpsCon 2016 in Berlin wurde schon demonstriert, wie elegant und einfach das SwarmKit die Aufgabe der Orchestrierung bereitstellt [3]. Die direkte Integration des Swarmkits in die Docker Engine verschafft uns nun einen intuitiven Zugang zur maschinenübergreifenden Containerorchestrierung (Listing 1). Im Beispiel werden einfach drei VirtualBox-Maschinen erzeugt. Dann wird auf der ersten Maschine swarm_node_1 ein Swarm-Manager erzeugt. Mit dem entsprechende Token werden zwei Worker swarm_node_2 und swarm_node_3 mit dem Manager auf dem swarm_node_1 verbunden. Fertig ist der komplette Cluster samt Netzwerk, vollständiger Verschlüsselung und Auto-Failover-Management. Fast beliebig skalierter ist es auch noch. Der Cluster von Chanwit Kaewkasi lief mit 2 384 Knoten einige Tage in der Cloud von Scaleway stabil [4].
Listing 1: Erzeuge einen Swarm von Docker-Maschinen
# (0) Install docker 1.12 and docker-machine 0.8 at your client
# (1) Create Docker Machines
$ docker-machine create -d virtualbox swarm_node_1
$ docker-machine create -d virtualbox swarm_node_2
$ docker-machine create -d virtualbox swarm_node_3
# (2) Init Swarm Manager
$ eval $(docker-machine env swarm_node_1)
$ docker swarm init \
--advertise-addr $(docker-machine ip swarm_node_1) \
--listen-addr $(docker-machine ip swarm_node_1):2377
$ TOKEN=$(docker swarm join-token -q worker)
# (3) Attach Workers to the Swarm
$ eval $(docker-machine env swarm_node_2)
$ docker swarm join \
--token $TOKEN \
$(docker-machine ip swarm_node_1):2377
$ eval $(docker-machine env swarm_node_3)
$ docker swarm join \
--token $TOKEN \
$(docker-machine ip swarm_node_1):2377
Der Docker Swarm kennt zwei verschiedene Rollen, die auch im laufenden Betrieb verändert werden können: Manager und Lead-Manager (Abb. 1). Die Manager koordinieren die Struktur des gesamten Clusters. Zu jedem Zeitpunkt wird der Zustand auf allen Managern gespeichert. Der Lead-Manager entscheidet, welche Aufgaben existieren und auf welchen Workern umgesetzt werden. Dabei ist die gesamte Kommunikation verschlüsselt. Die Manager bilden einen eigenen CA und stellen die entsprechenden Zertifikate aus. Die Zertifikate werden im laufenden Betrieb automatisch ausgetauscht. Die Vertraulichkeit des Zustands und der Kommunikation ist also hoch. Für den Eintritt eines neuen Knotens im Cluster wird ein Token als Geheimnis benötigt (Listing 1). Fallen Clusterknoten oder Container eines Service aus, greift das Failover-Management [5], [6]. Wenn tatsächlich der Lead-Manager ausfällt, wird auf der Basis eines Quorums ein neuer Leader gewählt. Die Steuerung des Docker-Clients kann über jeden Manager im Cluster erfolgen. Der Docker-Swarm-Cluster braucht also keinen separaten Zustandsspeicher, wie es Kubernetes mit etcd, Mesos mit ZooKeeper oder der Swarm-Proxy mit verschiedenen Service-Discovery-Lösungen benötigt. Die komplette Management-Control-Plane muss für die Docker-Swarm-Cluster nicht mehr installiert und gepflegt werden. Alles ist in der Docker Engine 1.12 vorhanden. Hier hat die Docker-Community für ein schwieriges technisches Problem eine elegante und schmale Lösung bereitgestellt. Die Ergänzung der automatischen Sicherheit der Kommunikation ist außerdem gelungen.
Der Docker-Swarm-Cluster kommuniziert über einen der Managementknoten. Ein Übersicht über die Rolle aller Maschinen im Cluster wird mit dem Befehl docker node ls geliefert. Nun können die verteilten Services gestartet werden. Der Manager übernimmt die Aufgabe, die Container des Service auf die Knoten zu verteilen. Mit dem Befehl docker service tasks können die Aufgaben, die gerade verarbeitet werden, angezeigt werden (Abb. 2). Skalierung und Redeployment von neuen Versionen sind inklusive. Es können alle Container ausgetauschten werden, oder es kann in einer bestimmten Frequenz und Anzahl die Umstellung erfolgen (Abb. 3). Natürlich sind ein Neustart, das Stoppen oder Löschen des Service möglich (Listing 2). Wenn ein Container eines Service oder gar ein kompletter Knoten nicht mehr antwortet, werden die Container auf die verbleibenden Knoten erneut verteilt und neu gestartet. Jeder Service bekommt eine eigene IP-Adresse zugeordnet. Hiermit können alle anderen Container den Service erreichen. Es wird ein automatisches Load Balancing unterstützt. Die Services sind via Docker-DNS abfragbar. Wenn ein Service seine Ports publiziert, ist der Port anschließend auf allen Knoten des Clusters direkt verfügbar, unabhängig davon, ob ein Container des Service auf dem Knoten gestartet ist. Der Netzwerkstack jedes Knotens erledigt das Routing und Load Balacing. Ein externer Load Balancer muss also den Traffic auf alle Knoten verteilen und nicht mehr die Location der einzelnen Container im Cluster verfolgen.
Als zweite Strategie für die Verteilung eines Service gibt es die Möglichkeit, einen Container exklusiv auf allen Knoten bereitzustellen. Kommt ein Node hinzu, wird automatisch vom Scheduler entschieden, den Service dort bereitzustellen. Im Beispiel ist auf allen Knoten der cAdvisor-Container bereitgestellt. Somit ist die Überwachung jedes Knotens einfach gewährleistet (Listing 3). Die Bereitstellung von Storage oder Netzwerken über den Cluster erfolgt automatisch. Der neue Plug-in-Manager stellt noch nicht das evtl. fehlende Plug-in auf einem Knoten bereit, aber bis zum nächsten Release könnte es soweit sein. Netzwerke verschwinden auf den Knoten wieder, wenn kein Service sie mehr nutzt. Nicht alle RUN-Optionen sind für Container eines Service verfügbar. Aber neben Einschränkungen von CPU und Memory, dem Bereitstellen von Umgebungsvariablen und Labeln sowie der Wahl der Netzwerke ist das Bereitstellen von Volumes vorhanden. Damit eine Menge kooperierender Services auf einen Cluster übertragen werden kann, existiert eine Konfigurationsdatei im DAB-Format [7].
Listing 2: Erzeuge, skaliere und erneuere Services im Swarm
$ eval $(docker-machine env swarm_node_1)
$ docker node ls
$ docker service create --name whoami \
--publish 8081:80/tcp emilevauge/whoami
$ docker service tasks whoami
$ curl $(docker-machine ip swarm_node_1):8081
$ curl $(docker-machine ip swarm_node_2):8081
$ curl $(docker-machine ip swarm_node_3):8081
$ docker service scale whoami=4
# Repeat access and see which container generate the answers
# Start a new service and deploy next version...
$ docker service create --replicas 8 --name micro_service \
--network mynet --publish 80:80/tcp service:v1.0
$ sleep 10
$ docker service update --image service:v2.0 \
--update-delay 5s --update-parallelism 2 micro_service
# access service and see at which time new version is accessible
Listing 3: Erzeuge die Überwachung durch cAdvisor auf allen Knoten des Clusters
$ eval $(docker-machine env swarm_node_1)
$ docker service create --name cadvisor --mode global \
--mount type=bind,source=/,target=/rootfs/,writable=false \
--mount type=bind,source=/var/run/,target=/var/run/ \
--mount type=bind,source=/sys/,target=/sys/,writable=false \
--mount type=bind,source=/var/lib/docker/,target=/var/lib/docker/,writable=false \
--publish 8080:8080 \
google/cadvisor:latest
Neben den vielen Bugfixes und kleinen Verbesserungen gibt es ein paar bedeutende Erweiterungen. Die Docker Engine kann nun mit der Option dockerd —live-restore gestartet werden. Damit wird es möglich, dass Container einen Neustart der Engine überstehen. Im Dockerfile kann die Shell vor der Ausführung eines RUN-Befehls gesetzt werden. Damit können nun neben sh auch Bash, zsh und PowerShell für Windows-Dialekte im Dockerfile direkt verwendet werden. Die Docker Engine kann nun aktiv den Laufzeitzustand der Prozesse in einem Container prüfen. Der Befehl HEALTHCHECK ermöglicht die Formulierung entsprechender Checks. Wenn dieser Test dreimal hintereinander alle fünf Minuten fehlschlägt, wird der Container als fehlerhaft gekennzeichnet. Ist der Container einem Clusterservice zugeordnet, wird ein Restart des fehlerhaften Containers durchgeführt (Listing 4). Der neue Plug-in-Manager erlaubt es nun, die Erweiterung der Docker Engine direkt zu installieren und zu verwalten (Listing 5).
Listing 4: Formulierung eines Health-Kriteriums im Dockerfile
FROM xxx
...
HEALTHCHECK --interval=5m --timeout=3s \
--retries=3 CMD curl –f http://localhost:8080/health || exit 1
Listing 5: Die Befehle des neuen Docker-Plug-in-Managers
$ docker plugin install tiborvass/no-remove
$ docker plugin enable no-remove
$ docker plugin disable no-remove
Noch fällt es etwas schwer, die einfache Lösung von Docker-Swarm-Cluster im Release 1.12 mit den bisherigen Orchestrierungslösungen direkt zu vergleichen. Sicherlich können Kubernetes und Mesos mit einigen interessanten Eigenschaften punkten. Aber in den Punkten Einfachheit, Verständlichkeit, Verfügbarkeit und Sicherheit wird es schwer werden, Docker Swarm von Platz 1 zu verdrängen. Die integrierte Service-Discovery, das einfache Publizieren von Serviceports, Container-Load-Balancing und die Bereitstellung von Netzwerken und Volumes sind weitere Eigenschaften, die überzeugen. Es bleibt sehr spannend, die ersten produktiven Einsatzberichte zu lesen.
Schön wäre es, wenn in einem der nächsten Releases ein Scheduling von Jobs oder ein Function-Gateway wie Amazon Lambda verfügbar wäre [8]. Noch ist der Einsatz des Clusters auf Netzwerkabschnitte begrenzt. Eine echte WAN-Fähigkeit oder ein Swarm-Cluster in verschiedenen Netzwerkzonen wird noch nicht unterstützt. Der Einsatz wird gerade hitzig diskutiert, und in den kommenden Monaten wird es weitere Neuigkeiten geben. Wer mit Docker gerade beginnt und erste Informationen benötigt, findet diese in dem frischen und aktualisierten Docker-Buch von Adrian Mouat „Docker: Software entwickeln und deployen mit Containern“, zu dessen Übersetzung ich etwas beitragen durfte [9].
Peter Roßbach ist ein Infracoder, Systemarchitekt und Berater vieler Websysteme. Sein besonderes Interesse gilt dem Design und der Entwicklung von komplexen Infrastrukturen. Er ist Apache Tomcat Committer und Apache Member. Mit der bee42 solutions gmbh realisiert er Infrastrukturprodukte und bietet Schulungen auf der Grundlage des Docker-Ökosystems, aktuellen Webtechnologien, NoSQL-Datenbanken und Cloud-Plattformen an.
[1] Madhavapeddy, Anil: „Improving Docker With Unikernels: Introducing HyperKit, VPNKit And DataKit“: https://blog.docker.com/2016/05/docker-unikernels-open-source/
[2] SwarmKit: https://github.com/docker/swarmkit
[3] Docker SwarmKit: https://hub.docker.com/r/rossbachp/docker-swarmkit/
[4] Kaewkasi, Chanwit: „Docker Swarm – An Analysis Of A Very-Large-Scale Container System“: https://blog.online.net/2016/07/29/docker-swarm-an-analysis-of-a-very-large-scale-container-system/
[5] Docker 1.12 Swarm Mode Deep Dive Part 1: Topology: https://www.youtube.com/watch?v=dooPhkXT9yI
[6] Docker 1.12 Swarm Mode Deep Dive Part 2: Orchestration: https://www.youtube.com/watch?v=_F6PSP-qhdA
[7] Friis, Michael: „Introducing Experimental Distributed Application Bundles“: https://blog.docker.com/2016/06/docker-app-bundle/
[8] Building Serverless Apps With Docker: https://blog.docker.com/2016/06/building-serverless-apps-with-docker/
[9] Mouat, Adrian: „Docker“: https://www.dpunkt.de/buecher/12553/9783864903847-docker.html
Kennen Sie die Geschichte des Entwicklers, der als Tester seine komplette Arbeit automatisierte? Er brauchte acht Monate, um die automatisierten Tests dafür zu schreiben. Das Ergebnis war sechs Jahre bezahltes Nichtstun. Angeblich verbrachte er die ganze Zeit damit, League of Legends zu spielen und Reddit zu lesen.
Für seine Idee der Arbeitserleichterung bekam der Testentwickler durchaus Zuspruch auf Reddit. Denn über kaum ein anderes Thema beschweren sich Entwickler so anhaltend und ausführlich wie über das Testen. Dabei ist ihnen durchaus bewusst, dass Testen wichtig ist. Denn der perfekte Code existiert nicht. Aber trotzdem ist nichts so langweilig wie immer wieder zu testen, zu testen und zu testen. Lieber möchten sich die Entwickler direkt an das nächste Projekt setzen und neuen Code produzieren, aus dem eine Anwendung erwächst. Ich kann das nachvollziehen. Etwas zu schaffen ist schöner und erfüllender als es dann auf Herz und Nieren für jeden noch so unwahrscheinlichen Problemfall zu testen. Ich schreibe auch lieber, als dass ich korrekturlese.
Vielleicht müsste man das Thema einfach mal von der anderen Seite angehen. Anstatt zu betonen, wie super automatisiertes Testen für die Codequalität ist, wie wichtig es für Build-Pipelines und Deployment ist und wie es Prozesse und Standards schlanker macht, sage ich Ihnen eines: Mehr automatisiertes Testen bedeutet mehr Zeit für Sie, Anwendungen zu entwickeln und zu bauen. Also lieber einmal hinsetzen und sich Gedanken machen, welche Tools und Prozesse nötig sind, um Code automatisiert zu testen. Aus den ersten kleinen automatisierten Tests entsteht schnell ein großes System, das Ihnen Zeit und Mühe spart. Lassen Sie die Software Ihre Arbeit machen und automatisieren Sie so viel wie möglich. Wie Sie das hinbekommen, lesen Sie in unserem Themenschwerpunkt.
Eventuell springt am Ende auch Zeit für eine Runde Tischkicker oder FIFA-Spielen auf der PS4 mehr heraus. Übertreiben Sie es nur nicht und legen die Füße sechs Jahre lang hoch. Das könnte Ihr Chef weniger witzig finden. Denn als der Arbeitgeber des unbekannten Entwicklers herausfand, dass er seine Tests komplett für sich arbeiten ließ, feuerte er ihn. So sollte die Geschichte für Sie nicht enden.
In diesem Sinn: Frohes Automatisieren!
In den vergangenen fünfzehn Jahren hat sich für Neo4j viel getan und die einstige Java-Bibliothek zur Verwaltung von Datennetzen ist jetzt eine weltweit genutzte Datenbank. Die Version 3.0 hat in drei Kernbereichen einen Sprung nach vorne gemacht: Leistung, Anwenderfreundlichkeit und Infrastruktur.
Die 3er-Version von Neo4j bringt eine große Menge neuer Features, Verbesserungen und Änderungen mit. Vor allem bei der Skalierbarkeit und der Leistung hat sich einiges getan. Denn obwohl Graphdaten nicht als Massendaten ausgelegt sind, und unsere Anwender die bislang existierenden Limits nie erreicht haben, gab es schon seit Längerem den Wunsch, diese zu entfernen. Eigentlich möchte man in seinem Graphmodell nur qualitativ hochwertige Daten speichern, bei denen durch Vorverarbeitung schon Rauschen und Artefakte entfernt wurden – es sei denn, man ist auf der Suche nach solchen Artefakten. Durch den immer breiteren Einsatz besonders im Handel, Telekommunikations- und Netzwerkumfeld, sowie bei Industrie 4.0 und dem Internet of Things wird die Menge der gespeicherten Graphdaten in den nächsten Jahren jedoch beträchtlich ansteigen, sodass wir schon jetzt dafür vorsorgen wollten.
Daher ist jetzt die Anzahl der Knoten, Kanten und Eigenschaften in einem Neo4j-Graph nicht mehr künstlich limitiert. Die Größe der Dateisysteme und die Schreibgeschwindigkeit von Festplatten sind damit die begrenzenden Faktoren. Um trotz der größeren Adressierbarkeit den Platzbedarf für die größeren Block-Offsets zu verringern, nutzen wir komprimierte Pointer, die nur minimalen Speicher benötigen. Für unsere Lucene-Integration haben wir ein automatisches Sharding implementiert, da ein einzelner Lucene-Index nur 2,4 Milliarden Dokumente umfassen kann.
Außerdem wurde in vielen Integrations- und Clustertests sichergestellt, dass Neo4j auch auf großen Maschinen gut skaliert. Innerhalb unserer Partnerschaft mit IBM wird die Datenbank auf Maschinen mit bis zu 160 Kernen und bis zu 512 GB RAM auf Herz und Nieren geprüft. Für die Zukunft ist die Unterstützung von CAPI-Flash mit mehr als 50 TB RAM in Arbeit.
Unser effizienter, kostenbasierter Abfrageplaner für Cypher wird jetzt auch für Statements genutzt, die in die Datenbank schreiben. Bisher war das nur für reine Leseabfragen der Fall. Besonders bei komplexen, gemischten Lese-Schreib-Abfragen profitiert man jetzt vom höheren Durchsatz, da durch die transaktional aktuellen Statistiken bessere Entscheidungen für die Ermittlung eines Ausführungsplans gemacht werden.
Bisher konnte der Neo4j-Server über HTTP-APIs angesprochen werden. Diese wurden sowohl von Datenbanktreibern als auch Webanwendungen wie unserer eigenen Oberfläche, dem Neo4j-Browser, direkt genutzt. Für die Version 3.0 wurde ein neues Binärprotokoll namens Bolt entwickelt. Dieses lässt neben der kompakten und schnellen Datenübertragung auch Raum für zukünftige Entwicklungen. Es ist ein versioniertes Protokoll. Ein Server kann deswegen mehrere Clientversionen unterstützen. Für Sicherheit ist mit zertifikatsbasierter Verschlüsselung der Transportschicht (TLS) und Authentifizierung gesorgt. Der Transport erfolgt über TCP Sockets und über WebSockets im Browser. Das Protokoll basiert auf einer Variante von MessagePack namens PackStream, die zusätzlich komplex geschachtelte Datentypen, Graphelemente wie Knoten, Beziehungen und Pfade sowie generelle Erweiterbarkeit bereitstellt.
Für die Nutzung des neuen Protokolls gibt es erstmals in der Geschichte von Neo4j offiziell unterstützte Treiber [1]. Bisher haben wir, wie für Open-Source-Projekte häufig der Fall, Treiber genutzt, die von Entwicklern in unserer Nutzergemeinschaft entwickelt wurden. Jetzt wollen wir unseren Nutzern zumindest einen konsistenten Basistreiber für die meisten Programmiersprachen anbieten. Für Java, JavaScript, .NET und Python ist das jetzt schon der Fall. Die C- und PHP-Treiber wurden in unserer Partnercommunity entwickelt. Bolt-Treiber für weitere Sprachen werden in Zukunft folgen.
Da alle Treiber dieselben Konzepte und APIs implementieren und das Cypher-Typsystem zur Anwendung bringen, sieht das API in fast jeder Sprache ähnlich aus. Wer Java kennt, wird Parallelen zu anderen Datenbank-APIs wie JDBC erkennen. Hier am Beispiel von Java:
Driver driver = GraphDatabase.driver("bolt://localhost");
try (Session session = driver.session()) {
StatementResult result = session.run(
"MATCH (p:Person) WHERE p.name CONTAINS {name} RETURN p", Values.parameters("name","Sven"));
while (result.hasNext()) {
Record record = result.next();
record.get("name");
record.get("age");
}
}
Diese neuen Treiber lassen sich leicht in eigene Anwendungen einbinden. Sie sind leichtgewichtig und stehen unter der liberalen Apache-2-Lizenz. Zwei Projekte, die schon Gebrauch davon machen, sind der Neo4j-JDBC-Treiber [2] und der neue Apache Spark Connector [3]. Demos wie die Integration in Webanwendungen und Beispielanwendungen sind verfügbar [4].
Zuerst einmal fragt man sich natürlich, wie denn eine Graphdatenbank, die gar kein SQL spricht und keine Tabellen kennt, mit JDBC klarkommt. Im Kern ist JDBC da flexibel, es werden textuelle, parametrisierbare Abfragen (Strings) an einen Treiber geschickt und tabellarische Ergebnisse zurückerwartet. Wie der Abfragetext aussieht (in unserem Fall Cypher) oder welches Datenmodell die Datenbank fährt (in Neo4j Graph), ist JDBC herzlich egal. Wenn die Datenbank dazu noch Transaktionen unterstützt, kann man ein großes Featureset von JDBC direkt nutzen. Das zu implementieren war gar nicht schwer. Wir hatten bisher schon einen JDBC-Treiber, der auf den HTTP-APIs aufgesetzt hat. Jetzt konnten wir eine sauberen Neuimplementierung basierend auf dem offiziellen Java-Bolt-Treiber vornehmen. Mittlerweile wurden auch HTTP und Java-Embedded als Protokolle nachgerüstet. Hier ein Beispiel für die Benutzung:
Connection con = DriverManager.getConnection("jdbc:neo4j:bolt://localhost");
String query = "MATCH (p:Person)-[:LIKES]->(b:Book) WHERE p.age < {1} "+
"RETURN b.title as title, count(*) as freq";
try (PreparedStatement pstmt = con.prepareStatement(query)) {
pstmt.setInt(1,25);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("title")+" "+rs.getInt("freq"));
}
}
con.close();
Mit der Verfügbarkeit eines JDBC-Treibers stehen einem plötzlich alle Tools, Integrationsbibliotheken und -frameworks zu Verfügung. Besonders für Nichtentwickler ist die Nutzung von Daten in Neo4j mittels Reporting- (JasperReports, BIRT), BI- (QlikView, Tableau, Cognos), ETL- (Talend, Pentaho) und Analysetools (MathLab) möglich. Für Entwickler stehen alle Frameworks offen, die eine JDBC-Integration haben, z. B. Spring, MyBatis oder Play. Das gilt auch für Datenbanktools wie SQuirreL SQL, SQL Explorer, IntelliJ oder DataGrip.
Apache Spark ist als speicherbasierte, massiv-parallele Datenverarbeitungslösung in aller Munde, und die meisten IT- und BI-Abteilungen machen ihre ersten oder zweiten Schritte damit. Für viele Datenquellen und -senken bietet Spark selbst schon Integrationen an, z. B. auch über JDBC. Andere sind über das spark-packages.org-Repository verfügbar. Für Neo4j ist besonders interessant, dass Spark auch massiv-parallele Graphoperationen unterstützt, sodass man z. B. eine Milliarde Beziehungen für einen Page-Rank-Algorithmus verarbeiten kann. Daher war mit der Verfügbarkeit des Bolt-Treibers die Möglichkeit gegeben, einen offiziellen Neo4j-Spark-Connector zu entwickeln. Dieser kann, basierend auf Cypher, parallele Lese- und Schreiboperationen auch in transaktionalen Batches ausführen und so den effizienten bidirektionalen Datentransfer sicherstellen. Nach Einarbeitung in die Konzepte von Spark war es mir möglich, Unterstützung für RDDs (Resilient Distributed Dataset), DataFrames, GraphX und GraphFrames zu implementieren. Listing 1 und 2 zeigen als Beispiel ein soziales Netzwerk mit der Spark-Shell und GraphX für das Erzeugen eines sozialen Netzwerks mit 100 000 Personen und einer Million Beziehungen.
Listing 1: Beispiel soziales Netzwerk
CREATE CONSTRAINT ON (p:Person) ASSERT p.id IS UNIQUE;
UNWIND range(1,100000) AS x
CREATE (n:Person {id:x, name:"name"+x, age:x%100})
WITH n
UNWIND range(1,10) as round
MATCH (m:Person) WHERE m.id = toInt(rand()*100000)
CREATE (n)-[:KNOWS]->(m);
$SPARK_HOME/bin/spark-shell --packages neo4j-contrib:neo4j-spark-connector:1.0.0-RC1
Listing 2: GraphX-Beispiel
import org.neo4j.spark._
val g = Neo4jGraph.loadGraph(sc, label1="Person", relTypes=Seq("KNOWS"), label2="Person")
// g: org.apache.spark.graphx.Graph[Any,Int] = org.apache.spark.graphx.impl.// GraphImpl@57498
// Größe des Teilgraphen
g.vertices.count // res0: Long = 999937
g.edges.count // res1: Long = 999906
// PageRank mit fünf Iterationen
import org.apache.spark.graphx._
import org.apache.spark.graphx.lib._
val g2 = PageRank.run(g, numIter = 5)
// Rang von fünf Knoten
val v = g2.vertices.take(5)
// v: Array[(org.apache.spark.graphx.VertexId, Double)] =
// Array((185012,0.15), (612052,1.0153), (354796,0.15), (182316,0.15), (199516,0.385))
// die Ranginformationen werden als 'rank' Attribut nach Neo4j zurückgeschrieben
Neo4jGraph.saveGraph(sc, g2, nodeProp = "rank", relProp = null)
// res2: (Long, Long) = (999937,0)
Wie auch in anderen Datenbanken wird es mit Neo4j 3.0 möglich, spezifische Erweiterungen der Abfragesprache als Prozeduren einzubringen. Die Prozeduren lassen sich in Java und anderen JVM-Sprachen mittels annotierter Methoden implementieren.
Hier ein einfaches Beispiel zur Generierung von UUIDs: Die Prozedur ruft die UUID-Klasse aus dem JDK auf, um die UUID zu generieren, und verpackt sie dann in ein einfaches DTO, das für die Spaltenzuordnungen der Prozedurergebnisse verantwortlich ist. Danach werden die Ergebnisse, je nach Kardinalität der Prozedur, mit einem Java-8-Stream an Neo4j zurückgegeben und von Cypher an den Aufrufer weitergereicht. Da Cypher eine Sprache ist, die ihre Ergebnisse inkrementell bereitstellt, kann dieser Stream in geeigneten Fällen direkt bis zum Endnutzer durchgereicht werden:
@Procedure("apoc.create.uuid")
public Stream<UUIDResult> uuid() {
return Stream.of(new UUIDResult(UUID.randomUUID().toString()));
}
static class UUIDResult {
public final String uuid;
...
}
In Cypher werden Prozeduren mittels CALL procedure(parameter) YIELD result integriert. Es gibt auch einen Standalone-Modus, in dem der Prozeduraufruf das ganze Statement ausmacht:
LOAD CSV WITH HEADERS FROM {url} AS row
CALL apoc.create.uuid() yield uuid
CREATE (:Person {id:uuid, name: row.name, born: row.dob });
Natürlich ist in einer solchen Prozedur alles möglich, was das Java-Backend zu bieten hat. So hat man viele Möglichkeiten, aber auch die Verantwortung dafür. Nützliche Prozeduren sind: Komplexe Graphalgorithmen, das zur Verfügungstellen von Neo4j-Java-APIs in Cypher, Datenkonvertierung, Import und Export. Aber auch ausgefallenere Sachen, wie Datentransfer von und zu anderen Datenbanken und APIs sind möglich, genauso wie die Generierung von Metainformationen. All das haben wir in unserem APOC-Projekt (Awesome Procedures for Neo4j) [5] umgesetzt, in dem jetzt mittlerweile knapp 200 Prozeduren viele verschiedene Anwendungsbereiche abdecken.
Die Laufzeitinfrastruktur von Neo4j ist über die Jahre gewachsen. Dadurch sind einige Inkonsistenzen entstanden. Mit der aktuellen Version wurde dieser Teil der Datenbank generalüberholt. Die Startskripte, Konfigurationsdateien, Logdateien, Betriebssystemintegration und Installationspakete wurden überarbeitet und konsolidiert. Damit sollte auch die Integration in Unix-Systemlandschaften leichter werden.
Wer bisher Neo4j genutzt hat, sollte sich auf jeden Fall die Upgradedokumentation [6] anschauen, da sich in diesem Bereich fast alle Namen, Verzeichnisse und Dateien geändert haben. Für die leichteren Wechsel auf die neue Version gibt es ein Konfigurationsmigrationstool und den direkten Import einer Datenbank und Konfiguration:
java -jar $NEO4J_HOME/bin/tools/config-migrator.jar pfad/zu/neo4j2.x pfad/zu/neo4j3.x
$NEO4J_HOME/bin/neo4j-admin import --mode=database --database=graph.db --from=pfad/zu/neo4j2.x
In Neo4j 2.3 wurde die umfangreiche PowerShell-Integration für Windows vorgenommen. Diese wurde jetzt in einfachere Skripte gekapselt. Die Docker-Images und Debian-Pakete wurden ebenso aktualisiert.
Michael Hunger arbeitet seit Mitte 2010 eng mit Neo Technology zusammen, um deren Graphdatenbank Neo4j noch leichter für Entwickler zugänglich zu machen. Hauptinteressensgebiete sind dort Integration mit anderen Technologien, wie Spring Data, Datenimport und Performance. Er arbeitet an mehreren Open-Source-Projekten mit, ist Autor, Editor, Buchreviewer und häufiger Sprecher und Organisator von Konferenzen.
[1] Binärprotokoll: https://neo4j.com/developer/language-guides/
[2] Neo4j-JDBC-Treiber: https://github.com/neo4j-contrib/neo4j-jdbc
[3] Apache Spark Connector: https://github.com/neo4j-contrib/neo4j-spark-connector
[4] Beispielanwendungen: https://github.com/neo4j-examples
[5] APOC: https://github.com/neo4j-contrib/neo4j-apoc-procedures
[6] Upgradedokumentation: https://neo4j.com/guides/upgrade/#neo4j-3-0
Es ist noch gar nicht lange her, da interessierte sich in den meisten Unternehmen allerhöchstens der IT-Betrieb für ein systematisches Logmanagement. Im Zuge von aktuellen technologischen und methodischen Entwicklungen wie Cloud Computing, Continuous Delivery und Continuous Deployment wird es für Unternehmen immer wichtiger, den Zustand eines Systems genau zu kennen und schnelle Fehlerdiagnosen zu erstellen.
Je verteilter die Anwendungen, umso wichtiger ist die zentrale Logspeicherung. Immer mehr Anwendungen werden in privaten oder öffentlichen Clouds betrieben [1]. Die Anwendungen laufen also nicht mehr auf realen Servern, sondern in virtuellen Maschinen oder Containern. Mit diesem Trend zu leichtgewichtigeren Umgebungen verändert sich auch ihre Lebensdauer. Anstatt einen Server immer wieder manuell zu hegen und zu pflegen, werden Container oder virtuelle Maschinen direkt neu erstellt und alte Versionen gelöscht. Ohne ein zentrales Logmanagement gehen die Logs der Anwendungen also verloren und damit auch eine wichtige Informationsquelle, die gebraucht wird, um Development und Operations bei der Untersuchung von Fehlern zu unterstützen.
Gleichzeitig beobachten wir eine Entwicklung, die von Applikationsservern und monolithischen Applikationen weggeht hin zu verteilten Anwendungen, bei denen jede Business-Capability von einem eigenen Microservice umgesetzt wird. Gerade in verteilten Umgebungen ist es für den Betrieb und die Softwarewartung schwierig, Anfragen über verschiedene Knoten zu verfolgen und in den einzelnen Applikationslogs nach Hinweisen zu suchen. Traditionell müssen sich die Administratoren dazu an jedem Knoten anmelden und dann die Logs durchforsten oder sie manuell zusammentragen. Das ist zeitaufwändig und skaliert nicht.
Mittlerweile gibt es viele verschiedene Logmanagementlösungen sowohl im Open-Source-Bereich als auch von kommerziellen Anbietern. Dabei kann der Betrieb in der eigenen Infrastruktur oder auch in der Cloud erfolgen. Graylog ist ein Open-Source-Werkzeug, das eine integrierte Plattform für das Sammeln, Indizieren und Analysieren von Logdaten anbietet. Im Wesentlichen besteht das System aus dem Graylog-Web-Interface, den Graylog-Servern, den Elasticsearch-Knoten und einer Mongo-Datenbank (Abb. 1). Die Knoten lassen sich nach Bedarf skalieren. Zum Testen reicht ein System aus, bei dem alles in einem Knoten vereint ist. Der Graylog-Server ist das zentrale Element der Architektur, der sich um das Management der Elasticsearch-Indizes kümmert und einen Abstraktionslayer bildet. Es wäre daher möglich, Elasticsearch gegen ein anderes System zu tauschen, das für die Analyse der Logdaten besonders geeignet ist.
Graylog unterstützt verschiedene Inputmechanismen. Standardmäßig werden vier verschiedene Formate oder Protokolle unterstützt: syslog, GELF, JSON/REST-URLs und RAW. syslog ist ein Standard zur Übermittlung von Logmeldungen und wird häufig von Systemkomponenten verwendet. Daher eignet sich dieser Transport recht gut, um systemnahe Logmeldungen an Graylog zu senden, z. B. HAProxy-Lognachrichten. Setzt man unter Graylog eine syslog-Eingabeschnittstelle auf, die auf Host und Port 10.20.30.6:11002 lauscht, kann HAProxy dorthin loggen, wenn man HAProxy wie folgt konfiguriert:
global
log 10.20.30.6:11002 local0 info
GELF ist ein offenes von Graylog spezifiziertes Format, um strukturierte Lognachrichten zu unterstützen. Es ist für eigene Applikationen sehr zu empfehlen. Wenn der JSON-Input benutzt wird, lässt sich die JSON-Antwort einer REST-Ressource einfach auswerten. Diese Funktion kann dafür benutzt werden, JMX Beans abzufragen, die über Jolokia [2] exportiert werden. Die RAW-Eingangsschnittstelle ermöglicht es, Text über TCP/UDP entgegenzunehmen und mittels eigener Extraktoren zu parsen. So wird jedes beliebige textbasierte Logformat unterstützt.
Logs sind häufig unstrukturiert. Die wirklichen Vorteile von Logmanagmentwerkzeugen erzielt man aber nur, wenn man mehr Struktur in die Logs bringt. Unstrukturierte Lognachrichten müssen daher häufig geparst und in einzelne Bestandteile zerlegt werden. Dies ist auch in Graylog über die Definition von Extraktoren möglich. Einfacher wird es, wenn die Logdaten schon in einem strukturierten Format ankommen. Graylog propagiert daher das Graylog Extended Log Format (GELF). Mit diesem ist es möglich, Logevents mit strukturierten Informationen anzureichern. Dazu gibt es einige vordefinierte Felder, wie host, timestamp, level, und zusätzliche Felder, die per Konvention mit einem Unterstrich beginnen. Das GELF-Format wird von einigen Applikationen schon nativ unterstützt. So bietet Docker einen GELF-Logtreiber an. Ansonsten gibt es für die gängigen Java-Logframeworks entsprechende GELF Appender. Diese lassen sich dann entweder direkt in der Anwendung oder im Applikationsserver konfigurieren.
Neben der eigentlichen Lognachricht übertragen diese Appender die Werte aus dem Mapped Diagnostic Context (MDC) in die GELF-Nachricht. Im MDC lassen sich zusätzliche Metainformationen zu einem Logeintrag ablegen. Wer Log4j 2 nutzt, kann dazu auch den Threadcontext verwenden. Besonders interessante Daten für den MDC sind jene, die es ermöglichen, verschiedene Nachrichten von unterschiedlichen Systemen zu korrelieren, etwa Logausgaben vom Load Balancer und Applikationsserver, oder Daten, die in den Kontext einer Logausgabe gesetzt werden können, etwa URLs von REST-Ressourcen.
Unique IDs können für unterschiedliche Zwecke zum Einsatz kommen. Man kann beispielsweise mit ihnen eine Anfrage über verschiedene Systeme hin verfolgen. Das folgende Skript zeigt, wie man mittels HAProxy eine UniqueId injiziert:
frontend www-https
# adding unique id for all requests that can be used for logging
acl existing-unique-id req.hdr(X-Unique-ID) -m found
http-request set-header X-Unique-ID %{+X}o\ %ci:%cp_%fi:%fp_%Ts_%rt:%pid unless existing-unique-id
In diesem Beispiel wird ein HTTP-Header X-Unique-ID erzeugt, falls er noch nicht existiert. Der letzte Punkt ist wichtig, wenn ein erster Request von außen Requests zwischen Subsystemen auslöst. Da eine Unique ID nur einmalig generiert wird, kann man innerhalb der Logmanagementsoftware alle Logausgaben nach ihr filtern. Dies setzt allerdings voraus, dass die eigene Anwendung diese Information in einem Message Diagnostic Context setzt und dann bei weiteren Aufrufen propagiert. Das kann man selbst implementieren oder auch mithilfe einer Library wie TracEE [3]. Neben einer selbst generierten Unique ID eignen sich auch andere IDs, wie eine JMS Message ID. Wer Lösungen für größere Umgebungen benötigt, sollte sich in diesem Zusammenhang Zipkin [4] anschauen. Je nach Anwendungsfall kann auch der Einsatz von Tools für Application Performance Management sinnvoll sein, um herauszufinden, wo genau sich die Engstellen bei der Anfragebearbeitung befinden.
Eine Aufgabe beim Logmanagement ist die Normalisierung. Typische Tasks sind dabei das Parsen und Konvertieren von Datums- und Zeitfeldern und das Vereinheitlichen von Feldnamen, die semantisch gleichen Inhalt enthalten. Wir haben schon gesehen, wie man Streams für HTTP-Codes erstellt und Korrelation über eindeutige IDs herstellen kann. Leider ist es so, dass jede Anwendung das Logging autonom realisiert. So sind HTTP-Codes bei dem einen System im Feld http_response_code enthalten, bei einem anderen System im Feld HTTP_CODE und beim dritten im Feld status_code. Bei Graylog lässt sich das Logging z. B. mittels Drools realisieren. Dies kann aktuell zwar noch nicht über die Oberfläche eingestellt werden, mit dem in Graylog Version 2 verfügbaren Message-Processing-Pipeline-Feature soll sich das aber ändern. Hat man die einzelnen Systeme unter Kontrolle, sollte man Konventionen für die Benennung von entsprechenden Feldern vereinbaren, um die Nachbearbeitung so einfach wie möglich zu halten.
Ebenso wichtig ist es, Lognachrichten zeitlich richtig zuordnen zu können. Es gibt sehr unterschiedliche String-Repräsentationen für Datums- und Zeitwerte. Über einen Konverter ist es möglich, diese extrahierten Werte in ein einheitliches Timestamp-Format zu konvertieren.
Ein weiterer wichtiger Punkt beim Logmanagement ist der Datenschutz. Wenn immer mehr Menschen auf Logs zugreifen können, müssen personenbezogene Daten anonymisiert werden. Dazu zählt z. B. die IP-Adresse. Auch das lässt sich über einen entsprechenden Konverter in Graylog realisieren.
Mit Streams lassen sich Nachrichten in Echtzeit in Kategorien einordnen. Dabei verwendet man für die Definition des Streams die gleichen Abfragen, die man für die Suche von Nachrichten verwendet. Ein solcher Stream kann im Anschluss für zwei wichtige Anwendungsfälle genutzt werden: Alarmierung und Zugriffskontrolle. Eine Alarmierungsfunktion sieht dann wie folgt aus: Man definiert einen Stream für HTTP-Fehler (400er- und 500er-Fehlercodes). Daraufhin kann man auf diesem Stream eine Bedingung definieren, die eine Benachrichtigung auslöst, wenn z. B. innerhalb von fünf Minuten mehr als zehn Nachrichten in den Stream einsortiert werden. Der Zugriff auf Logs wird bei Graylog 2 über Rollen geregelt. Eine Rolle gewährt Rechte auf bestimmte Streams. So kann man unterschiedliche Streams und Rollen für Entwickler und Administratoren definieren.
Graylog 2 besitzt auch die Möglichkeit, Dashboards zu erzeugen. Damit sind grafische Auswertungen zu Abfragen auf den Logdaten möglich, wie etwa Antwortzeiten oder die Anzahl an Ausnahmen in einer Anwendung pro Umgebung. Die Funktionalität von Graylog reicht häufig für einfache Dashboards aus. Allerdings sind spezialisierte Werkzeuge wie Grafana für komplexere und ansprechendere Darstellungen besser geeignet.
Außerdem lässt sich Graylog 2 einfach über Plug-ins oder Content Packs erweitern. Content Packs sind Zusammenfassungen von Inputs, Extractors, Streams, Dashboards und Ausgabekonfigurationen. Für eine Anzahl an Infrastrukturkomponenten stehen schon entsprechende Content Packs auf dem Graylog Marketplace [5] zur Verfügung. Content Packs sind aber auch eine Möglichkeit, aktuelle Konfigurationseinstellungen eines Servers zu exportieren, zu sichern und zu versionieren. Bei einem Content Pack handelt es sich um eine einfache JSON-Datei. Es eignet sich auch, um die Konfiguration von Graylog zwischen verschiedenen Installationen zu propagieren.
Graylog nutzt zum Speichern von Lognachrichten Elasticsearch. Der entsprechende Elasticsearch-Index wird dynamisch erzeugt. In der Dokumentation von Graylog empfehlen die Autoren deshalb, sich konsequent für einen Typ pro Feld zu entscheiden und dabei zu bleiben. Leider hat man über Graylog keinen direkten Einfluss darauf. Elastisearch versucht den Typ eines Felds in der Nachricht nämlich automatisch zu bestimmen. Hier liegt ein kleiner Stolperstein. Wenn der erste Wert numerisch ist, setzt Elasticsearch den Typ des Felds auf „numerisch“. Wenn die Werte aber eigentlich Strings sind, kommt es zu Indexfehlern, wenn nachfolgend Nachrichten mit beliebigen Zeichen für das entsprechende Feld gespeichert werden sollen. Dieses Problem kann man umgehen, indem man in der Elasticsearch-Konfiguration Dynamic Templates [6] definiert und den Typ des problematischen Felds direkt auf „String“ einstellt.
Zu wissen, ob Applikationen rund laufen, und schnelle Fehlerdiagnosen sind für Unternehmen essenziell wichtig. Logs sind eine wichtige Quelle für diese Informationen. Einerseits ist eine Datenzentrale heute wichtiger denn je. Andererseits ist aber auch der Einstieg ins Logmanagement viel einfacher geworden. Für Betreiber von verteilten oder Cloud-basierten Anwendungslandschaften ist daher jetzt ein guter Zeitpunkt, um Logmanagement als wichtigen Baustein zu etablieren. Mit Graylog 2 steht ein einfach zu administrierendes System bereit.
Richard Attermeyer arbeitet als Senior Solution Architect bei der OPITZ CONSULTING Deutschland GmbH. Er beschäftigt sich seit vielen Jahren mit der Architektur und Implementierung von Anwendungen im agilen Umfeld und fokussiert sich dabei aktuell auf moderne Architekturansätze rund um Microservices, Cloud, DevOps und Continuous Delivery.
[1] Bitkom Cloud Monitor 2015: https://www.bitkom.org/Publikationen/2015/Studien/Cloud-Monitor-2015/Cloud-Monitor-2015-KPMG-Bitkom-Research.pdf
[2] Jolokia: https://jolokia.org/
[3] TracEE: http://tracee.io/
[4] Zipkin: https://github.com/openzipkin/zipkin
[5] Graylog Marketplace: https://marketplace.graylog.org/
[6] Elasticsearch, Customizing Dynamic Mapping: https://www.elastic.co/guide/en/elasticsearch/guide/current/custom-dynamic-mapping.html
Immer wieder jammern Kunden, dass Systeme schlecht seien und die IT die Anforderungen überhaupt nicht erfüllt habe. Entwicklungsteams verteidigen sich damit, dass ihnen niemand gesagt hat, was das Produkt wirklich können soll. Sie schieben die Schuld auf schlechte Anforderungen. Hätte man diese Wünsche rechtzeitig und klar geäußert, dann wäre die Lösung auch skalierbar, erweiterbar, performant und sicher. Fachbereiche oder Marketingabteilungen kontern: Es war doch klar, dass wir nach dem europäischen auch den asiatischen Markt erobern wollen. Selbstverständlich muss das Produkt leicht an neue Gesetze, Standards und Normen adaptiert werden können. Warum hätten wir das explizit sagen sollen?
Auch wenn wir heute zum Glück die leidigen Wasserfallprojekte hinter uns gelassen haben – haben wir? –, so gilt weiterhin eine Aufgabentrennung zwischen Beteiligten. Architekten und Entwicklungsteams verantworten hauptsächlich die Lösungen von Problemstellungen. Die Problemstellung in Form von mehr oder weniger präzisen Anforderungen sollte als Input für die Architekturarbeit vorhanden sein. Wir wissen seit Langem, was zu einer guten Requirements-Spezifikation gehört [1], [2]. Im Wesentlichen sind das die treibenden Kräfte für Projekte und Produktentwicklung: Die Ziele oder die Vision, wo wir hinwollen, die Mitspieler (neudeutsch: Stakeholder), denn sie sind die Quellen für alle Wünsche und Anforderungen, sowie die Festlegung des Scopes (Kontext), denn unsere Spielwiese ist nicht beliebig groß. Teams sollten wissen, was sie gestalten dürfen, wovon sie die Finger lassen müssen und welche Dinge außerhalb ihres Einflussbereichs liegen. Außerdem müssen Teams die gewünschte Funktionalität des Systems sowie die Prozesse und Aufgaben kennen, die ein System unterstützen soll. Dazu gehören auch die Daten und Informationen, die das System verwalten und verarbeiten muss. Last but not least müssen sie die gewünschten Qualitäten kennen und alle technologischen und organisatorischen Randbedingungen, die sie bei der Entwicklung oder Weiterentwicklung einhalten müssen. Wir betrachten, wie Input heute typischerweise für Entwicklungsprojekte vorliegt.
Wir beide sind geteilter Meinung, wie gut oder schlecht unsere Branche heute in Bezug auf die Vorgabe guter Anforderungen ist. In Tabelle 1 ist ein Kompromiss zwischen Peter, der die Anforderungswelt etwas positiver sieht, und Gernot, der oft noch viel Schlimmeres in Projekten erleben musste. Übersichten schlimmer Anforderungsfehler haben wie auch in [4] und [5] zusammengestellt.
Benötigt |
In der Realität vorhanden |
|
---|---|---|
Projekttreiber |
Projektziele |
meist implizit bekannt |
Stakeholder |
oft unvollständig, nicht schriftlich |
|
Kontextabgrenzung (Scope des Systems) |
gar nicht oder unvollständig |
|
Funktionale Anforderungen |
Funktionen und Abläufe |
recht gut vorgegeben, meist jedoch umgangssprachlich ohne präzisere Modelle |
Daten |
oft implizit, ohne klare Definition |
|
Nicht funktionale Anforderungen |
Qualitätsanforderungen |
fehlen oft, falls vorhanden, dann oft lückenhaft |
Randbedingungen |
oft bekannt, aber Hörensagen |
Tabelle 1: Stand der Praxis bei zentralen Requirements-Themen
Der miserable Zustand vieler Anforderungen nervt gewaltig. Wir sollen unter Zeitdruck großartige Systeme bauen, aber unsere Auftraggeber oder andere Beteiligte möchten die Zeit für vernünftige Anforderungen am liebsten nicht investieren, weil „das ja alles ohnehin klar sein müsste“. Mitnichten, liebe Business-Stakeholder und Fachabteilungen, vielmehr startet nun folgender Teufelskreis:
Entwicklungsteam bekommt schlechte Anforderungen.
Weil viele Dinge noch unklar sind, implementiert das Team eine flexible oder generische Lösung. Das dauert länger als geplant. Zusätzlich wird Code umfangreicher und komplexer als er hätte sein müssen.
Fachabteilung schimpft über Entwicklungsteam.
Goto 1.
Softwareentwicklung ohne vernünftige Vorgaben ist etwa genauso unproduktiv wie eine Gruppenreise ohne klar definiertes Reiseziel. Alle Beteiligten stehen am Ausgangspunkt und lamentieren darüber, wie und wohin es gehen sollte, könnte oder müsste. Falls Sie mal versucht haben, mit mehr als zehn Personen ein Restaurant zum gemeinsamen Mittagessen auszusuchen, wissen Sie, was wir meinen. Die Anzahl der Meinungen ist in der Regel größer als die Anzahl der Beteiligten. Was Sie als Architekten und Entwickler im Fall unklarer oder schlechter Anforderungen tun sollten, nennen wir intelligent raten und aktiv Feedback einholen. Sie kennen sich in der Regel zumindest halbwegs mit der Materie, der Domäne und dem allgemeinen Umfeld aus. Damit sind Sie oder das Team durchaus in der Lage, explizite Annahmen (educated guesses) zu formulieren. Schreiben Sie diese Annahmen auf und beschaffen Sie sich dazu das Feedback der Anwender, Fachabteilungen oder Business-Stakeholder. Unserer Erfahrung nach führt diese Feedbackschleife recht schnell zu besseren Anforderungen. Der Preis dafür: Sie als Architekten oder Entwickler müssen etwas Zeit und Hirn investieren sowie ein paar methodische Grundlagen berücksichtigen.
Zuerst sollten Sie den Kontext Ihres Systems klären und damit dessen externe Schnittstellen festlegen. Wir haben im Dschungel realer Projekte gelernt, niemals ohne Kontextabgrenzung an der Entwicklung von Systemen zu arbeiten. Unserer Erfahrung nach kommen insbesondere bei externen Schnittstellen die gefährlichen impliziten Annahmen zum Tragen, die später im Produktivbetrieb dann ihre verheerende Wirkung in Form von Prio-1-Fehlern und schmerzhaften Eskalationen zeigen.
Dabei kann Kontextklärung so einfach sein: Ein simples Diagramm ergänzt um eine kleine Tabelle mit den Beschreibungen der externen Schnittstellen können Sie in kurzer Zeit fertigstellen und über die gesamte Entwicklungs- oder Lebenszeit des Systems kontinuierlich weiterpflegen. Dazu können Ihnen wesentliche Stakeholder in minutenschnelle konstruktive Rückmeldung geben. Ein Beispiel finden Sie in Abbildung 1.
Neben fehlendem Kontext schmerzt uns als Architekten am meisten der oft miserable Zustand von Qualitätsanforderungen: Alle Stakeholder erwarten implizit gute Qualität, aber was das für das System genau sein soll, bleibt implizit und im Dunkeln. Also kümmern Sie sich nach der Kontextklärung um Qualitätsanforderungen. Nutzen Sie dazu Qualitätsbäume und Szenarien [6]. Das sind einfache, pragmatische, aber hochgradig wirkungsvolle methodische Mittel dazu. Unser konkreter Vorschlag: Sammeln Sie mal die Themen (Qualitätsmerkmale), die überhaupt für Ihr System relevant sind, und priorisieren Sie sie. Einen guten Ausgangspunkt bietet das ISO-25010 Qualitätsmodell oder die Kapitel 10 bis 17 in [2]. Ergänzen Sie detaillierte Szenarien, die konkrete Erwartungshaltungen an das System beschreiben. Unserer Erfahrung nach verstehen praktisch alle Stakeholder solche Szenarien sofort und können gute Rückmeldung dazu geben. Ein kleines Beispiel finden Sie in Abbildung 2. Sie finden in [7] dutzendweise konkrete anonymisierte Beispiele aus realen Systemen.
Als Requirements und Architektur noch Phasen in Projekten waren, ging man davon aus, dass das Business (Fachabteilungen, Kunden, Product Owner, Marketing zusammen mit Analytikern) die Vorgaben macht und ein IT-Entwicklungsteam diese Vorgaben danach umsetzt. Im Grunde ist das o.k., denn IT sollte das Business unterstützen. Oft haben Techies viel bessere Kenntnisse über neue Technologien, die dem Business Dinge ermöglichen, die bisher gar nicht vorstellbar waren. Deshalb sollten sich Entwickler und insbesondere Architekten auch bei der Ausgestaltung von Anforderungen und bei der Ausarbeitung von alternativen Businesslösungen aktiv einbringen.
Wir wissen heute, dass Auftraggeber und Auftragnehmer in etwa im gleichen Tempo lernen, was wirklich für das Business gebraucht wird [3]. Manchmal kennt das Business den Bedarf besser, manchmal haben Architekten mehr Einsichten und Erfahrungen, um das Business durch innovative Lösungen voranzubringen. Durch gemeinsames Schaffen und Verbessern von Anforderungen und Lösungen sowie durch gezielte Wechselwirkungen zwischen Requirements und Architektur kommen Sie am ehesten zu marktgerechten, kundenfreundlichen und gewinnbringenden Produkten oder Systemen.
Klagen Sie als Architekt weniger über mangelhafte, schlecht dokumentierte Requirements. Nehmen Sie diesen Teil Ihres Schicksals in die eigene Hand und klären Sie Anforderungen. Gehen Sie auf Marketing, Businessanalysten, Product Owner, Produktmanager und andere relevante Stakeholder proaktiv zu. Helfen Sie ihnen, zu angemessenen Zeitpunkten festzulegen, was Sie oder Ihre Teams als Basis für Entwurfsentscheidungen dringend benötigen. Konzentrieren Sie sich dabei insbesondere auf konkrete Qualitätsanforderungen – am besten durch Szenarien untermauert – und schaffen Sie mit expliziter Kontextabgrenzung klare Absprachen an den externen Schnittstellen Ihres Systems.
Peter Hruschka (System Guild) und Dr. Gernot Starke (innoQ-Fellow) haben vor einigen Jahren www.arc42.de gegründet, das freie Portal für Softwarearchitekten. Sie sind Gründungsmitglieder des International Software Architecture Qualification Board (www.iSAQB.org) sowie Autoren mehrerer Bücher rund um Softwarearchitektur und Entwicklungsprozesse.
[1] Hruschka, Peter: „Business Analyse und Requirements Engineering: Produkte und Prozesse nachhaltig verbessern“, Carl Hanser Verlag, 2014
[2] Volere Requirements Specification Template: www.volere.de
[3] DeMarco, Tom, et al.: „Adrenalin Junkies und Formularzombies“, Carl Hanser Verlag, 2007
[4] Firesmith, Donald: „Common Requirements Problems, Their Negative Consequences, and the Industry Best Practices to Help Solve Them“: http://www.jot.fm/issues/issue_2007_01/column2.pdf
[5] Wiegers, Karl: „10 Requirements Traps to Avoid“: http://processimpact.com/articles/reqtraps.html
[6] Qualitätsszenarien: https://leanpub.com/isaqbglossary/read/#term-scenario
[7] Reale Beispiele für typische Qualitätsanforderungen: https://github.com/arc42/quality-requirements
Bei der Bildung einer digitalen Vertrauensbeziehung für den E-Commerce, bei sozialen Interaktionen oder bei Vertragsabschlüssen ist die korrekte Identifizierung unabdingbar. Damit ist sie ein essenzieller Bestandteil jeder Strategie zum Schutz von Informationen und Ressourcen vor unbefugtem Zugriff.
Die Sicherheit eines Websystems ist ein Basismerkmal [1]. Es wird von den Stakeholdern erwartet, ohne dass sie mit Ihnen darüber sprechen. Im Falle der Speicherung personenbezogener Daten ist die Sicherheit sogar von Rechts wegen vorgeschrieben. Dass Identifizierung nicht immer einfach ist und selbst bei Milliardenprojekten stümperhaft gelöst wird, hat unlängst Volkswagen demonstriert. Deren Autoschlüssel lassen sich in weniger als einer Sekunde knacken [2].
Dabei benötigt die sichere Authentifizierung keine Investitionen in Milliardenhöhe, wird aber auch nicht gratis geliefert. Zunächst müssen wir aber die Terminologie etwas auseinandernehmen, damit die Unterschiede und Zusammenhänge klar werden. Nicht verwechseln sollte man Authentisierung und Authentifizierung. Die Begriffe klingen ähnlich, aber die Authentisierung ist eine rechtskräftige Bestätigung und meint deswegen etwas völlig anderes. Authentifizierung ist hingegen die Bestätigung, dass etwas echt ist, also beispielsweise ein Passwort stimmt. Ob ein richtiges Passwort ausreicht, um einen Akteur zu identifizieren oder nicht, hängt davon ab, wie sicher das System sein soll. Der Grad an Sicherheit variiert über die Anzahl von Faktoren, wie Passwörter oder MTAN, die im Rahmen der Identifizierung authentifiziert werden (Abb. 1).
Identifizierung und Identifikation sind sich ähnlich wie Authentifizierung und Authentisierung. Die Identifikation ist ein Begriff aus der Psychologie, der den Vorgang beschreibt, sich in einen anderen Menschen einzufühlen. Identifizieren wird als Vorgang beschrieben, der zum eindeutigen Erkennen einer Person oder eines Objekts dient. Der letzte Begriff in diesem babylonischen Reigen ist die Autorisierung, also die Berechtigung einer Person nach der Identifizierung.
Die Techniken zur Authentifizierung werden nach Faktoren kategorisiert. Ein Identifizierung, die mehrere Faktoren benötigt, heißt Multi-Faktor-Authentifizierung. Die folgenden Faktoren gibt es:
Etwas, das man weiß (Passwort)
Etwas, das man besitzt (Mobiltelefon)
Etwas, das man ist (Retinascan oder Fingerabdruck)
Eine Identifizierung gilt heute als sicher oder stark authentifiziert, wenn zwei oder mehr Faktoren involviert sind. Der heute verbreitete Marktstandard ist immer noch die Verwendung eines Benutzernamens und eines Passworts, was als schwache Authentifizierung gilt. Immer verbreiteter werden mittlerweile die Zwei-Faktor-Authentifizierungen, kurz 2FA, die deutlich mehr Sicherheit bieten. Die FIDO-Allianz hat 2014 einen Standard für eine universelle und lizenzfreie 2FA veröffentlicht [3]. Der Trend geht aber heute bereits weiter zu Mehrfaktorauthentifizierung (MFA), da bereits erfolgreiche Man-in-the-Middle-Angriffe auch gegen 2FA festgestellt werden konnten. Die 2FA ist in unterschiedlichen Ausprägungen möglich, je nach Anwendungsfall.
Bei der mittelbaren 2FA wird üblicherweise der Besitz mit einem Hardwaretoken, z. B. einer Smartcard oder einem Schlüsselgenerator, analog RSA oder VASCO, definiert. Neben dieser klassischen Variante verbreitete sich in den letzten Jahren die tokenlose Authentifizierung. Bei dieser wird dem Benutzer per SMS, E-Mail oder Apps wie dem Google Authenticator ein dynamischer Passcode zugestellt. Eine Spezialität, die in Europa wenig verbreitet ist, ist die Zustellung eines Faktors über einen Telefonanruf. Mittelbare 2FA ist im Onlinebankingbereich oder auch bei Google mittlerweile Standard. Neben der mittelbaren 2FA sind auch halbautomatische oder vollautomatische Faktoren wie Near Field Communication (NFC) möglich. Bei einem automatischen Faktor ist keine Aktion durch den Benutzer notwendig, es muss also nichts eingegeben werden. Die Authentifizierung erfolgt hier beispielsweise über ein zuvor personalisiertes Mobilgerät.
Wesentlich für die Qualität der Identifizierung sind die Prozesse, die die Identifizierung möglich machen. Dabei ist der Initialisierungsprozess (Onboarding) wohl der wichtigste. Dieser knüpft das Individuum an ein Credential, respektive einen Account. Für diesen Prozess bestehen je nach Land und Anwendungsfall unterschiedliche rechtliche Anforderungen, die es zu beachten gilt. Die Eröffnung eines Bankkontos hat andere Anforderungen als die Eröffnung eines Accounts in einem Onlineshop, bei der im Minimum die Angabe einer E-Mail-Adresse notwendig ist.
Ein Account hat einen Lifecycle, von seiner Erzeugung bis hin zur Löschung. Ein Account muss sich anpassen lassen (Modifikation), da sich beispielsweise Name, E-Mail-Adresse oder andere Daten ändern können (Anpassung der Identität). Weitere Prozesse wären die Überprüfung (Account Assurance), die Sperrung oder Suspendierung inklusive Entsperrung sowie die Löschung eines Accounts. Kernbestandteil der Prozesse ist die wiederkehrende Prüfung, um die Sicherheit zu erhöhen (Abb. 2).
Im Rahmen der Identifizierung sollte man zwischen der Identity Assurance und der Credential Assurance unterscheiden. Die Identity Assurance ist die Gewissheit, ob eine Verbindung zu einem bestimmten Individuum hergestellt wurde. Die Credential Assurance ist die Gewissheit, dass aufgrund des Besitzes und der Kenntnis eines nicht kompromittierenden Faktors die Verbindung zu einem Individuum besteht. Im Fall der Identity Assurance müssen neben dem Besitz und der Kenntnis von Credentials auch die behaupteten Attribute des Individuums überprüft werden – je nach regulatorischen Anforderungen auf unterschiedliche Art und Weise. In der Praxis wird meistens nur überprüft, ob Credentials benutzt sind. In den wenigsten Fällen wird auch überprüft, ob sie noch zu dem ursprünglich zugeordneten Benutzer gehören. Die folgenden Verfahren haben sich bewährt und sollten nach Möglichkeit berücksichtigt werden: mehrstufige Prozesse und Rückfragen bei Anpassungen.
Zum einen haben wir die mehrstufigen Prozesse, insbesondere bei der Modifikation, dem Entsperren und dem Löschen von Credentials. Bei mehrstufigen Prozessen, wird die gewünschte Aktion erst ausgeführt, wenn eine Bestätigung durch den betroffenen Benutzer erfolgt. Dies kann sowohl mit einer 2FA erfolgen – analog einer Transaktionssignatur – als auch über einfache Bestätigungsprozesse, beispielsweise mittels E-Mail oder Telefonanruf. Zum anderen kann es Rückfragen bei Anpassungen geben. Sollen keine aufwendigen mehrstufigen Prozesse verwendet werden, so hilft in jedem Fall eine Information des Benutzers, dass an seinen Faktoren Anpassungen vorgenommen wurden. Der Benutzer hat so die Möglichkeit, sich im Fall von Unklarheiten zu melden, um eine Veränderung rückgängig zu machen. Ein Fallstrick ist hierbei die Anpassung der E-Mail-Adresse, denn die Information muss natürlich auf die alte E-Mail-Adresse des Benutzers erfolgen. Generell gilt, dass im Rahmen der Auditierung von Veränderungen auch die alten Werte eingesehen werden können.
Da nachfolgend Protokolle und Verfahren für die Authentifizierung erklärt werden, ist es notwendig, den grundlegenden Ablauf einer Identifizierung und die dazu notwendigen Komponenten zu erklären. Da sich dieser Artikel mehrheitlich mit der Sicherheit von Websystemen befasst, gehen wir davon aus, dass sich der Benutzer über einen Browser auf ein Websystem verbindet. Der hier beschriebene Ablauf ist sehr rudimentär und soll als Beispiel für die nachfolgend beschriebenen Verfahren und Protokolle dienen. Der Austausch der Informationen zwischen Benutzer und Webanwendung (Credentials oder Session-Token) erfolgt jeweils verschlüsselt und ist vor Manipulation geschützt. Jede Webanwendung, die eine vorherige Authentifizierung verlangt (Log-in), hat auch einen Log-out. Dies wird oft sträflich vernachlässigt. Die Schritte (Abb. 3) sind im Einzelnen:
Der Benutzer möchte sich auf eine geschützte Webseite verbinden.
Die Webanwendung stellt fest, dass für die geschützte Webseite eine Authentifikation notwendig ist. Sie sendet dem Benutzer ein Formular zur Eingabe der Credentials.
Der Benutzer füllt das Formular aus, bei MFA können dies auch mehrere Formulare sein, und sendet es an die Webanwendung.
Die Webanwendung prüft gegen ein Verzeichnis, ob der Benutzer vorhanden, ob er für die entsprechenden Webseiten berechtigt ist und ob seine Credentials stimmen.
Ist soweit alles in Ordnung, wird dem Benutzer ein Session-Token gesendet, das für die aktuelle Session gültig ist.
Mithilfe des Session-Tokens wird die Authentifizierung aufrechterhalten, ohne dass sich der Benutzer für jede Anfrage, die er zur Webanwendung sendet, wieder authentifizieren muss.
Tatsache ist, dass bezüglich des sicheren Handhabens von Sessions wohl am meisten Fehler gemacht werden und diese Punkte deshalb bei der Erstellung von Websystemen großer Sorgfalt bedürfen. OWASP [4] gibt hier sehr gute Richtwerte für die Einhaltung von Sicherheitsanforderungen von Websystemen.
Der Markt für Authentication as a Service (AaaS) wächst stark, denn es gibt eine klare Abhängigkeit zur zunehmenden Digitalisierung der Gesellschaft und der Unternehmen. Es gibt hier viele verschiedene Produkte, wie Azure AD oder SafeNet. An dieser Stelle möchten wir auf die Eigenschaften von SaaS-Angeboten eingehen, die heute am Markt erhältlich sind. Dabei geht es uns also um die reine Authentifizierung als Serviceangebot. Wir grenzen dieses hier genau ab, da es auch komplette IAM-(Identity-und-Access-Management-)Lösungen gibt, die neben der Authentifizierung auch den gesamten Account-Lifecycle und die Provisionierung zum Ziel haben.
Viele Firmen sind heute eine Zweiklassengesellschaft, was die Identifizierung von Mitarbeitern angeht: Mitarbeiter mit und Mitarbeiter ohne Accounts. Da beispielsweise Personalprozesse zunehmend digitalisiert werden, steigt der Bedarf an digitaler Identifizierung. Vor einer Anschaffung einer solchen Software oder der Transformation in einen Cloud-Service müssen diverse Überlegungen angestellt werden, um die richtige Lösung für eine Organisation zu finden.
Generell lassen sich von AaaS die gleichen kritischen Anmerkungen wie bei anderen Cloud-Lösungen anbringen. In vielen Fällen kommt bei Firmen heute bereits ein Dienstleister für die Authentifizierung zum Einsatz, z. B. wenn SMS-Tokens verwendet werden, sodass der Einbezug eines Dienstleisters nicht kategorisch ausgeschlossen werden sollte. Denn es ist auch möglich, einen DDoS-Angriff auf einen SMS-Provider vorzunehmen. Wichtig ist, dass man sich bewusst ist, was man dem Service-Provider anvertraut, und wie weit dieses dann Risiken für das eigene Geschäft nach sich zieht. Generell können hier die Richtlinien und Checklisten beispielsweise der Cloud Security Alliance für Detailfragen konsultiert werden [5]. Dabei gibt es hier mehrere Punkte, die besonderer Aufmerksamkeit bedürfen, wie im Folgenden beschrieben.
Zunächst gibt es Anforderungen an die Verfügbarkeit. Bei der Verwendung von AaaS ist zu beachten, dass der Service mindestens dasselbe Servicelevel erfüllt wie die Services, die damit bedient werden sollen. Wird der Service über das Internet bezogen, so ist es möglich, dass der Provider des Diensts einem DDoS-Angriff ausgesetzt wird. Wird die AaaS nur von Webapplikationen verwendet, die lediglich über das Internet verfügbar sind, sind diese über einen DDOS-Angriff auch angreifbar. Fakt bleibt aber, dass ich beim Einsatz von AaaS nur noch den Authentication-Provider angreifen muss, um alle meine Applikationen lahmzulegen. Es lohnt sich hier, die Maßnahmen des Service-Providers hinsichtlich solcher Szenarien zu prüfen. Wird der Service auch für interne Applikationen eingesetzt, wird dies meist über interne Komponenten abgewickelt, z. B. MFA-Server. Die Kommunikation zum Service-Provider wird in diesen Fällen meist nicht über das Internet, sondern über eine Mietleitung erfolgen.
Auch an die Performance werden Anforderungen gestellt. Wichtig ist, dass die Performance den Anforderungen entspricht und eine maximale Skalierung für die eigenen Bedürfnisse geprüft wird. Das bedeutet, dass der Anbieter ausreichend Kapazität für eine bestimmte Latenz bei einer maximalen Anzahl von Authentifizierungen pro Sekunde sicherstellt. Die Auslieferung von Tokens über SMS kann je nach Land etwas dauern. Bei eigenen Websystemen kann man dem Rechnung tragen (Authentication Time-out). Bei eingekauften Services, z. B. VPN-Client, oder SaaS-Applikationen muss hier der Anbieter der Software im Zweifelsfall notwendige Anpassungen vornehmen. Diese Punkte sollten vor der Beschaffung eines Authentifizierungsservice geprüft und auch getestet werden.
Die Vertraulichkeit ist auch ein wesentlicher Punkt. Die Anforderungen an die Vertraulichkeit der technischen Lösungen unterscheiden sich nicht, egal ob diese intern bereitgestellt oder als Service bezogen wird. Bei der Bereitstellung von AaaS ist aber zu beachten, wie sie genau erfolgt. Da ein Serviceanbieter viele Kunden hat, ist genau zu beachten, wie die Trennung der Mandanten erfolgt. Erfolgte Zertifizierungen helfen, diese Bewertung vorzunehmen.
Zudem gibt es Anforderung an die Nachweisbarkeit. Wichtig ist bei einem Bezug eines Service, dass eine lückenlose Nachweisbarkeit (oder Nichtabstreitbarkeit, engl. Non-repudiation) der Authentifizierungsvorgänge und der Konfigurationen vorliegen.
Zu den vertraglichen Aspekten: Setzt man einen Authentication-Provider beispielsweise für die Authentifizierung von Cloud-(SaaS-)Applikationen eines weiteren Providers ein, kann eine Dreiecksbeziehung bestehen. Diese muss vertraglich sauber definiert werden, damit im Fall eines Falles beispielsweise geklärt ist, wer auf die Logs zugreifen darf. Es könnte ja sein, dass eingebrochen wird, und dies wird durch den SaaS-Anbieter untersucht. Dann muss er eben auf die Daten des Authentication-Providers zugreifen dürfen.
Auch der Datenschutz spielt eine Rolle. Generell braucht der Service-Provider minimale Angaben, um eine Authentifizierung durchführen zu können. Im Minimum handelt es sich dabei um eine Verbindung des Geräts (bzw. Telefonnummer oder E-Mail Adresse), das für die Authentifizierung eingesetzt wird (2FA), und den Benutzer, für den eine Authentifizierung durchgeführt werden soll. Wird der Benutzer für diesen Vorgang anonymisiert, bestehen keine datenschutzrechtlichen Bedenken. Sofern es sich bei der Telefonnummer um eine private Telefonnummer handelt, möchte der Benutzer meistens nicht, dass diese Nummer weiter bekannt gemacht wird. Dem ist Rechnung zu tragen, ansonsten kann die Akzeptanz des Service darunter leiden.
DevOps: Die AaaS sollte sich gut in die Systemlandschaft integrieren lassen. Hierfür sind APIs für die Automatisierbarkeit der Identifizierungsprozesse ebenso notwendig wie die Möglichkeit der Einbindung in das eigene Monitoring.
Vertrauen gegen Risiko: Die meisten Benutzer benötigen keinen Zugang zu Daten und Dokumenten mit hoher Vertraulichkeit, andere aber teilweise schon. Da Sicherheit und Bedienbarkeit einander behindern, ist es sinnvoll, nicht dasselbe Zugangsverfahren bei jeder Gelegenheit zu verwenden, sondern abhängig von Sicherheitsklassifikation, Zeit, Lokation, Gerät und Benutzer eine andere Methode einzusetzen. Man nennt das auch risikobasierte Authentifizierung oder kontextbasierte Log-ins.
Zuletzt betrachten wir die Kosten, die Usability, die Prozesse und die Architektur. Zu den Kosten: Für den Vergleich der Kosten bei einem Bezug eines Cloud-Service sollten Eins-zu-eins-Vergleiche erstellt werden. So lässt sich herausfinden, ob das Kostenmodell des Providers günstiger sein wird als der Eigenbetrieb. Eine Bewertung der Usability gegen die Anforderungen der Benutzer sollte durchgeführt werden. Hierbei sollte man auch auf die Usability für den Service Desk achten. Bei den Prozessen stellt sich die Frage: Wie werden die Prozesse der Identifizierung mit der neuen AaaS-Lösung funktionieren? Reicht auch hier die Usability aus? Und zuletzt die Architektur: Ist nur On Premise oder nur Off Premise möglich, oder gibt es auch eine gemischte Variante?
Die korrekte Identifizierung ist für den digitalen Transformationsprozess unabdingbar. Ohne Identifizierung kein Geschäft, so lautet die einfache Formel, auf die immer mehr Organisationen Rücksicht nehmen müssen. Für die Evaluation eines Systems raten wir, einen Spezialisten zu bemühen. Ist ein Authentifizierungsdienst erst einmal ausgerollt, lässt er sich später praktisch nicht mehr ersetzen, da die Kosten hierfür zu hoch wären. Die Entscheidung hat also, wie viele andere Entscheidungen in der Architektur auch, eine große Tragweite und sollte mit Bedacht gefällt werden.
Gion Manetsch ist Security-Architekt und arbeitet in Bern. Seine Aufgabe ist es, die Sicherheit im Aufbau von IT-Lösungen sicherzustellen, unter der Berücksichtigung von Businessanforderungen und Wirtschaftlichkeit. Dabei sollen keine Insellösungen gebaut werden, sondern immer die Gesamtsicht im Vordergrund stehen.
Daniel Takai ist Enterprise-Architekt, arbeitet in der Schweiz und ist spezialisiert auf die digitale Transformation. Er berichtet regelmäßig im Java Magazin über seine Erfahrungen und Erkenntnisse. Im Frühling 2016 erscheint sein Buch über Methoden der Entwicklung von Cloud-basierten und serviceorientierten Websystemen.
[1] Kano, Noriaki; Seraku, Nobuhiko; Takashi, Fumio; Tsuji, Shin-ichi: „Attractive Quality and Must-Be Quality“, in: Journal of the Japanese Society for Quality Control 14, 1984
[2] Sicherheitslücken bei Funkschlüsseln: https://www.tagesschau.de/inland/funkschluessel-auto-101.html
[3] About The FIDO Alliance: https://fidoalliance.org/about/overview/
[4] OWASP: https://www.owasp.org/index.php/Main_Page
[5] Cloud Security Alliance: https://cloudsecurityalliance.org/