Using CircleCI to Build Spring Boot Microservices
1 CommentLast Updated on October 21, 2024 by jt
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.
- Setup the Remote Docker connection to our build container.
- 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.
Andre Kapp
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)