Projektspezifisch und dynamisch

Docker-Container als flexible Buildumgebung
Kommentare

Docker wird in diversen Varianten als Lösung für das Setup und die Kapselung lokaler Entwicklungsumgebungen, als temporär verfügbarer Service im Rahmen von Integrationstests und als Laufzeitumgebung auf Produktionsservern eingesetzt. Gerade der temporäre Charakter von Docker-Containern wird für einen anderen Anwendungsfall interessant: als dynamisch erzeugte Buildsysteme mit einer projektspezifischen Buildumgebung.

Gerade der temporäre Charakter von Docker-Containern wird für einen anderen Anwendungsfall interessant: als dynamisch erzeugte Buildsysteme mit einer projektspezifischen Buildumgebung.

Aktuell müssen CI-Server wie TeamCity, Jenkins oder Anbieter wie Travis CI auf die Vielzahl von verschiedenen Projektanforderungen vorbereitet sein. Dazu gehören beispielsweise verschiedene Versionen von Java, Node.js, Ruby, Go oder anderen Sprachen, Buildtools wie Maven oder Gradle in verschiedenen Versionsständen und sicherlich auch Datenbanken und sogar vollständige Betriebssysteme wie OS X. Dieser Artikel geht auf Herausforderungen und Möglichkeiten beim Einsatz von TeamCity ein, das beschriebene Setup eines Docker-Buildsystems ist für andere CI-Tools ebenso anwendbar.

DevOps Conference 2015

DevOpsConDie neue Konferenz für Docker, Infrastructure as Code, Continuous Delivery, Cloud und Lean Business startet am 1. bis 3. Juni in Berlin. Erleben Sie spannende Erfahrungsberichte und eine Fülle an wertvollem  Praxiswissen von den bekanntesten deutschsprachigen und internationalen DevOps-Experten. Holen Sie sich jetzt den entscheidenden Wissensvorsprung für die IT Ihres Unternehmens! Alle Infos auf www.devopsconference.de.

Projektspezifische Build-Server

Die Pflege umfassender Build-Systeme ist aufwändig und führt gerade bei auslaufendem Herstellersupport von älteren Versionen zur Frage, wie bzw. wann beispielsweise ältere Compiler vom Build-System entfernt werden können. Die parallele Nutzung auf einem Server mit mehreren Build-Agents kann problematisch werden, wenn versehentlich geteilte Ressourcen von zwei verschiedenen Builds modifiziert werden. Selbst bei gewöhnlichen Vorgängen wie dem Einspielen von Systemupdates müssen nicht nur ein Projekt, sondern praktisch alle Projekte im Vorfeld auf potenzielle Probleme geprüft werden. Der kommunikative Aufwand mit verschiedenen Teams steigt entsprechend. Man hat die Möglichkeit, projektspezifische Build-Server zu konfigurieren. Spezifische Server haben den Vorteil, dass man spezielle Optimierungen vornehmen kann: Manche Projekte benötigen zum Build mehr CPUs oder Kerne, andere Projekte lassen sich durch schnellere Festplatten beschleunigen. Solche Server funktionieren problemlos, sind nach Bedarf auf geänderte Anforderungen anpassbar und beeinflussen fremde Projekte nicht. Was passiert, wenn ein solcher spezifischer Server ausfällt? Andere Build-Systeme könnten als Fallback dienen, müssen allerdings meist um die bisher nicht benötigten Tools und deren Konfiguration ergänzt werden. Abgesehen davon, dass man unter einem wahrscheinlich höheren Zeitdruck steht, kann man nur schlecht abschätzen, ob die zwei auf einem Server vereinten Build-Systeme miteinander harmonieren.

Projektspezifische Build Images

Mit Docker hat man die Chance, gemeinsam nur einen Server zu nutzen, ohne die Kapselung eines individuellen Build-Systems aufzugeben: Dazu kann jedes Projekt ein Docker Image definieren, das sogar in Form eines Dockerfiles direkt neben dem Sourcecode liegen kann. Im Projekt findet man also eine vollständige Beschreibung des für einen erfolgreichen Build notwendigen Setups, das sogar passend zum aktuellen Softwarestand versioniert ist. Zur Beschreibung des Builds gehören einerseits die notwendigen Tools und Bibliotheken in den gewünschten Versionen, andererseits die notwendigen Kommandos, die den Build ausführen und die notwendigen Artefakte erzeugen. Als Beispiel sieht man in Listing 1 einen Node.js-basierten Build. Nach der Installation einiger projektspezifischer Pakete wird der Projektordner angelegt und die package.json aus dem Build-Kontext in den Container kopiert. Entsprechend dieser Hinweise wird bewusst nicht das komplette Projekt kopiert, um zunächst nur die Node.js-Pakete per npm install zu installieren. Falls die package.json seit dem letzten Build nicht geändert wurde, kann der Docker Daemon das schon vom letzten Build bekannte Image aus dem Cache verwenden. Docker hilft an dieser Stelle also auch, Build-Zeiten zu optimieren. Nach den vorbereitenden Schritten wird das vollständige Projekt in den Docker-Container kopiert, per grunt gebaut und getestet und abschließend ein Release-Artefakt als Archiv erzeugt.

FROM node:0.10
RUN apt-get update && apt-get install -y git fontconfig
RUN npm install -g grunt-cli
RUN mkdir -p /project
WORKDIR /project

# verwende nach Möglichkeit den Docker-Cache
ADD ./package.json /project/package.json
RUN npm install

ADD ./ /project/
RUN grunt --no-color release

ENV PACKAGE_FILENAME /release.tgz
RUN cd release && tar cfz $PACKAGE_FILENAME ./*

CMD ["ls", "-l", "$PACKAGE_FILENAME"]

Das beschriebene Image kann wie ein gewöhnlicher Docker Build erzeugt werden: docker build -t projekt-builder. Das innerhalb des Images liegende Artefakt muss noch verteilt oder kopiert werden. Ein Verteilen schon während des Builds kann durch einen Upload zu einem Repository-Manager gelöst werden. Um zu vermeiden, dass die dafür notwendigen Passwörter innerhalb des Images liegen, haben wir uns dafür entschieden, das Artefakt manuell aus dem fertig gestellten Image herauszukopieren. Abbildung 1 zeigt den Ablauf eines Builds mit Docker Daemon.

Abb. 1: Build per Docker-Container

Abb. 1: Build per Docker-Container

Eine Datei kann nicht ohne Weiteres aus einem Image auf das Hostsystem kopiert werden. Hilfsweise kann ein Container per docker run projekt-builder true kurz gestartet und direkt wieder beendet werden, sodass man per docker cp eine im Container liegende Datei kopieren kann. docker run führt wie hier beschrieben zwei Aktionen aus, nämlich zunächst ein create und danach ein start eines Containers. Das Docker-CLI ermöglicht schon das Starten eines vorhandenen Containers per docker start, ein entsprechendes Kommando docker create ist aber erst in einem auf Version 1.2.0 folgendem Docker-Release enthalten. Das Remote-API unterstützt schon jetzt das Erzeugen eines Containers, ohne ihn zu starten. Daher zeigt Listing 2 neben dem Erzeugen des projekt-builder Images, wie man unter Verwendung von üblichen Kommandozeilentools wie tar und curl sehr einfach das gewünschte Artefakt erhält. Falls der Docker Daemon nur per Unix Socket erreichbar sein sollte, ist ein Tool wie socat hilfreich. Allerdings verliert man dann die einfache Möglichkeit, das Remote-API aus dem Netzwerk zu nutzen.

tar -cz --exclude=./node_modules . | curl -X POST "$DOCKER_HOST/build?t=projekt-builder" -H "Content-Type: application/tar" -T -

curl -X DELETE "$DOCKER_HOST/containers/build-result"
curl -X POST "$DOCKER_HOST/containers/create?name=build-result" -d '{"Image":"projekt-builder"}' -H "Content-Type: application/json"
curl -X POST "$DOCKER_HOST/containers/build-result/copy" -d '{"Resource":"/release.tgz"}' -H "Content-Type: application/json" | tar xm -C ./target/

Die erste Anweisung in Listing 2 entspricht dem oben erwähnten docker build-Kommando. Der auf dem DOCKER_HOST verfügbare Docker Daemon erwartet einen so genannten Build-Kontext, der als Tar-Archiv übergeben wird. Im Wurzelverzeichnis des Build-Kontexts wird ein Dockerfile erwartet. Um den Build-Kontext nicht unnötig zu vergrößern, werden ggf. schon lokal liegende Dateien beim Erzeugen des Archivs per –exclude ausgeschlossen. Dies entspricht der vom Docker-CLI bekannten .dockerignore-Funktionalität. In der zweiten Anweisung entfernen wir einen möglicherweise von vorhergehenden Builds liegenden Container, sodass in der dritten Zeile ein neuer Container mit dem von uns gewählten Namen build-result auf Basis des projekt-builder Images erzeugt werden kann.

Die letzte Anweisung in Listing 2 kopiert das gewünschte Archiv aus dem Docker-Container. Wie hier beschrieben, muss man die gewünschte Ressource angeben und erhält in einem Tar-Archiv die gewünschte Datei. Dieses Tar-Archiv wird von uns schon innerhalb der letzten Anweisung per tar xm entpackt. Unter ./target/release.tgz liegt nun das Projektartefakt. Ein interessanter Aspekt, der in Listing 2 erkennbar wird, ist die Flexibilität des Docker Daemons, für beliebige Projekte einen Build auszuführen und ein oder mehrere Artefakte zu produzieren. Wird das Skript nun auf einem TeamCity-Agent ausgeführt, können die gewohnten Features des Agents, wie das Protokollieren von Logausgaben oder Test-Reporting, verwendet werden, und zwar während der tatsächliche Build auf einem beliebigen Server mit einem Docker Daemon ausgeführt wird. Der TeamCity-Agent und der Docker Daemon können natürlich auch auf demselben Server laufen. Die einführend beschriebenen, projektspezifischen Agents sind nicht mehr notwendig, sodass eine flexiblere CI-Infrastruktur ermöglicht wird. Falls Projekte wie oben erwähnt spezielle Anforderungen an die Hardware stellen, können über die Auswahl des Docker Daemons per DOCKER_HOST passend konfigurierte Server ausgewählt werden.

Aktueller Stand

Wir haben das oben beschriebene Konzept als Ersatz für einen Node.js-Build-Server eingeführt. Die bisher auf diesem Server durchgeführten Builds haben die in Listing 1 erkennbaren Aktionen ausgeführt. Üblicherweise dauerten das Auschecken des Codes und das npm install relativ lange (insgesamt bis zu vier Minuten) im Verhältnis zur restlichen Build-Dauer (ca. eineinhalb Minuten). Das implizit durch Docker eingeführte Caching hat nun zur Folge, dass das npm install nur dann ausgeführt wird, falls sich die package.json ändert. Die Build-Dauer inklusive Tests ist durch die Umstrukturierung auf ein Viertel der ursprünglichen Zeit gesunken. Solche Effekte sind sicherlich nicht für alle Build-Systeme möglich: Speziell wenn Build-Tools wie Gradle eigene Konzepte zum Cachen von Artefakten bereitstellen, kann ein auf Docker-Containern basierender Build solche Konzepte aushebeln. Man kann den oben beschriebenen Ablauf also nicht als allgemeine Optimierung betrachten. Am Beispiel von Gradle soll aber zumindest erwähnt werden, dass ein zwischen verschiedenen Servern gemeinsam genutzter Cache geplant ist.

Durch die technisch nachvollziehbare Beschreibung einer funktionierenden Build-Umgebung wird sogar die Integration neuer Mitarbeiter vereinfacht: Der Vorteil des Dockerfiles liegt darin, in direkter Nähe zum Applikations-Sourcecode zu liegen. Die ständige Verwendung in jedem Build sorgt für schnelles Feedback, sodass die Beschreibung des Projektsetups stets aktuell bleibt.

Inside Docker: Wenn Sie mehr über Docker wissen möchten, empfehlen wir Ihnen das Entwickler Magazin Spezial Vol. 2: Docker zum leichten Einstieg in die Container-Virtualisierung.
docker-coverMit Docker feiern Linux-Container momentan ein eindrucksvolles Comeback. Während der Einsatz von virtuellen Maschinen viele Vor-, aber auch zahlreiche Nachteile mit sich bringt, ist Docker eine leichtgewichtige, containerbasierte Alternative, die die System-Level-Virtualisierung auf ein neues Level hebt. Dabei ergänzt Docker das Deployment von Betriebssystemen und Webanwendungen um die Lösungen, die man beim Original schmerzlich vermisst. In diesem Jahr hat Docker eine hohe Dynamik entwickelt und wird in allen aktuellen Linux-Distributionen wie Redhat, SUSE oder Ubuntu ausgeliefert. Firmen wie Spotify, Google, BBC, eBay und seit kurzem auch Zalando setzen Docker bereits produktiv ein. Das Entwickler Magazin Spezial „Docker“ informiert kompetent über diese revolutionäre Technologie, von der viele meinen, dass sie eine neue Ära in der IT einläuten wird. Wir erklären technische Hintergründe, demonstrieren Best Practices und zeigen, wie Docker effektiv eingesetzt werden kann. Das Sonderheft vereint umfangreiches Wissen über die wichtigsten Aspekte von Docker, spannende Ideen für eigene Docker-Projekte und wertvolle Impulse für ihre strategische Planung.

Aufmacherbild: 3d rendered illustration of an industrial port with containers. Loading container von Shutterstock / Urheberrecht: Egorov Artem

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -