Suche
Kolumne: Docker rockt Java

Docker mit Maven steuern

Peter Roßbach, Roland Huß
docker-fuer-java

© Software & Support Media

Docker bietet eine alternative Möglichkeit, Java-Applikationen zu bauen und in Produktion zu setzen. Bisher wurden Java-Enterprise-WARs oder -EARs in vorinstallierte Java-EE-Server deployt. Jetzt können die Anwendungen direkt komplett, inklusive des Servers, in Docker-Containern ausgeliefert werden. Der Ausführungskontext wird gleich mit verpackt, und lästige Anpassungen entfallen. Mit dieser Methode können die Container überall laufen und schon in der Entwicklung für den Betrieb zusammengestellt werden.

Dieser Artikel zeigt, wie ein Docker-Maven-Plug-in elegant helfen kann, das Zusammenstellen und Steuern der Container zu erleichtern. Dabei stehen zwei Aspekte im Vordergrund: die Erstellung von Docker-Containern aus dem Build-Prozess heraus und die Realisierung autarker Integrationstests. Anhand eines einfachen Service, der HTTP-GET-Anfragen in eine PostgreSQL-Datenbank protokolliert, wird gezeigt, wie sich Docker mithilfe eines Maven-Plug-ins in den Java Build-Prozess einfach integrieren lässt.

Das Beispiel nutzt zwei Docker-Images: ein Standard-PostgreSQL-Image von Docker Hub und eines, das den eigentlichen Service enthält. Die Aufgabe der Plug-ins ist es, das Serviceimage zu bauen und vor dem Integrationstest sowohl einen PostgreSQL-Container als auch einen Container mit der Anwendung zu starten, wobei der DB-Container in den Servicecontainer gelinkt wird. Der Service erzeugt dann beim Start automatisch das Schema, falls noch nicht vorhanden, via Flyway.

Obwohl dieses Set-up sehr einfach ist, sind ein paar Dinge zu beachten: Der Container mit dem HTTP-Service greift bei der Initialisierung bereits auf die Datenbank zu, sodass der PostgreSQL-Container zu diesem Zeitpunkt bereits vollständig laufen muss. Der Integrationstest darf auch erst loslaufen, wenn alle Container gestartet und initialisiert sind.

Natürlich muss ein Docker-Daemon laufen, der auf einem Port horcht, da das Docker-Maven-Plug-in seine HTTP-Schnittstelle nutzt. Der Docker-Daemon kann in einer Cloud oder der lokalen virtuellen Maschine, beispielsweise durch das Projekt Boot2Docker, bereitgestellt sein. Auf dem Entwicklungsrechner muss kein Docker-CLI installiert sein. Das Docker-Maven-Plug-in spricht direkt via HTTP mit dem Daemon. Auf jeden Fall müssen die Docker-Umgebungsvariablen entsprechend gesetzt sein, z. B.:

export DOCKER_HOST=tcp://192.168.59.103:2376
export DOCKER_CERT_PATH=~/.docker
export DOCKER_TLS_VERIFY=1

Alternativ können die Verbindungsparameter auch in der Plug-in-Konfiguration direkt angegeben werden.

Ein einfacher HTTP-Logging-Service

Der Logging-Service wird als Anwendung mit einem Embedded Apache-Tomcat zusammen realisiert. Der Service selbst greift auf eine PostgreSQL-Datenbank zu, um seine HTTP-Aufrufe mitzuprotokollieren. Als Ausgabe liefert er direkt die Liste aller Aufrufe als Text aus. Dieser etwas sinnbefreite Service ist von der Fachlichkeit bewusst einfach gehalten, da das Hauptaugenmerk in diesem Beitrag auf der Docker-Maven-Integration liegt, die wir uns im Anschluss ansehen wollen. Zu einem „richtigen“ Microservice fehlt ihm noch die korrekte REST-Semantik.

Listing 1 zeigt die wichtigsten Methoden der einzigen Klasse LogService. Dieser Service hat zum einen eine statische main()-Methode, die einen Embedded Tomcat startet. Zusätzlich ist LogService selbst ein Servlet, das im Tomcat in der Methode setupAndStartTomcat() registriert wird. Der Konstruktor erzeugt zunächst den Datenbankverbindungs-URL mithilfe der von Docker gesetzten Umgebungsvariablen DB_PORT_5432_TCP_PORT, um dann via Flyway ein Datenbankschema anzulegen. Flyway führt dabei alle notwendigen SQL-Skripte aus, die im Classpath unter db/migration liegen. Im Beispiel wird einfach eine Tabelle LOGGING mit drei Spalten (DATE, IP, URL) angelegt. Der komplette Beispielcode befindet sich auf GitHub.

Listing 1: LogService

public class LogService extends HttpServlet {

  private final String connectionUrl;

  public LogService() throws SQLException {
    // Prepare JDBC Url from Docker provided environment variable
    connectionUrl = 
         "jdbc:postgresql://db:" + 
         System.getenv("DB_PORT_5432_TCP_PORT") + 
         "/postgres";

    // Create DB schema (as defined in resources/db/migration/)
    Flyway flyway = new Flyway();
    flyway.setDataSource(connectionUrl, "postgres", null);
    flyway.migrate();
  }


  // Log into DB and print out all logs.
  protected void doGet(HttpServletRequest  req, 
                       HttpServletResponse resp) 
        throws ServletException, IOException {
    try (Connection connection = DriverManager.getConnection(connectionUrl,"postgres",null)) {
        // Insert current request in DB ...
        insertLog(req, connection);

        // ... and then return all logs stored so far
        resp.setContentType("text/plain");
        printOutLogs(connection, resp.getWriter());
    } catch (SQLException e) {
        throw new ServletException("Cannot update DB: " + e,e);
    }
  }  


  public static void main(String[] args) 
         throws LifecycleException, SQLException {
    // Start embedded tomcat with a LogService servlet and wait forever
    setupAndStartTomcat(new LogService());
  }

....
}

Ab in den Container!

Dieser Service soll nun in ein eigenes Docker-Image gepackt werden. Als Image bezeichnet man ein transportables Rootfile-System, das in verschiedene Layer aufgeteilt ist. Normalerweise wird zur Produktion eines Images ein Dockerfile verwendet. Dies berücksichtigt aber nicht den Flow zum Erzeugung und Testen der Anwendung. Automatisiert lässt sich dieser Build-Flow für Java-Anwendungen beispielsweise aus einem Maven-Build heraus umsetzen, wobei man am besten ein docker-maven-plugin dazu nutzt. Es stehen mehrere Alternativen zur Verfügung (siehe Kasten „Der Docker-Maven-Plug-in-Dschungel“). Im Beispiel wird das aktuell beliebteste (nach Anzahl der GitHub-Stars) Plug-in rhuss/docker-maven-plugin verwendet. Die zu unserem Beispiel passende Plug-in-Konfiguration zeigt Listing 2. Generell ist die Konfiguration nach Images (<image> … </image>) aufgeteilt.

Der Docker-Maven-Plug-in-Dschungel

Ein Wald von Maven-Docker-Plug-ins gedeiht auf GitHub. Aktuell gibt es sage und schreibe elf Projekte, die sich um die verschiedensten Aspekte der Docker-Integration in einen Maven-Build kümmern. Zum Glück ist die Auswahl dann doch nicht ganz so schwer: Aktuell sind nur vier Plug-ins noch aktiv bzw. haben überhaupt einen Mehrwert gegenüber dem direkten Aufruf dem Docker-CLI von Maven aus (mit dem maven-exec-plugin). Ein Featurevergleich der vier Plug-ins findet sich hier. Dazu gibt es auch ein „Shootout“-Projekt auf GitHub, das die Verwendung der vier Plug-ins demonstriert.

 

Listing 2

<plugin>
  <groupId>org.jolokia</groupId>
  <artifactId>docker-maven-plugin</artifactId>
  <version>0.11.1~</version>
  <configuration>
    <images>
      <!-- HTTP Service image holding the FAT-Jar with embedded tomcat -->
      <image>
       <alias>service</alias>
       <name>jolokia/${project.artifactId}-rhuss:${project.version}</name>
       <build>
         <from>java:8u40</from>
         <assembly>
            <descriptor>assembly.xml</descriptor>
         </assembly>
         <ports>
           <port>8080</port>
         </ports>
         <command>java -Djava.security.egd=file:/dev/./urandom
 -jar /maven/docker-sample.jar</command>
       </build>

       <!-- Running the container -->
       <run>
         <ports>
           <port>service.port:8080</port>
         </ports>
         <links>
           <link>db</link>
         </links>
       </run>
      </image>


      <!-- PostgresSQL Image -->
      <image>
        <alias>db</alias>
        <name>postgres:9</name>
        <run>
          <wait>
            <log>database system is ready to accept connections</log>
         </wait>
        </run>
      </image>
    </images>
  </configuration>
</plugin>
Abb. 1: Docker Maven-Plug-in-Setup

Abb. 1: Docker Maven-Plug-in-Setup

Das erste Image des Logging-Services, mit dem Alias service und dem Namen jolokia/docker-maven-sample:0.0.1, basiert auf dem Standard-Java-Trusted -Image java:8u40. Mithilfe eines Assembly-Deskriptors, wie man ihn vom maven-assembly-plugin kennt, wird die JAR-Datei mit dem Microservice und all seinen Abhängigkeiten spezifiziert. Dieses „fat“ JAR wird von dem maven-shade-plugin während der Package-Build-Phase erzeugt. Des Weiteren ist der exportierte Port 8080 angegeben und auch das Kommando, mit dem der Service gestartet wird. Interessant ist auch der <run>-Abschnitt, der für den Integrationstests genutzt wird. Dort ist das Port Mapping des Container-Ports auf die Maven Property service.port spezifiziert. In diesem Fall wird der von Docker dynamisch gewählte Port der Variablen ${service.port} zugewiesen, die z. B. in den Integrationstest referenziert werden kann, um den URL zum Zugriff auf den Service zu konstruieren. Wichtig für den Service ist auch die Verbindung zur Datenbank, die über die <links>-Konfiguration eingerichtet wird. Dort wird das Image mit dem Alias db aus der Plug-in-Konfiguration referenziert. Der LogService kann dann nach Docker-Art über Umgebungsvariablen mit dem Präfix DB_ auf die exportierten Ports dieses Images zugreifen.

Das zweite Image mit dem Alias db ist das Datenbankimage postgres:9. Es hat keine <build>-Konfiguration, da das Image direkt ohne Anpassung genutzt wird.

Interessant ist hier noch der <wait>-Abschnitt, der bewirkt, dass nach dem Starten des Containers Maven so lange wartet, bis der angegebenen „Substring“ auf Standard-Output erscheint.

Maven Goals

mvn package docker:build baut das Serviceimage.

mvn docker:start startet alle Images in der richtigen Reihenfolge. In unserem Beispiel wird das Image db gestartet und im Anschluss das service-Image, das mit dem Datenbankcontainer verknüpft wird.

mvn docker:stop stoppt alle laufenden Container, die in pom.xml konfiguriert sind.

mvn docker:push lädt die gebauten Images zu einer Registry hoch.

Diese und viele weitere Möglichkeiten des Docker-Maven-Plug-ins sind ausführlich im Benutzerhandbuch beschrieben.

Integrationstest

Mit dem Plug-in lassen sich elegante Integrationstests umsetzen. Dazu wird das maven-failsafe-plugin genutzt, das den in Listing 3 gezeigten Test startet. Dieser Test verwendet REST Assured, um den Microservice via HTTP anzusprechen. Den URL erhält der Test über die System-Property log.url, die in der Konfiguration des maven-failsafe-plugins gesetzt wird: <log.url>http://${dockerHost}:${service.port}/jolokia</log.url>. Die Property ${service.port} enthält ja den von Docker dynamisch zugewiesenen Host-Port des Services. ${dockerHost} ist die IP-Adresse des Docker-Hosts. Bei dem Integrationstest werden die beiden Docker-Container erzeugt und gestartet, der Test durchgeführt und schließlich die Container gestoppt. Da der Port dynamisch zugewiesen wird, besteht keine Gefahr eines Konflikts. Damit eignet sich diese Art Test sehr gut auch für gut gefüllte CI-Server.

Listing 3

@Test
public void testLog() {
    long nonce = (int) (Math.random() * 10000);

    RestAssured.baseURI = System.getProperty("log.url");
    RestAssured.defaultParser = Parser.TEXT;

    given()
            .param("mimeType", "text/plain")
            .get("/" + nonce)
    .then().assertThat()
            .header("content-type", containsString("text/plain"))
            .body(containsString(nonce + ""));
}

Fazit

Anhand dieses abgespeckten Beispiels eines Microservice wird deutlich, wie einfach es ist, Docker in den Java-Build-Prozess zu integrieren. Das Bauen von Docker-Images, die durch die Continous-Delivery-Pipeline geschickt werden können, lässt sich mit einem docker-maven-plugin ebenso leicht und flexibel konfigurieren wie Integrationstests mit mehreren Containern. Dabei haben wir hier nur einen kleinen Teil der Möglichkeiten von rhuss/docker-maven-plugin angerissen. Die vollständige Dokumentation erklärt alle Details der Konfiguration und weitere Anwendungsmöglichkeiten. Zudem ist die Unterstützung von Fig bzw. Docker Compose geplant.

Weitere und tiefergehende Informationen zu Microservices in Docker-Containern kann man auf Peters Blog nachlesen.

DevOpsCon Whitepaper 2018

Free: BRAND NEW DevOps Whitepaper 2018

Learn about Containers,Continuous Delivery, DevOps Culture, Cloud Platforms & Security with articles by experts like Michiel Rook, Christoph Engelbert, Scott Sanders and many more.

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.
Roland Huß

Roland Huß ist ein Software Engineer, der sich für Ops-Themen im Allgemeinen und für Docker im Besonderen begeistert. Er arbeitet bei der ConSol* Software GmbH und ist Initiator der Open-Source-Projekte Jolokia (http://www.jolokia.org) und rhuss/docker-maven-plugin. Zudem bloggt er von Zeit zu Zeit auf https://ro14nd.de.

Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: