Microservices im Zusammenspiel mit Continuous Delivery, Teil 2 – die Ablaufumgebung

Seite 2: Vagrant

Inhaltsverzeichnis

Vagrant (engl. etwa: Tippelbruder) ist ein Werkzeug, das den Betrieb mehrerer Docker-Container auf einem Entwicklerrechner vereinfacht. Mit ihm können VMs – beispielsweise auf Basis von VirtualBox – gestartet und Software darauf installiert werden. Für Docker legt Vagrant eine VM mit einer Linux-Installation zurecht, auf der Docker eingerichtet wird und schließlich die verschiedenen Docker-Container gestartet werden.

Vagrant.configure("2") do |config|
config.vm.box = "chef/ubuntu-13.10"
config.vm.network "forwarded_port", guest: 8080, host: 8080

config.vm.provision "docker" do |d|
d.build_image "--tag=java /vagrant/java"
d.build_image "--tag=app /vagrant/app"
d.run "app",
args: "-p 8080:8080 -v /vagrant/target:/target"
end

end

Das Codebeispiel gibt einen Eindruck von diesem Setup: Zum Einsatz kommt eine Ubuntu-13.10-Installation. Der Port 8080 der Ubuntu-Vagrant-VM wird an den Port 8080 des Hosts gebunden. Dann werden die beiden Images für Java und die Anwendung erzeugt. Schließlich startet man das Image der Applikation. Dabei wird der Port 8080 des Containers an den Port 8080 der VM gebunden. Außerdem stellt Docker dem Container ein Verzeichnis aus der VM bereit, in dem die Java-Anwendung zur Verfügung steht. Einige Beispiele, die dem hier gezeigten recht ähnlich sind, finden sich online.

Das Erstellen von Docker-Containern ist oft einfacher als die Installation von Software beispielsweise mit Chef oder Puppet. Die Ansätze der Werkzeuge sind unterschiedlich: Chef und Puppet definieren, in welchem Zustand das System nach der Installation sein soll. Dockerfiles beschreiben nur die Schritte zur Installation eines Containers. Eine neue Version der Software oder Änderungen in der Konfiguration lassen sich mit Chef oder Puppet also gar in einer laufenden VM ausrollen. Die Skripte definieren den gewünschten Zielzustand. Beide Werkzeuge vergleichen den Zielzustand mit dem Istzustand und sorgen dann dafür, dass die notwendigen Schritte ausgeführt werden, um das System in den gewünschten Zustand zu überführen. Ist beispielsweise nur die Konfiguration eines Application Server zu ändern, werden nur diese Datei überschrieben und der Server neu gestartet. Ist hingegen noch gar kein Anwendungsserver installiert, kommt es zur vollständigen Installation und Konfiguration.

Ein Docker-Container lässt sich hingegen nur komplett neu installieren. Das verursacht aber weniger Aufwand, als es auf den ersten Blick scheint: Dockers Filesystem basiert auf einem Stack von Read-only-Dateisystemen, die aus Images erzeugt werden. Nach jedem RUN-Kommando in einem Dockerfile wird automatisch ein neues Images angelegt und außerdem kontrolliert, ob es schon ein Image mit dem gewünschten Stand gibt. Beim Erstellen einer neuen Version der Java-Anwendung wird lediglich ein neues Image mit dieser Datei angelegt – die Ubuntu- und Java-Installation sind ja schon vorhanden und nicht noch einmal neu zu erzeugen. So optimiert Docker die vollständige Neuinstallation so weit, dass das Update existierender Container überflüssig ist. Der Container lässt sich einfach wegwerfen und neu installieren.

In komplexeren Setups kollaborieren verschiedene Docker-Container miteinander. Sie können Daten dann über gemeinsam genutzte Dateisysteme oder über Netzwerk-Ports austauschen. So entsteht ein System, in dem jeder Container einen Microservice enthalten kann und sich das gesamte System dank Docker auch auf einer Entwicklermaschine starten lässt.

Die Docker-Images lassen sich auch in einem lokalen Repository ablegen. Ebenso können sie in Tar-Archiven abgelegt und eingelesen werden. Werkzeuge wie Shipyard erlauben es darüber hinaus, Docker-Container auf verschiedenen Rechnern zu verwalten. So ist es auch möglich, als Ergebnis eines Builds einen Docker-Container in einem Repository abzulegen und diesen daraus später zu starten.