Monday, November 26, 2018

Automating Docker Deployments Using Jib and Payara Micro

If you are like me, you spend most of your day developing applications.  During that time, it is difficult to carve out the time to manage containers.  Writing a Dockerfile has almost become part of the normal development process nowadays, but there is a different way.  I'd rather spend as much time developing my applications as possible, and leave the Docker configuration as a minimal deployment procedure that is automatically completed for me.

I believe that the folks at Google were thinking in this way when they produced Jib.  The Jib project allows one to automate the creation of a Docker image so that the container can simply be started up after building the project.  There is no need to create a Dockerfile, as Jib takes care of this for you.  Jib works with both Maven and Gradle, and in this post I will demonstrate how to use it with Maven and deploy to Payara Micro.  By default, Jib includes a Jetty distribution to which an application will deployed inside of the container.  However, I am familiar with Payara Micro and quite enjoy the Payara Micro environment, so I'll bypass the use of Jetty and deploy to Payara Micro in this example.

To begin, it is important to get familiar with the Jib Maven process by reading through the documentation.

In this example I am utilizing the SportsTeamQueryService microservice that I produced for a Java Magazine article in 2018, but simply modifying the POM file to incorporate the Jib build.  I will not go into full detail on Jib in this post, as the documentation itself does quite well.  Rather, I will explain how I was able to get this project to build and deploy to Docker on my Mac.  If using a different OS, you may have to configure host-container networking in order for the container to communicate with the host database.

To begin, the POM file looks like the following, including only those dependencies that are required for the project.  I call your attention to the <plugins> section for the Jib hooks:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.javamagazine</groupId>
    <artifactId>SportsTeamQueryService</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>

    <name>SportsTeamQueryService</name>

    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>javax.persistence-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.jpa</artifactId>
            <version>2.5.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>javax.ws.rs-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.core</artifactId>
            <version>2.5.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.ejb</groupId>
            <artifactId>javax.ejb-api</artifactId>
            <version>3.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.asm</artifactId>
            <version>2.5.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.antlr</artifactId>
            <version>2.5.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.jpa.jpql</artifactId>
            <version>2.5.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>javax.persistence</artifactId>
            <version>2.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
            <version>2.5.2</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <!-- Docker JIB Dependency-->
            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>0.10.0</version>
                <configuration>
                    <from>
                        <image>
                            payara/micro:latest
                        </image>
                    </from>
                    <to>
                        <image>sportsteamqueryservice</image>
                    </to>
                    <container>
                        <appRoot>/opt/payara/SportsTeamQueryService</appRoot>
                        <args>
                            <arg>
                                --addlibs
                            </arg>
                            <arg>
                                /jars/derbyclient.jar
                            </arg>
                            <arg>
                                --deploy
                            </arg>
                            <arg>
                                /opt/payara/SportsTeamQueryService
                            </arg>
                        </args>
                        <environment>                            
                            <DOCKER_HOST>docker.for.mac.localhost</DOCKER_HOST>
                            <DB_NAME>ACME</DB_NAME>
                            <DB_USER>acmeuser</DB_USER>
                            <DB_PASSWORD>password</DB_PASSWORD>
                        </environment>
                    </container>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
</project>


In the POM, I added the Jib workflow within the <plugins> section.  In this project I utilized the latest Jib dependency at the time of writing, using the following maven coordinates:

<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>0.10.0</version>

The <configuration> portion of the plugin specifies custom configuration for my project.  If I leave the <configuration> section off the declaration, then the WAR file generated by the project will be deployed to a Docker image containing a Jetty server.  The WAR file will be available at the Jetty ROOT.  Instead, since I am interested in using Payara Micro, I customized the Jib declaration as follows:

1)  Inherit FROM the latest hosted Payara Micro image using the <from><image> tags.

2)  Next, the <to><image> tags declare the name of the resulting Docker image.

3)  Add a <container> section to specify details about the container.
    a)  First, I added an <appRoot> tag to specify that the WAR file will be loaded into the "/opt/payara/" location, within the "SportsTeamQueryService" directory.

    b)  Next, I added a number of arguments by specifying <args><arg></arg></args>.

The first argument adds the Apache Derby Jar file to the deployment.  One note is that in the project itself, there is a "src/main/jib/jars" folder.  You must add the derbyclient.jar file inside of this "jib/jars" folder so that it is copied into the image.  By default, Jib adds anything within the "src/main/jib" folder into the project image.

The second argument specifies the Payara Micro "--deploy" argument and points to the "/opt/payara/SportsTeamQueryService" directory...which is the exploded WAR for the project.

    c)  Lastly, add the <environment> section to specify any environment variables that will be passed into the image.  In this case, I added database-specific environment variables.  If you take a look at the glassfish-resources.xml file within the project, you can see that these environment variables are referenced to configure the datasource.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
    <jdbc-connection-pool ..abbreviated for brevity...>
        <property name="serverName" value="${ENV=DOCKER_HOST}"/>
        <property name="portNumber" value="1527"/>
        <property name="databaseName" value="${ENV=DB_NAME}"/>
        <property name="User" value="${ENV=DB_USER}"/>
        <property name="Password" value="${ENV=DB_PASSWORD}"/>
        <property name="URL" value="jdbc:derby://${ENV=DOCKER_HOST}:1527/acme"/>
        <property name="driverClass" value="org.apache.derby.jdbc.ClientDriver"/>
    </jdbc-connection-pool>
    <jdbc-resource enabled="true" jndi-name="java:app/jdbc/acme" object-type="user" pool-name="derby_net_acme_acmeuserPool"/>
</resources>

Note that you may need to modify the servername environment variable if not using Docker for Mac.

That is it for configuring Jib!  Now all that is required is simply to build the project and deploy the image to your local Docker repository.  In this case, I open the terminal and traverse inside of the SportsTeamQueryService-Jib project directory and issue the following Maven build:

mvn clean install

After the project has been built, invoke the Jib build by issuing the following:

mvn compile jib:dockerBuild

The Docker image should now be ready to start within the local Docker environment.  To start the container, issue the following:

docker run -d -p 8080:8080 sportsteamqueryservice

The container should now be up and running.  Visit the URL http://localhost:8080/SportsTeamQueryService/rest/teamrosterqueryservice/findAll to see the list of players. 

In order to rebuild the Docker image, simply issue the command:

mvn compile jib:dockerBuild

Project Sources:  https://github.com/juneau001/SportsTeamQueryService-Jib