IT-Automatisierung mit oder ohne Docker
Oft werden Container mit klassischen Provisionierungswerkzeugen verglichen, obwohl Container andere Problemstellungen lösen. Dieser Artikel zeigt, worin sich Container bzw. Werkzeuge aus dem Container-Umfeld und Provisionierungswerkzeuge unterscheiden und beleuchtet, in welchen Situationen es Sinn macht, auf ein klassisches Provisionierungswerkzeug zu setzen, wann das Container-Ökosystem die bessere Lösung ist oder auch eine Kombination aus beidem.
Container vs. Provisionierungswerkzeuge
Bevor wir darauf eingehen, wann welches Werkzeug am besten geeignet ist, sollen deren Aufgaben näher betrachtet werden. Container haben drei Aufgaben:
- Sie verpacken Anwendungen und ihre Abhängigkeiten zu einer Einheit.
- Sie isolieren diese von anderen Anwendungen.
- Sie standardisieren die Art und Weise der Auslieferung von Anwendungen.
Diese Definition passt nicht nur auf Docker-Container [1]. Gerade im Java-Umfeld passt diese Definition z. B. auf Webcontainer wie Apache Tomcat [2] oder auf Applikation-Server wie Wildfly [3]. Beim Tomcat wird die Anwendung, eine Java-Webanwendung, und ihre Abhängigkeiten (in Form von JAR-Dateien) in eine WAR-Datei (Web Application Archive) verpackt. Tomcat isoliert die Java-Webanwendungen voneinander durch Webcontexte innerhalb des Webcontainers und von anderen Prozessen auf dem Server, so dass es ein eigener Prozess ist. Auch die Art und Weise der Auslieferung der WAR-Dateien auf eine Tomcat-Instanz ist standardisiert. Außerdem haben die Entwickler im Java-Umfeld ihre Anwendungen als eine sogenannte Fat-JAR zu verpacken. Sie beinhaltet dann die Anwendung inklusiver Abhängigkeiten und sie wird als ein ganz normaler Java-Prozess gestartet.
Natürlich beschränken sich solche Container auf Java-Anwendungen, im dem Fall von Tomcat sogar auf Java-Webanwendungen. Wenn aber das Team nur Java-(Web-)anwendungen entwickelt, dann kann dieser Container eine Lösung sein. Kommt stattdessen ein anderer Technologie-Stack zum Einsatz, der keine eigenen Isolierungsmöglichkeiten bietet, wie z. B. das Ruby-, PHP- oder auch das NodeJS-Ökosystem, dann helfen Container-Technologien wie Docker und rkt [4] weiter.
Auf der anderen Seite automatisieren Provisionierungswerkzeuge – wie Ansible [5] oder Puppet [6] – die Provisionierung eines Servers. Die Server-Provisionierung ist eine Menge an Schritten, um einen Server mit Daten und Software vorzubereiten. Dazu zählen Schritte wie
- Ressourcen zuweisen und konfigurieren,
- Middleware installieren und konfigurieren und
- Anwendungen installieren und konfigurieren.
Diese Aufgaben können auch Shellskripte bewältigen. Provisionierungswerkzeuge haben aber gegenüber Shellskripten den Vorteil, dass ihre Skripte per se idempotent sind, d. h. sie werden mehrfach hintereinander ausgeführt und führen zum selben Ergebnis wie bei einer einzelnen Ausführung. Der Provisionierung ist es egal, welche Art Software auf einen Server installiert werden soll.
Es kann sich dabei z. B. um ein Apache Tomcat, Docker-Daemon, JVM oder auch Docker-Container handeln.
Nachdem die Aufgaben der jeweiligen Werkzeuge definiert sind, erklären die folgenden Abschnitten, wann und wie der Einsatz von Containern, Provisionierungswerkzeugen, einer Kombination von beiden Gruppen oder auch einer Kombination mit anderen Werkzeugen Sinn macht. Als konkrete Vertreter dienen Docker für Container und Ansible für Provisionierungswerkzeuge.
Folgende Situationen werden betrachtet:
- IT-Automatisierung im IT-Start-up-Umfeld,
- IT-Automatisierung in einer bestehender Infrastruktur und
- IT-Automatisierung der Infrastruktur für Anwendungen mit heterogenen Technologien.
IT-Automatisierung im IT-Start-up-Umfeld
Ein Start-Up-Unternehmen hat das Glück, auf einer grünen Wiesen anfangen zu können, d. h. es kämpft nicht mit Altlasten, sondern kann genau die Technologien nehmen, die am besten zum Problem passen. Gerade am Anfang ist es wichtig, dass das Unternehmen die Anwendung kontinuierlich ausliefert und seine vorhandenen Mittel in die Produktentwicklung steckt. Am Anfang ist die Anzahl der Benutzer gering und die Entwicklungsmannschaft klein. Der Fokus liegt auf einer schnellen Feature-Entwicklung. Daher bietet es sich an, das Aufsetzen der Infrastruktur und die Auslieferung der Anwendung von Anfang an zu automatisieren, um nicht Zeit mit manuellen Tätigkeiten zu verschwenden.
Weil die Mittel beschränkt sind und das Nutzungsverhalten der Benutzer nicht vorhersehbar ist, bietet es sich an, eine Cloudlösung für die Infrastruktur zu nehmen. Fällt die Entscheidung für eine Infrastructure-As-A-Service-Lösung, dann hilft Ansible in Kombination mit Terraform [7] dabei, die virtuellen Server automatisiert zu erstellen und zu provisionieren. Terraform ist für die Konfiguration und Erstellung der Server-Instanz beim Cloudprovider zuständig. Dabei beschreibt der Entwickler mit Hilfe einer deklarativen Konfigurationssprache, unabhängig vom Provider, wie die Server-Instanz aussehen soll. Dabei definiert der Entwickler z. B. in welcher Region die Instanz erzeugt werden soll, auf welchen Images es basieren soll, Größe des Servers (CPU, RAM etc.) usw. Sobald die Instanz erstellt und zugreifbar ist, kümmert sich ein Ansible-Skript um die Installation der benötigten Software für die spätere Anwendung.
Nachdem das Aufsetzen der Infrastruktur automatisiert ist, geht es um die Automatisierung der Softwareverteilung. Wie die Verteilung automatisiert wird, hängt vom gewählten Deployment-Artifakt ab. Dieses wiederum hängt davon ab, für welchen Technologiestack sich die Entwickler entschieden haben.
Entscheidet sich das Team z. B. den Java-Stack zu benutzen, dann reicht am Anfang ein einfaches Fat-JAR als Deployment-Artifakt für die Anwendung. Die Entwickler verteilen das Fat-JAR dann mit Hilfe von Shellskripten oder mit Ansible auf die Server.
Entscheiden sich die Entwickler für einen Technologiestack, der keinen eigenen Mechanismus hat, um die Anwendung und ihre Abhängigkeiten zu einer Einheit zu verpacken, dann kann hier Docker weiterhelfen. Die Entwickler verpacken die Anwendung und ihre Abhängigkeiten in einen Container und Ansible verteilt den Docker-Container auf den Server. Alternativ können die Entwickler die Anwendung und ihre Abhängigkeit direkt mit Ansible auf den Server verteilen. Das funktioniert solange gut bis die Abhängigkeiten sich ändern.
Dann muss das Ansible-Skript vorsehen, dass es die alten und die nicht mehr benötigten Abhängigkeiten wieder vom Server löscht, bevor die Anwendung neu auf den Servern verteilt wird oder es wird bei jedem Update der Anwendung eine neue Server-Instanz erstellt und provisioniert und die alte Server-Instanz einfach gelöscht. Hier muss das Entwicklungsteam abwägen, welches Vorgehen an meisten Zeit spart. Nicht nur bei der Provisionierung selbst, sondern auch bei der Pflege der Provisionierungsskripte.
Wenn die Entwickler sich für Docker-Container als Deployment-Artifakt entscheiden, dann kommt Kubernetes [8] für die Orchestrierung der Container schnell ins Gespräch. Am Anfang lohnt sich der Einsatz von Kubernetes nicht, da es Features wie Cluster-Management, Service-Discovery etc. mitbringt, die erst einmal nicht benötigt werden. Was nützt es dem Unternehmen, wenn es von Anfang viel Zeit und damit Geld in eine hochskalierbare Infrastruktur investiert, aber nicht weiß, ob die Anwendung diese Infrastruktur jemals ausreizen wird?
Die Infrastruktur muss mit der Anwendung wachsen und die Entwickler passen die Infrastruktur an, wenn neuen Anforderungen es notwendig machen. Solche Anforderungen entstehen z. B., wenn die Anzahl der Benutzer steigt und deswegen die Anwendung skalieren muss oder die Anwendung auf mehrere Deployment-Artifakte aufgeteilt wird und somit die Orchestrierung komplexer wird. Diese Anpassung erfolgt dann Schritt für Schritt und die Art und Weise richtet sich nach der jeweiligen Anforderung.
Wenn z. B. die Entwickler entscheiden, die Anwendung auf mehrere Container aufzuteilen, dann ist Docker Compose [9] eine leichtgewichtigere Lösung für die Orchestrierung der Container als eine Kubernetes-Installation und zusätzlich bringt Docker Compose Mechanismen für eine rudimentäre Skalierung der Container mit. Wenn dieses Werkzeug dann an seine Grenzen stößt, dann kann der Einsatz von Docker Swarm [10] eine Alternative zu Kubernetes sein. Docker Swarm ist in der Docker Engine integriert und der Einstieg ist einfacher als bei Kubernetes.
Wenn die Anforderungen dann doch einen Einsatz von Kubernetes rechtfertigen, dann sollte sich das Team die Frage stellen, ob es nicht günstiger ist, eine Cloudlösung zu nehmen, die eine fertige Kubernetes-Installation und Betrieb anbietet, statt Kubernetes selbst zu betreiben. Bei dieser Aussicht ist die Frage berechtigt, ob die Entwickler mit einem Java-Stack nicht gleich auf Docker-Container als Deployment-Artifakt setzen sollen. Die Antwort lautet: Nicht unbedingt, da es Alternativen zu Kubernetes und Docker Swarm gibt. Der Hashicorp-Stack z. B. bietet Lösungen für diese Anforderungen an. Er besteht aus mehreren Werkzeugen, deren Konzeption der UNIX-Philosophie [11] entspricht. Ein Benefit dieser Philosophie ist, dass der Entwickler genau das Werkzeug für sein gerade aktuelles Problem einsetzen kann und damit die Komplexität gering hält, weil dieses Werkzeug einen klare Aufgabe hat. Dennoch hat der Entwickler die Möglichkeit, bei Bedarf dieses Werkzeug mit anderen Werkzeugen zu kombinieren.
Im Beispiel Hashicorp-Stack ist Nomad [12] eine Lösung für das Cluster-Management, die neben Docker-Containern auch mit JARs als Deployment-Artifakt zurecht kommt, und Consul [13] eine Lösung für Service-Discovery. Dieser Ansatz versperrt nicht den Weg in Richtung Container, da dieser Stack auch Container als Deployment-Artifakt unterstützt und der Aufwand die Fat-JAR in einen Docker-Container zu verpacken relativ gering ist (s. Listing 1).
Listing 1:
From openjdk:10.0.1-10-slim ADD my-app.jar /opt/my-app.jar CMD java $JAVA_OPT -jar /opt/my-app.jar
IT-Automatisierung in einer bestehenden Infrastruktur
Bei einer existierenden Infrastruktur muss sich die IT-Automatisierung nach den Anwendungen richten, die darauf laufen sollen. Oft handelt es sich um Anwendungen mit einem älteren Technologiestack. Pflegt das Entwicklungsteam die Infrastruktur manuell, dann erhofft sich das Team durch eine Automatisierung vor allem eine Reduzierung des Pflegeaufwandes und der Fehlerquote. Hier können Provisionierungswerkzeuge wie Ansible helfen, indem das Entwicklungsteam die manuellen Schritte in einem Provisionierungsskript beschreiben. Damit vermeidet das Team einen kompletten Umbau der Infrastruktur. Das Entwicklungsteam wird ggf. einige Installationsprozesse angleichen, um die Provisionierungsskripte zu vereinfachen, aber an der Grundstruktur der Infrastruktur ändert sich nichts und die Entwickler können die Skripte für mehrere Anwendungen verwenden.
Der Einsatz einer Container-Technologie hilft hier erstmal nicht, da für ihren Einsatz erst die Infrastruktur umgebaut werden müsste. Auch muss hier das Entwicklungsteam testen, wie sich ihre Anwendung mit dem älteren Technologiestack im Docker-Container verhält und ob das Team ggf. die Anwendung für den Container-Einsatz anpassen muss. Der Einsatz von einer Container-Technologie erzeugt erstmal mehr Aufwand, ohne das eigentliche Problem der Automatisierung zu lösen. Das mag für eine Infrastruktur mit einer Anwendung gelten, die einen ähnlichen Technologiestack benutzt. Doch wie sieht es aus, wenn das Team eine Infrastruktur für Anwendungen mit unterschiedlichen Technologiestacks betreiben muss?
IT-Automatisierung der Infrastruktur für Anwendungen mit heterogenen Technologien
Wenn auf der existierenden Infrastruktur Anwendungen mit unterschiedlichen Technologien laufen, sind die Entwickler gezwungen, sich für jeden Technologiestack einen eigenen Prozess auszudenken. Jeder Technologiestack bringt ggf. sein eigenes Deployment-Artifakt und seinen eigenen Deployment-Prozess mit. Damit steigt der Aufwand bei der Automatisierung – das Unternehmen könnte versuchen, die Technologiestacks zu vereinheitlichen. Je nachdem, wie hoch die Anzahl der Stacks ist, können die Kosten einer Migration höher sein, als die Pflege unterschiedlicher Deploymentskripte.
Wenn das Unternehmen die Philosophie vertritt, dass jedes Entwicklungsteam seinen Technologiestack selbst entscheidet, dann ist sogar ein homogener Technologiestack gar nicht erwünscht. Hier helfen Docker-Container, den Deployment-Prozess zu vereinheitlichen, indem die Teams die jeweilige Anwendung in einen Container verpacken und somit ein einheitliches Deployment-Artifakt-Format über alle Teams hinweg entsteht. Das vereinfacht auch die Provisionierungsskripte für die Serverbereitstellung, da ggf. nur noch Docker auf dem Server vorinstalliert werden muss. Auch können ggf. die Serverkapazitäten besser genutzt werden, weil durch die Isolierung im Container der Betrieb unterschiedlicher Technologiestacks einfacher wird. Somit könne Server wegfallen, die nur ihre Daseinsberechtigung hatten, weil bestimmte Technologiestacks nicht zusammen auf einem Server betrieben werden konnten. Die Verteilung der Container kann dann wieder mit Ansible erfolgen.
Auch hier gilt wieder: die Entwickler fangen mit einfachen Mitteln an, ihre Container zu verteilen. Nur wenn die Anforderungen es notwendig machen, wird der Verteilungsprozess und ggf. die Infrastruktur angepasst.
Fazit
Docker allein hilft bei der IT-Automatisierung bedingt. Es kann aber vor allem bei Technologiestacks weiterhelfen, die kein eigenes Format für ein Deployment-Artifakt anbieten. Auch der Einsatz von Kubernetes und Co. ist oft nicht gerechtfertigt, wenn die einzige Anforderung ist, die Infrastruktur und die Softwareverteilung zu automatisieren.
- Docker
- Apache Tomcat
- Wildfly
- Coreos rkt
- Ansible
- Puppet
- Terraform
- Kubernetes
- Docker Compose
- Docker Swarm
- Wikipedia: Unix-Philosophie
- Nomad
- Consul