Using CircleCI to Build Spring Boot Microservices

Using CircleCI to Build Spring Boot Microservices

1 Comment

Introduction

I’m quickly becoming a fan of using CircleCI for CI builds. I’m finding that CircleCI is a very powerful platform. Recently, I configured CircleCI to build a Spring Boot Microservice. The microservice was generated by JHipster.

CircleCI is a online resource which uses Docker containers to run your CI builds. Since your build is running inside a Docker container, you can customize the container to support numerous different scenarios.

In this post, we’ll look at configuring CircleCI to build a Spring Boot Microservice generated by JHipster

Using CircleCI

CircleCI Account

CircleCI has a free tier which you can use for your CI builds. The free tier is limited to one running container at a time. Which is fine for many situations.

Signing up for CircleCI is crazy easy. All you need is a GitHub, BitBucket, or Google account.

Click here to get your free account.

Configuring CircleCI to Build JHipster Projects

JHipster Microservice

In this example, I’m using a Spring Boot microservice generated by JHipster.

My example application is a VERY basic example. I have not added any domains.

The focus of this post is on CI builds, not building microservices.

You can get the complete source code for this blog post here on GitHub.

CircleCI Build Config File

To build your project, CircleCI will look in the project root for the directory .circleci. The CircleCI build file is a YAML file named config.yml .

CircleCI has very powerful build capabilities. I cannot cove everything in this post. But, you can Click here to explore the capabilities found in CircleCI 2.0.

As Spring Framework Developers, it’s likely we will be using Maven or Gradle for our build tools. (I hope none of you are using Ant!)

Below are example build files for Maven and Gradle provided by CircleCI.

Maven CircleCI config.yml Example

# Java Maven CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-java/ for more details
#
version: 2
jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/openjdk:8-jdk
      
      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/repo

    environment:
      # Customize the JVM maximum heap limit
      MAVEN_OPTS: -Xmx3200m
    
    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "pom.xml" }}
          # fallback to using the latest cache if no exact match is found
          - v1-dependencies-

      - run: mvn dependency:go-offline

      - save_cache:
          paths:
            - ~/.m2
          key: v1-dependencies-{{ checksum "pom.xml" }}
        
      # run tests!
      - run: mvn integration-test

Gradle CircleCI config.yml Example

# Java Gradle CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-java/ for more details
#
version: 2
jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/openjdk:8-jdk
      
      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/repo

    environment:
      # Customize the JVM maximum heap limit
      JVM_OPTS: -Xmx3200m
      TERM: dumb
    
    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "build.gradle" }}
          # fallback to using the latest cache if no exact match is found
          - v1-dependencies-

      - run: gradle dependencies

      - save_cache:
          paths:
            - ~/.m2
          key: v1-dependencies-{{ checksum "build.gradle" }}
        
      # run tests!
      - run: gradle test

Installing NodeJS

If your JHipster project has a UI component, you will need to install NodeJS and Yarn for the build process.

Adding these commands to the ‘steps’ section of your CircleCI build configuration will install NodeJS into the docker container running your build.

      #TODO create custom Docker image with Node and Yarn Installed
      # Install Node for JH Build
      - run:
          name: Download Node
          command: curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash
      - run:
          name: Install Node
          command: sudo apt-get install -y nodejs
      - run:
          name: update-npm
          command: sudo npm install -g npm@latest

Installing Yarn

JHipster also uses Yarn for dependency management of UI components.

You can install Yarn by adding the following steps to your CircleCI build configuration.

      # Install Yarn
      - run:
          name: Download Yarn
          command: curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add && echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
      - run:
            name: Install Yarn
            command: sudo apt-get update && sudo apt-get install yarn

Custom Docker Images

CircleCI does provide a number of pre-built images you can use for your builds.

In this example, I’m using an image with Java pre-installed.

It does not have NodeJS or Yarn pre-installed.

Above, I’m showing you how to install NodeJS and Yarn into your build container.

If I needed to build a lot of JHipster projects, I probably would develop my own custom Docker image for the builds.

In my custom image, I would pre-install NodeJS and Yarn.

Comment below if you would like to see a future blog post on how to setup a custom Docker image like this!

Building A Docker Image with CircleCI

You can also use CircleCI to build docker images to hold your Spring Boot microservice.

Of course, JHipster out of the box gives us the tools to build the Docker image.

CircleCI gives us the ability to leverage a remote Docker service to support Docker commands from within our build container.

To build a Docker Image of our Spring Boot microservice, we need to add two steps to our build configuration.

  1. Setup the Remote Docker connection to our build container.
  2. Run the build command for Maven / Gradle to build the Docker Image.

Here is an example configuration for using Gradle to create the Docker Image:

      - setup_remote_docker

      - run:
          name: Build Docker Image
          command: ./gradlew bootRepackage -Pprod buildDocker

Complete CircleCI Build File

Here is the complete CircleCI Build file for my Spring Boot Microservice.

.circleci/config.yml

# Java Maven CircleCI 2.0 configuration file

# Check https://circleci.com/docs/2.0/language-java/ for more details
#
version: 2
jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/openjdk:8-jdk

      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/repo

    environment:
      # Customize the JVM maximum heap limit
      JVM_OPTS: -Xmx3200m
      TERM: dumb

    steps:
      - checkout

      #TODO create custom Docker image with Node and Yarn Installed
      # Install Node for JH Build
      - run:
          name: Download Node
          command: curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash
      - run:
          name: Install Node
          command: sudo apt-get install -y nodejs
      - run:
          name: update-npm
          command: sudo npm install -g npm@latest

      # Install Yarn
      - run:
          name: Download Yarn
          command: curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add && echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
      - run:
            name: Install Yarn
            command: sudo apt-get update && sudo apt-get install yarn

      # Download and cache dependencies
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "build.gradle" }}
          # Uncomment if your build has UI components. 
          #- node-dependency-cache-{{ checksum "node_modules" }}
          # fallback to using the latest cache if no exact match is found
          - v1-dependencies-

      - run: gradle dependencies

      # run tests and package
      - run: ./gradlew clean test

      - run:
          name: Save test results
          command: |
            mkdir -p ~/junit/
            find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/junit/ \;
          when: always

      - store_test_results:
          path: ~/junit

      - store_artifacts:
          path: ~/junit

      - setup_remote_docker

      - run:
          name: Build Docker Image
          command: ./gradlew bootRepackage -Pprod buildDocker

      - save_cache:
          paths:
            - ~/.m2
          key: v1-dependencies-{{ checksum "build.gradle" }}

     # Uncomment if your build has UI components. 
     # - save_cache:
     #     paths:
     #       - ~/repo/node_modules
     #     key: node-dependency-cache-{{ checksum "node_modules" }}

Memory Errors in CircleCI

In setting up some of my builds for JHipster, I ran into a intermittent build failures.

Here is the error I was seeing:

Process 'Gradle Test Executor 1' finished with non-zero exit value 137

The exit value of 137 indicates the Java process was getting terminated by the operating system. Effectively the JVM was consuming too much memory. Then Docker was killing the container.

Some builds would work, some would fail.

I worked on this issue several hours and learned a lot about Gradle and JVM memory management.

Gradle Daemon for CI Builds

For CI Builds, the Gradle team recommends disabling the Gradle daemon. You can do this as follows:

gradle.properites

## https://docs.gradle.org/current/userguide/gradle_daemon.html#sec:ways_to_disable_gradle_daemon
## un comment the below line to disable the daemon

org.gradle.daemon=false

JVM Memory Settings

You can also configure JVM memory settings via the Gradle properties file.

## Specifies the JVM arguments used for the daemon process.
## The setting is particularly useful for tweaking memory settings.
## Default value: -Xmx1024m -XX:MaxPermSize=256m
## un comment the below line to override the daemon defaults

org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

The above configuration did not help me.

Gradle seems to be launching another JVM process to execute tests in, and that JVM process does not seem to honor the memory arguments set in org.gradle.jvmargs or via environment variables.

However, what did work for me, was to configure the test task via build.gradle.

I added the following to the build configuration generated by JHipster:

build.gradle

test {
    include '**/*UnitTest*'
    include '**/*IntTest*'

    // uncomment if the tests reports are not generated
    // see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
    // ignoreFailures true
    reports.html.enabled = false

    // set heap size for the test JVM(s)
    minHeapSize = "128m"
    maxHeapSize = "512m"

    // set JVM arguments for the test JVM(s)
    jvmArgs '-XX:MaxPermSize=256m'
}

Note: MaxPermSize has been deprecated from Java 8 and above. See this link.

Once I limited the JVM memory consumption, my builds became stable.

The JVM was likely failing due to how Java works with Docker. The JVM ‘sees’ memory for the entire host system, and does not recognize the memory limitations of the Docker container. See this post for additional details.

This issue is going to get better in future releases of Java. It has been addressed in Java 9 and backported to Java 8.

 

About jt

    You May Also Like

    One comment

    1. March 15, 2018 at 3:33 pm

      I have used CircleCI for basic testing using SpringBoot.
      Something that I would like to see is an article about how you would do full integration testing with MicroServices.
      IE:
      1. Spin up the JHipster Registry
      2. Spin up MicroService1
      3. Spin up MicroService2
      Now do testing where the request goes to the SerivceGateway, then to MicroService1.
      Would be nice to spin up a MongDB container and pre-load a test pack with reference data.
      I have Google Pub/Sub message flow and REST comms between modules that I would like to test as well. Will show if there is configuration problems with the config files on Git (Using external Git based profile settings)

      Reply

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.