CI/CD for SpringBoot applications using Travis-CI
In this article, we are going to learn how we can use Travis CI for Continuous Integration and Continuous Deployment (CI/CD) of a Spring Boot application. We will learn how to run Maven build goals, perform test coverage validation using the JaCoCo plugin, run Code Quality checks using SonarCloud, build a Docker image and push it to DockerHub, and finally deploy it to Heroku.
The source code for this article is at https://github.com/sivaprasadreddy/jblogger
Last week, I was talking to my friend about how easy it has become to build and deploy a Java application using Spring Boot. During the discussion, one point came up about how much it costs to build a Java application and deploy it somewhere (in the cloud). So, I thought of exploring more about the free services that we can use to automate all project development activities with minimal or no cost at all.
A few years ago, I used CloudBees and CloudFoundry to build and deploy my pet projects, which were offering free hosting services, but they are not providing free services anymore.
In the past, I have used Travis CI for my Java projects just for preliminary testing purposes, but looking at their documentation, I realized they provide a lot more features.
So I thought of checking if I can use Travis CI for my projects to do all the usual tasks, such as:
- Checkout the latest code
- Compile and run Unit and Integration Tests
- Run JaCoCo code coverage and fail the build if the desired percentage is not met
- Run SonarQube code quality checks
- Optionally, build a Docker image and publish it to Docker Hub
- Deploy the application on some free cloud hosting service like Heroku or OpenShift
After going through their documentation, I realized that we can do all these tasks by using some of the free online services and Travis CI’s integration with them.
- GitHub for the code repository
- SonarCloud for a free SonarQube service
- Docker Hub for publishing Docker images
- Heroku for deploying the application
Let us see how we can do all the above-mentioned tasks using Travis CI for a Spring Boot project.
Step 1: Create a Spring Boot project
Create a Spring Boot project either using http://start.spring.io or from your IDE. I am using the Maven build tool; you can use Gradle also if you prefer. Now commit the project into your GitHub repository.
Step 2: Create a .travis.yml file
In order to enable Travis CI integration, we need to create a .travis.yml file in the project’s root folder.
As we are creating a Maven-based Java project, create a .travis.yml file with the following content:
.travis.yml
language: java
jdk: oraclejdk8
This minimal configuration is sufficient for Travis CI to recognize our Maven-based Java project and build it.
If there is a build.gradle file in our project’s root folder, Travis will treat it as a Gradle project, or if there is a pom.xml, it will treat it as a Maven project. If both build.gradle and pom.xml are there, then the Gradle build script will take priority.
By default, Travis will run mvn test -B for building the project.
If Travis finds the mvnw wrapper, then it will be used like ./mvnw test -B.
But if you want to run a different command or want to run multiple commands, we can use a script block to customize it.
Now commit and push the .travis.yml file to GitHub.
Step 3: Enable Travis CI for the GitHub repository
Go to https://travis-ci.org/ and Sign in with GitHub.
Now click on Add New Repository (+ symbol).
Enable Travis for the repository. After enabling Travis, click on that repository, and you can trigger a build by selecting More Options -> Trigger build.
Now you can see that the build is running, tests are being executed, and an email notification will be sent to your email regarding the build status.
Step 4: Add JaCoCo Code Coverage check
Add the Maven JaCoCo plugin to pom.xml with options like what is the desired code coverage percentage, packages/classes to ignore, etc.
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.9</version>
<configuration>
<excludes>
<exclude>com/sivalabs/jblogger/entities/*</exclude>
<exclude>com/sivalabs/jblogger/*Application</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-prepare-agent-integration</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
</execution>
<execution>
<id>default-report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>default-report-integration</id>
<goals>
<goal>report-integration</goal>
</goals>
</execution>
<execution>
<id>default-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<!-- implementation is needed only for Maven 2 -->
<rule implementation="org.jacoco.maven.RuleConfiguration">
<element>BUNDLE</element>
<limits>
<!-- implementation is needed only for Maven 2 -->
<limit implementation="org.jacoco.report.check.Limit">
<counter>COMPLEXITY</counter>
<value>COVEREDRATIO</value>
<minimum>0.60</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
Step 5: Run Unit and Integration Tests
As I mentioned earlier, by default, Travis runs mvn test -B, which will only run Unit tests.
We want to run Unit tests and Integration tests separately by using the maven-failsafe-plugin. We will follow the convention by naming Unit tests as *Test.java/*Tests.java and Integration tests as *IT.java.
Add the maven-failsafe-plugin as mentioned below:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<includes>
<include>**/*IT.java</include>
</includes>
</configuration>
<executions>
<execution>
<id>failsafe-integration-tests</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
</executions>
</plugin>
While configuring the maven-failsafe-plugin for a Spring Boot project, I hit this issue: https://github.com/spring-projects/spring-boot/issues/6254.
To fix this issue, I have added the classifier configuration to the spring-boot-maven-plugin as follows:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
Now we are going to use the script block to specify our custom Maven goal to run instead of the default goal.
.travis.yml
language: java
jdk: oraclejdk8
script:
- ./mvnw clean install -B
Step 6: SonarQube code quality checks using SonarCloud
SonarCloud, which is built on SonarQube, offers free code quality checks for Open Source projects.
Login with GitHub, go to My Account -> Security, and generate a new token for your project and save it somewhere. Now click on the Organizations tab and create an Organization with a unique key.
Travis CI provides the ability to encrypt sensitive data (https://docs.travis-ci.com/user/encryption-keys/) so that we can encrypt any keys or passwords and configure them in the .travis.yml file.
> sudo gem install travis
From the project root folder, run the following command to encrypt data:
travis encrypt SOMEVAR=“secretvalue”
This will generate output like:
secure: “…. encrypted data ….”
We can add all the secrets as global environment variables as follows:
env:
global:
- secure: "....encrypted data....."
Now let us encrypt the SonarCloud Token as follows:
travis encrypt SONAR_TOKEN=“my-sonar-token-here”
Finally, let us add SonarCloud support as an AddOn (https://docs.travis-ci.com/user/sonarcloud/) as follows:
language: java
jdk: oraclejdk8
env:
global:
- secure: "....encrypted sonar token here....."
addons:
sonarcloud:
organization: "sivaprasadreddy-github"
token:
secure: $SONAR_TOKEN
script:
- ./mvnw clean install -B
- ./mvnw clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar
Note that we used $SONAR_TOKEN to refer to the encrypted token variable and added one more command to run in the script block to run the sonar:sonar goal.
Step 7: Build a Docker image and publish it to DockerHub
Travis CI builds can run and build Docker images, and can also push images to Docker repositories. For more information, read https://docs.travis-ci.com/user/docker/.
Create a Dockerfile in the project’s root folder for our Spring Boot application as follows:
FROM frolvlad/alpine-oraclejdk8:slim
VOLUME /tmp
ADD target/jblogger-0.0.1-SNAPSHOT.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JAVA_OPTS="-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8787,suspend=n"
EXPOSE 8080 8787
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.profiles.active=docker -jar /app.jar" ]
To use Docker, add the following settings to .travis.yml:
sudo: required
services:
- docker
Now we can run Docker commands in our build.
Once the build is successful, we may want to build the Docker image and push it to Docker Hub. We can leverage the after_success section to perform this action.
We need to log in to DockerHub before pushing the image; we are going to configure the DockerHub credentials by encrypting them.
travis encrypt DOCKER_USER=”dockerhub-username”
travis encrypt DOCKER_PASS=”dockerhub-password”
Add these 2 secrets to the env.global section of .travis.yml.
Now we can add our Docker commands to build the image and publish it to Docker Hub in the after_success section as follows:
after_success:
- docker login -u $DOCKER_USER -p $DOCKER_PASS
- export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH; fi`
- export IMAGE_NAME=sivaprasadreddy/jblogger
- docker build -t $IMAGE_NAME:$COMMIT .
- docker tag $IMAGE_NAME:$COMMIT $IMAGE_NAME:$TAG
- docker push $IMAGE_NAME
Step 8: Deploy to Heroku
Travis CI provides options to deploy on a wide range of platforms, including Heroku, OpenShift, AWS, Azure, etc. Travis CI can automatically deploy your Heroku application after a successful build.
We are going to deploy our Spring Boot application on Heroku using Travis: https://docs.travis-ci.com/user/deployment/heroku/. Before deploying our application to Heroku, first we need to log in to https://www.heroku.com/ and create an application from the Dashboard.
Now create a Procfile in the root folder of the project as follows:
web java -Dserver.port=$PORT -Dspring.profiles.active=heroku $JAVA_OPTS -jar target/jblogger-0.0.1-SNAPSHOT-exec.jar
First, we need to get the Heroku API Key and add it as an encrypted secret.
travis encrypt HEROKU_API_KEY=“your-heroku-api-key-here”
We can deploy to Heroku from Travis by adding a deploy section as follows:
deploy:
provider: heroku
api_key: $HEROKU_API_KEY
app: jblogger
Now the complete .travis.yml file will look as follows:
sudo: required
language: java
jdk: oraclejdk8
services:
- docker
env:
global:
- secure: "encrypted-sonar-token"
- secure: "encrypted-dockerhub-username"
- secure: "encrypted-dockerhub-password"
- secure: "encrypted-heroku-api-key"
- COMMIT=${TRAVIS_COMMIT::7}
addons:
sonarcloud:
organization: "sivaprasadreddy-github"
token:
secure: $SONAR_TOKEN
script:
- ./mvnw clean install -B
- ./mvnw clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar
after_success:
- docker login -u $DOCKER_USER -p $DOCKER_PASS
- export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH; fi`
- export IMAGE_NAME=sivaprasadreddy/jblogger
- docker build -t $IMAGE_NAME:$COMMIT .
- docker tag $IMAGE_NAME:$COMMIT $IMAGE_NAME:$TAG
- docker push $IMAGE_NAME
deploy:
provider: heroku
api_key: $HEROKU_API_KEY
app: jblogger
Once the build is successful and deployed on Heroku, you should be able to access the application at https://<app>.herokuapp.com/.
I have just covered the most commonly performed tasks in Java applications, but Travis CI can do a lot more. Check out the Travis CI documentation at https://docs.travis-ci.com/.
Related content
- MicroServices - Part 6 : Distributed Tracing with Spring Cloud Sleuth and Zipkin
- MicroServices - Part 5 : Spring Cloud Zuul Proxy as API Gateway
- MicroServices - Part 4 : Spring Cloud Circuit Breaker using Netflix Hystrix
- MicroServices - Part 3 : Spring Cloud Service Registry and Discovery
- MicroServices - Part 2 : Configuration Management with Spring Cloud Config and Vault