Kolumne: Docker rockt Java

Apache Tomcat meets Docker: Webanwendungen als Docker-Images herstellen

Peter Roßbach
docker-fuer-java

Das Docker-Ökosystem ist die ideale Umgebung, um Webanwendungen zu implementieren. Verschiedene Anwendungen auf einer Gruppe von Rechnern isoliert voneinander bereitzustellen, ist unser täglich Brot. Leider ist es mit der viel gelobten Portabilität im Java-Umfeld so eine Sache: Unsere Anwendungen müssen dennoch an die jeweilige Umgebung angepasst werden. Docker-Container können hier helfen, die Anwendungen und ihre Umgebung betriebsbereit und portabel zu produzieren. Wie in den meisten Fällen existieren unterschiedlichste Möglichkeiten, eine Anwendung zu verpacken. In diesem Beitrag werden verschiedene Bereitstellungen von Webanwendungen mit Docker beschrieben.

Docker-Container basieren auf den Funktionalitäten des Linux-Kernels und können in verschiedenen Umgebungen direkt betrieben werden. Damit wird ein Versprechen von Java „Write once, run anywhere“ systematisch umgesetzt: Es betrifft alle Komponenten eines Systems und nicht nur die Anwendungssoftware. Wir können somit in gleicher Art und Weise auch unsere Loadbalancer, Datenbank, Caches oder Message-Systeme bereitstellen. Die Leichtgewichtigkeit beruht auf den schon lange vorhandenen Linux-APIs wie Namespaces, cgroups, Capabilities, Software Defined Networks oder SELinux und den guten Erfahrungen von Cloud-PaaS-Providern mit der Linux-Container-Technologie. Da Docker direkt auf der Kernelvirtualisierung aufsetzt und ein Layered-Filesystem zur Bereitstellung der Software nutzt, ist Docker enorm ressourcenschonend im Vergleich zu herkömmlichen Virtualisierungslösungen. Docker fügt neben einem REST-Service für das Management der Container noch die Definition eines austauschbaren Image-Formats hinzu. Das Teilen von vorgefertigter, direkt ausführbarer Software ist mit Docker Realität geworden. Es befeuert die Idee, eine kontrollierte Umgebung von der Entwicklung bis in die Produktion zu nutzen. Webanwendungen oder gar Microservices lassen sich so einfacher realisieren und orchestrieren. Wichtiger Punkt ist, dass die Software in möglichst unabhängigen und autarken Einheiten bereitgestellt wird.

Bereitstellen von Apache-Tomcat-Docker-Images

Bekanntermaßen kann das Aufsetzen einer Entwicklungsumgebung für Java mit Apache Tomcat von vielen Tücken begleitet sein. Schnell kommt das Set-up einer Datenbank und weiterer Backends hinzu. Die Skalierung auf vielen Maschinen und ein unterbrechungsfreier Releasewechsel stellen sich schnell als die wirklichen Herausforderungen heraus. Das Aufsetzen einer einheitlichen Umgebung im Team und die Integration neuer Mitglieder ist leider oft eine zeitaufwendige Geschichte. Schnelle Auslieferung bedeutet auch schnelles Handeln im Team, im Code, in der Entwicklungsumgebung und in der Produktion. An dieser Stelle hilft nur, die eigene Automatisierung und Standardisierung stärker voranzutreiben. Docker kann auch hier helfen, die Infrastruktur mehr als Code zu begreifen. Viele vorgefertigte Images stehen auf dem offiziellen Docker-Hub schon bereit. Das Test-Set-up einer „fremden“ Komponente kann also nun in Sekunden oder Minuten erfolgen. Eine Herausforderung bleibt es allerdings, den eigenen individuellen Code schnell mit den bereitgestellten Services zu verheiraten oder ein eigenes Services-Image zu realisieren.

Auf der Basis des Trusted Java-Images kann die Bereitstellung des Tomcats relativ schnell erfolgen. Natürlich existiert bereits ein fertiges Tomcat-Image für die Entwicklung. Wie gewohnt wird eine aktuelle Tomcat-Distribution auf der Basis des Java-Images geladen (Listings 1 und 2). Da wir ein Image für die Produktion herstellen wollen, nutzen wir ein aktuelles JRE 8, löschen die Tomcat-Beispielanwendungen und Windows-Skripte. Dann werden die entsprechenden Ports preisgegeben und der Startbefehl für den Tomcat gesetzt.

Dieses Installationsmuster lässt sich auf alle Java-Artefakte übertragen. Wichtig ist noch, dass die Themen Wiederholbarkeit und Absicherung von Artefakten aus dem Internet kritisch betrachtet werden. Hier wird die MD5-Summe ebenfalls aus dem Netz geladen; das ist sicherlich praktisch, aber leider nicht immer ausreichend oder wirklich sicher. Wie vertraulich ist denn der DNS-Server, den Sie gerade nutzen? Darüber hinaus kann sich relativ schnell das Base-Image verändern. Um Wiederholbarkeit bei der Erstellung von Docker-Images zu gewährleisten, sollte lieber auf Basis von Tags, in diesem Fall openjdk-8u40-jre, gearbeitet werden.

FROM java:8-jre
MAINTAINER Peter Rossbach <peter.rossbach@bee42.com>

ENV TOMCAT_MINOR_VERSION=8.0.21 \
 CATALINA_HOME=/opt/tomcat \
 TOMCAT_URL=https://archive.apache.org/dist/tomcat/tomcat-8

RUN curl -O ${TOMCAT_URL}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz && \
 curl ${TOMCAT_URL}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz.md5 | md5sum -c - && \
 tar xzf apache-tomcat-*.tar.gz && \
 rm apache-tomcat-*.tar.gz* && mv apache-tomcat* ${CATALINA_HOME} && \
 rm -rf ${CATALINA_HOME}/webapps/* \
  ${CATALINA_HOME}/RELEASE-NOTES ${CATALINA_HOME}/RUNNING.txt \
  ${CATALINA_HOME}/bin/*.bat ${CATALINA_HOME}/bin/*.tar.gz

WORKDIR /opt/tomcat
EXPOSE 8080
EXPOSE 8009

ENTRYPOINT [ "/opt/tomcat/bin/catalina.sh" ]
CMD [ "run" ]
$ mkdir -p tomcat8
$ vi tomcat8/Dockerfile
...
$ docker build -t infrabricks/ex-tomcat:8 tomcat8
$ docker run --rm \
 --entrypoint=/opt/tomcat/bin/version.sh \
 infrabricks/ex-tomcat:8
Server version: Apache Tomcat/8.0.21
Server built:   Mar 23 2015 14:11:21 UTC
Server number:  8.0.21.0
OS Name:        Linux
OS Version:     3.18.5-tinycore64
Architecture:   amd64
JVM Version:    1.8.0_40-internal-b22
JVM Vendor:     Oracle Corporation
$ docker run -d -p 8080:8080 infrabricks/ex-tomcat:8
$ docker tag infrabricks/ex-tomcat:8 infrabricks/ex-tomcat:8.0.21
DATE='date +'%Y%m%d%H%M''
$ docker tag infrabricks/ex-tomcat:8 infrabricks/ex-tomcat:$DATE

Einen Anwendungscontainer herstellen

In unserem Beispiel zeigt eine Statusanwendung schnell die verschiedenen Möglichkeiten der Integration. Eine einfache JSP-Seite kann direkt als Volume eingebunden werden, die sich außerhalb des Containers befindet und dort auch modifiziert werden kann [Listings 3 und 4]. Ein Test der Anwendung kann mit einem Browser oder in der Shell mittels curl erfolgen. Wenn ein externer Zugriff gewünscht ist, muss der Tomcat-Port mit der Option -p 8080:8080 als stabiler Hostservice gewählt werden. Änderungen an der JSP werden in dieser Entwicklungsvariante mit einem Volume sofort in dem laufenden Tomcat berücksichtigt. Die Anwendung ist so noch nicht wirklich portabel oder statisch, aber eine bequeme Entwicklung ist wie gewohnt möglich. Das Image garantiert, dass auf allen Arbeitsplätzen und Testsystemen mit derselben Version und OS entwickelt wird. Neben der fehlenden Optimierung bestehen nun zwei Optionen für die Herstellung einer produktiven Version der Anwendung (Abb. 1). Entweder wird die Anwendung on top auf der Basis des Tomcat-Image verpackt oder es wird ein unabhängiger App-Container hergestellt. Die Anwendung wird dann als eine Containergruppe gestartet (Abb. 2). Falls gewünscht, kann dieser Ansatz noch vertieft werden und der Tomcat auch noch in einen eigenen Container auslagert werden (Abb. 3).

Abb. 1: Web-App auf der Basis eines Tomcat-Images

Abb. 1: Web-App auf der Basis eines Tomcat-Images

Abb. 2: Web-App als separater Container

Abb. 2: Web-App als separater Container

Abb. 3: Docker für die Java-Web-App-Entwicklung

Abb. 3: Docker für die Java-Web-App-Entwicklung

Der zweite Ansatz bietet uns weiterhin viel Flexibilität. Damit solche Gruppen in der Entwicklung einfacher eingesetzt werden können, existiert das Werkzeug docker-compose. Der Einsatz verschiedener Tomcat- oder Java-Releases ist kein Problem, und selbst der Einsatz mit einem anderen Webcontainer ist einfach durch entsprechende Komposition möglich. Genau diese Freiheitsgrade bescheren uns im Alltag viele unnötige Aufwendungen für Anpassungen, und zwar in der Entwicklung und dem Betrieb. Warum entscheidet nicht eigentlich der Hersteller, sprich Entwickler, welche Plattform am besten für seine Anwendung geeignet ist? Die Laufzeitoptimierung kann mit dem Administrator besprochen werden. Das Ergebnis ist endlich ein- und dieselbe Anwendung, die so in der Produktion und Entwicklung wirklich gleich ist. Ein paar unterschiedliche Konfigurationen werden sicherlich sinnvoll sein, und letztendlich kommt man um die Lieferung einiger Varianten vielleicht nicht herum, aber weniger ist hier eindeutig mehr. Die Anpassungen in diesem Ansatz konzentrieren sich auf die Bereitstellung von Credentials, eventuell Poolgrößen und der Anbindung von Backends. Wer noch konsequenter ist, wird die vielen Konfigurationsoptionen eines Tomcat vielleicht noch weiter reduzieren und sich deshalb gleich für eine Embedded-Tomcat-Lösung als Fat Jar, wie Spring Boot, Webapp Runner oder direkt für die Umsetzung auf der Basis des Tomcat-Embedded-API entscheiden (Abb. 4). Weniger Funktion pro Prozess und eine starke Reduktion der Projektabhängigkeiten tragen dann zusätzlich zum Erfolg des eigenen Projekts bei. Software muss einfacher, änderbarer und direkt einsetzbar werden. Die Verpackung als Docker-Image hilft entscheidend bei der Fertigstellung von Java-Anwendung mit Tomcat oder anderen Produkten. Wer es nun noch schafft, die Datenhaltung nicht mehr in riesigen gemeinsamen Lösungen vorzuhalten, kann die Vorteile von Microservices sicherlich produktiv für Wissenserwerb und neue Projekte nutzen.

<%@ page session="false" %>
<%
java.text.DateFormat dateFormat =
new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
%>
<html>
<body>
<h1>Docker Tomcat Status page</h1>
<ul>
<li>Hostname : <%= java.net.InetAddress.getLocalHost().getHostName() %></li>
<li>Tomcat Version : <%= application.getServerInfo() %></li>
<li>Now : <%= dateFormat.format(new java.util.Date()) %></li>
</ul>
</body>
</html>
$ mkdir -p status
$ vi status/index.jsp
...
$ CID=$(docker run -d —v 'pwd'/status:/opt/tomcat/webapps/status) infrabricks/ex-tomcat:8
$ IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${CID})
$ curl -s http://$IP:8080/status/index.jsp
<html>
<body>
<h1>Docker Tomcat Status page</h1>

<ul>
<li>Hostname : a222c4e3f231</li>
<li>Tomcat Version : Apache Tomcat/8.0.21</li>
<li>Now : 2015/04/14 10:23:32</li>
</ul>
</body>
</html>
Abb. 4: Docker mit einer Java-Fat-Jar-Anwendung

Abb. 4: Docker mit einer Java-Fat-Jar-Anwendung

Fazit

Das hier beschriebene einfache Docker-Tomcat-Beispiel ist sicherlich noch nicht produktionsreif. Das Tuning der Parameter und die Härtung der Installation fehlen noch. Eine bessere Apache-Tomcat-Docker-Distribution liegt im GitHub-Projekt infrabricks/tomcat8 bzw. direkt auf dem Docker-Hub infrabricks/tomcat:8 vor. Mit diesem Projekt kann der Start für die eigenen Web-Apps oder REST-Microservices gewagt werden. Ein Image für alles ist allerdings wenig hilfreich. In der Entwicklung ist die Flexibilität erforderlich, um schneller Varianten zu testen oder weitere Werkzeuge zur Fehlererkennung einzubinden. In der Kolumne der Java-Magazin-Ausgabe 5.2015 haben wir beschrieben, wie einfach und mächtig die Integration in der Entwicklung mit Maven und Docker ist. Für den produktiven Betrieb ist ein Docker-Image mit möglichst wenigen Freiheitsgraden die bessere Wahl und ein deutlicher Gewinn in Bezug auf die Sicherheit.

Geschrieben von
Peter Roßbach
Peter Roßbach
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.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
4000
  Subscribe  
Benachrichtige mich zu: