Search

Dark theme | Light theme
Showing posts with label Grails3. Show all posts
Showing posts with label Grails3. Show all posts

October 21, 2015

Grails Goodness: Run Grails Application As Docker Container

Docker is a platform to build and run distributed applications. We can use Docker to package our Grails application, including all dependencies, as a Docker image. We can then use that image to run the application on the Docker platform. This way the only dependency for running our Grails applications is the availability of a Docker engine. And with Grails 3 it is very easy to create a runnable JAR file and use that JAR file in a Docker image. Because Grails 3 now uses Gradle as build system we can even automate all the necessary steps by using the Gradle Docker plugin.

Let's see an example of how we can make our Grails application runnable as Docker container. As extra features we want to be able to specify the Grails environment as a Docker environment variable, so we can re-use the same Docker image for different Grails environments. Next we want to be able to pass extra command line arguments to the Grails application when we run the Docker container with our application. For example we can then specify configuration properties as docker run ... --dataSource.url=jdbc:h2:./mainDB. Finally we want to be able to specify an external configuration file with properties that need to be overridden, without changing the Docker image.

We start with a simple Grails application and make some changes to the grails-app/conf/application.yml configuration file and the grails-app/views/index.gsp, so we can test the support for changing configuration properties:

# File: grails-app/conf/application.yml
---
# Extra configuration property. 
# Value is shown on grails-app/views/index.gsp.
app:
    welcome:
        header: Grails sample application
...
...
<g:set var="config" value="${grailsApplication.flatConfig}"/>
<h1>${config['app.welcome.header']}</h1>

<p>
    This Grails application is running
    in Docker container <b>${config['app.dockerContainerName']}</b>.
</p>
...

Next we create a new Gradle build file gradle/docker.gradle. This contains all the tasks to package our Grails application as a runnable JAR file, create a Docker image with this JAR file and extra tasks to create and manage Docker containers for different Grails environment values.

// File: gradle/docker.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // Add Gradle Docker plugin.
        classpath 'com.bmuschko:gradle-docker-plugin:2.6.1'
    }
}


// Add Gradle Docker plugin.
// Use plugin type, because this script is used with apply from:
// in main Gradle build script.
apply plugin: com.bmuschko.gradle.docker.DockerRemoteApiPlugin


ext {
    // Define tag for Docker image. Include project version and name.
    dockerTag = "mrhaki/${project.name}:${project.version}".toLowerCase()

    // Base name for Docker container with Grails application.
    dockerContainerName = 'grails-sample'

    // Staging directory for create Docker image.
    dockerBuildDir = mkdir("${buildDir}/docker")

    // Group name for tasks related to Docker.
    dockerBuildGroup = 'Docker'
}


docker {
    // Set Docker host URL based on existence of environment
    // variable DOCKER_HOST.
    url = System.env.DOCKER_HOST ?
            System.env.DOCKER_HOST.replace("tcp", "https") :
            'unix:///var/run/docker.sock'
}


import com.bmuschko.gradle.docker.tasks.image.Dockerfile
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
import com.bmuschko.gradle.docker.tasks.image.DockerRemoveImage
import com.bmuschko.gradle.docker.tasks.container.DockerCreateContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStartContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStopContainer
import com.bmuschko.gradle.docker.tasks.container.DockerRemoveContainer

task dockerRepackage(type: BootRepackage, dependsOn: jar) {
    description = 'Repackage Grails application JAR to make it runnable.'
    group = dockerBuildGroup

    ext {
        // Extra task property with file name for the
        // repackaged JAR file.
        // We can reference this extra task property from
        // other tasks.
        dockerJar = file("${dockerBuildDir}/${jar.archiveName}")
    }

    outputFile = dockerJar
    withJarTask = jar
}

task prepareDocker(type: Copy, dependsOn: dockerRepackage) {
    description = 'Copy files from src/main/docker to Docker build dir.'
    group = dockerBuildGroup

    into dockerBuildDir
    from 'src/main/docker'
}

task createDockerfile(type: Dockerfile, dependsOn: prepareDocker) {
    description = 'Create Dockerfile to build image.'
    group = dockerBuildGroup

    destFile = file("${dockerBuildDir}/Dockerfile")

    // Contents of Dockerfile:
    from 'java:8'
    maintainer 'Hubert Klein Ikkink "mrhaki"'

    // Expose default port 8080 for Grails application.
    exposePort 8080

    // Create environment variable so we can customize the
    // grails.env Java system property via Docker's environment variable
    // support. We can re-use this image for different Grails environment
    // values with this construct.
    environmentVariable 'GRAILS_ENV', 'production'

    // Create a config directory and expose as volume.
    // External configuration files in this volume are automatically
    // picked up.
    runCommand 'mkdir -p /app/config'
    volume '/app/config'

    // Working directory is set, so next commands are executed
    // in the context of /app.
    workingDir '/app'

    // Copy JAR file from dockerRepackage task that was generated in
    // build/docker.
    copyFile dockerRepackage.dockerJar.name, 'application.jar'
    // Copy shell script for starting application.
    copyFile 'docker-entrypoint.sh', 'docker-entrypoint.sh'
    // Make shell script executable in container.
    runCommand 'chmod +x docker-entrypoint.sh'

    // Define ENTRYPOINT to execute shell script.
    // By using ENTRYPOINT we can add command line arguments
    // when we run the container based on this image.
    entryPoint './docker-entrypoint.sh'
}

task buildImage(type: DockerBuildImage, dependsOn: createDockerfile) {
    description = 'Create Docker image with Grails application.'
    group = dockerBuildGroup

    inputDir = file(dockerBuildDir)
    tag = dockerTag
}

task removeImage(type: DockerRemoveImage) {
    description = 'Remove Docker image with Grails application.'
    group = dockerBuildGroup

    targetImageId { dockerTag }
}

//------------------------------------------------------------------------------
// Extra tasks to create, run, stop and remove containers
// for a development and production environment.
//------------------------------------------------------------------------------
['development', 'production'].each { environment ->

    // Transform environment for use in task names.
    final String taskName = environment.capitalize()

    // Name for container contains the environment name.
    final String name = "${dockerContainerName}-${environment}"

    task "createContainer$taskName"(type: DockerCreateContainer) {
        description = "Create Docker container $name with grails.env $environment."
        group = dockerBuildGroup

        targetImageId { dockerTag }
        containerName = name

        // Expose port 8080 from container to outside as port 8080.
        portBindings = ['8080:8080']

        // Set environment variable GRAILS_ENV to environment value.
        // The docker-entrypoint.sh script picks up this environment
        // variable and turns it into Java system property
        // -Dgrails.env.
        env = ["GRAILS_ENV=$environment"]

        // Example of adding extra command line arguments to the
        // java -jar app.jar that is executed in the container.
        cmd = ["--app.dockerContainerName=${containerName}"]

        // The image has a volume /app/config for external configuration
        // files that are automatically picked up by the Grails application.
        // In this example we use a local directory with configuration files
        // on our host and bind it to the volume in the container.
        binds = [
            (file("$projectDir/src/main/config/${environment}").absolutePath):
            '/app/config']
    }


    task "startContainer$taskName"(type: DockerStartContainer) {
        description = "Start Docker container $name."
        group = dockerBuildGroup

        targetContainerId { name }
    }

    task "stopContainer$taskName"(type: DockerStopContainer) {
        description = "Stop Docker container $name."
        group = dockerBuildGroup

        targetContainerId { name }
    }

    task "removeContainer$taskName"(type: DockerRemoveContainer) {
        description = "Remove Docker container $name."
        group = dockerBuildGroup

        targetContainerId { name }
    }

}

We also must add a supporting shell script file to the directory src/main/docker with the name docker-entrypoint.sh. This script file makes it possible to specify a different Grails environment variable with environment variable GRAILS_ENV. The value is transformed to a Java system property -Dgrails.env={value} when the Grails application starts. Also extra commands used to start the Docker container are appended to the command line:

#!/bin/bash
set -e

exec java -Dgrails.env=$GRAILS_ENV -jar application.jar $@

Now we only have to add an apply from: 'gradle/docker.gradle' at the end of the Gradle build.gradle file:

// File: build.gradle
...
apply from: 'gradle/docker.gradle'

When we invoke the Gradle tasks command we see all our new tasks. We must at least use Gradle 2.5, because the Gradle Docker plugin requires this (see also this issue):

...
Docker tasks
------------
buildImage - Create Docker image with Grails application.
createContainerDevelopment - Create Docker container grails-sample-development with grails.env development.
createContainerProduction - Create Docker container grails-sample-production with grails.env production.
createDockerfile - Create Dockerfile to build image.
dockerRepackage - Repackage Grails application JAR to make it runnable.
prepareDocker - Copy files from src/main/docker to Docker build dir.
removeContainerDevelopment - Remove Docker container grails-sample-development.
removeContainerProduction - Remove Docker container grails-sample-production.
removeImage - Remove Docker image with Grails application.
startContainerDevelopment - Start Docker container grails-sample-development.
startContainerProduction - Start Docker container grails-sample-production.
stopContainerDevelopment - Stop Docker container grails-sample-development.
stopContainerProduction - Stop Docker container grails-sample-production.
...

Now we are ready to create a Docker image with our Grails application code:

$ gradle buildImage 
...
:compileGroovyPages
:jar
:dockerRepackage
:prepareDocker
:createDockerfile
:buildImage
Building image using context '/Users/mrhaki/Projects/grails-docker-sample/build/docker'.
Using tag 'mrhaki/grails-docker-sample:1.0' for image.
Created image with ID 'c1d0a600c933'.

BUILD SUCCESSFUL

Total time: 48.68 secs

We can check with docker images if our image is created:

$ docker images
REPOSITORY                               TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
mrhaki/grails-docker-sample              1.0                 c1d0a600c933        4 minutes ago       879.5 MB

We can choose to create and run new containers based on this image with the Docker run command. But we can also use the Gradle tasks createContainerDevelopment and createContainerProduction. These tasks will create containers that have predefined values for the Grails environment, a command line argument --app.dockerContainerName and directory binding on our local computer to the container volume /app/config. The local directory is src/main/config/development or src/main/config/production in our project directory. Any files placed in those directories will be available in our Docker container and are picked up by the Grails application. Let's add a new configuration file for each environment to override the configuration property app.welcome.header:

# File: src/main/config/development/application.properties
app.welcome.header=Dockerized Development Grails Application!
# File: src/main/config/production/application.properties
app.welcome.header=Dockerized Production Grails Application!

Now we create two Docker containers:

$ gradle createContainerDevelopment createContainerProduction
:createContainerDevelopment
Created container with ID 'feb56c32e3e9aa514a208b6ee15562f883ddfc615292d5ea44c38f28b08fda72'.
:createContainerProduction
Created container with ID 'd3066d14b23e23374fa7ea395e14a800a38032a365787e3aaf4ba546979c829d'.

BUILD SUCCESSFUL

Total time: 2.473 secs
$ docker ps -a
CONTAINER ID        IMAGE                             COMMAND                  CREATED             STATUS                     PORTS               NAMES
d3066d14b23e        mrhaki/grails-docker-sample:1.0   "./docker-entrypoint."   7 seconds ago       Created                                        grails-sample-production
feb56c32e3e9        mrhaki/grails-docker-sample:1.0   "./docker-entrypoint."   8 seconds ago       Created                                        grails-sample-development

First we start the container with the development configuration:

$ gradle startContainerDevelopment
:startContainerDevelopment
Starting container with ID 'grails-sample-development'.

BUILD SUCCESSFUL

Total time: 1.814 secs

In our web browser we open the index page of our Grails application and see how the current Grails environment is development and that the app.welcome.header configuration property is used from our application.properties file. We also see that the app.dockerContainerName configuration property set as command line argument for the Docker container is picked up by Grails and shown on the page:

If we stop this container and start the production container we see different values:

The code is also available as Grails sample project on Github.

Written with Grails 3.0.9.

April 15, 2015

Grails Goodness: Add Banner to Grails Application

Grails 3 is based on Spring Boot. This means we get a lot of the functionality of Spring Boot into our Grails applications. A Spring Boot application has by default a banner that is shown when the application starts. The default Grails application overrides Spring Boot's behavior and disables the display of a banner. To add a banner again to our Grails application we have different options.

First we can add a file banner.txt to our classpath. If Grails finds the file it will display the contents when we start the application. Let's add a simple banner with Grails3 in Ascii art in the file src/main/resources/banner.txt. By placing the file in src/main/resources we can assure it is in the classpath as classpath:/banner.txt:

________             .__.__         ________
 /  _____/___________  |__|  |   _____\_____  \
/   \  __\_  __ \__  \ |  |  |  /  ___/ _(__  <
\    \_\  \  | \// __ \|  |  |__\___ \ /       \
 \______  /__|  (____  /__|____/____  >______  /
        \/           \/             \/       \/

Let's run our application with the bootRun task:

$ gradle bootRun
:compileJava UP-TO-DATE
:compileGroovy UP-TO-DATE
:processResources
:classes
:findMainClass
:bootRun

  ________             .__.__         ________
 /  _____/___________  |__|  |   _____\_____  \
/   \  __\_  __ \__  \ |  |  |  /  ___/ _(__  <
\    \_\  \  | \// __ \|  |  |__\___ \ /       \
 \______  /__|  (____  /__|____/____  >______  /
        \/           \/             \/       \/

Grails application running at http://localhost:8080
...

To have more information in the banner we can implement the org.springframework.boot.Banner interface. This interface has a printBanner method in which we can write the implementation for the banner. To use it we must create an instance of the GrailsApp class and set the banner property:

// File: grails-app/init/banner/Application.groovy
package banner

import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import grails.util.Environment
import org.springframework.boot.Banner

import static grails.util.Metadata.current as metaInfo

class Application extends GrailsAutoConfiguration {
    static void main(String[] args) {
        final GrailsApp app = new GrailsApp(Application)
        app.banner = new GrailsBanner()
        app.run(args)
    }
}

/**
 * Class that implements Spring Boot Banner
 * interface to show information on application startup.
 */
class GrailsBanner implements Banner {

    private static final String BANNER = '''
  ________             .__.__         ________
 /  _____/___________  |__|  |   _____\\_____  \\
/   \\  __\\_  __ \\__  \\ |  |  |  /  ___/ _(__  <
\\    \\_\\  \\  | \\// __ \\|  |  |__\\___ \\ /       \\
 \\______  /__|  (____  /__|____/____  >______  /
        \\/           \\/             \\/       \\/'''

    @Override
    void printBanner(
            org.springframework.core.env.Environment environment,
            Class<?> sourceClass,
            PrintStream out) {

        out.println BANNER

        row 'App version', metaInfo.getApplicationVersion(), out
        row 'App name', metaInfo.getApplicationName(), out
        row 'Grails version', metaInfo.getGrailsVersion(), out
        row 'Groovy version', GroovySystem.version, out
        row 'JVM version', System.getProperty('java.version'), out
        row 'Reloading active', Environment.reloadingAgentEnabled, out
        row 'Environment', Environment.current.name, out

        out.println()
    }

    private void row(final String description, final value, final PrintStream out) {
        out.print ':: '
        out.print description.padRight(16)
        out.print ' :: '
        out.println value
    }

}

Now we run the bootRun task again:

$ gradle bootRun
:compileJava UP-TO-DATE
:compileGroovy
:processResources
:classes
:findMainClass
:bootRun

  ________             .__.__         ________
 /  _____/___________  |__|  |   _____\_____  \
/   \  __\_  __ \__  \ |  |  |  /  ___/ _(__  <
\    \_\  \  | \// __ \|  |  |__\___ \ /       \
 \______  /__|  (____  /__|____/____  >______  /
        \/           \/             \/       \/
:: App version      :: 0.1
:: App name         :: grails-banner-sample
:: Grails version   :: 3.0.1
:: Groovy version   :: 2.4.3
:: JVM version      :: 1.8.0_45
:: Reloading active :: true
:: Environment      :: development

Grails application running at http://localhost:8080
...

Written with Grails 3.0.1.

Ascii art is generated with this website.