Spring boot Cloud Native Buildpacks and Layered jars.

spring boot

In May 2020 spring boot 2.3 is released with some interesting features. There are many but in this article, we will talk about support for building OCI images using cloud-native build packs. 

Cloud-Native BuildPacks?

These days cloud migration and in that cloud-native application development is becoming a trend.

Cloud-Native Buildpacks
transform your application source code to images that can run on any cloud.

Cloud-native buildpacks definition from From https://buildpacks.io/,

The Cloud Native Buildpacks project was initiated by Pivotal and Heroku in January 2018 and joined the Cloud Native Sandbox in October 2018. The project aims to unify the buildpack ecosystems with a platform-to-buildpack contract that is well-defined and that incorporates learnings from maintaining production-grade buildpacks for years at both Pivotal and Heroku.

credit goes to https://buildpacks.io/

Cloud-Native Buildpacks embrace modern container standards, such as the OCI(Open container initiative) image format. They take advantage of the latest capabilities of these standards, such as cross-repository blob mounting and image layer “rebasing” on Docker API v2 registries.

All the above information is from buildpack website only. The buildpack in fewer words: will transform your beautiful source code into runnable container images.

The Paketo java build is used by default to create an image.

Prerequisites for this example:

  1. Java
  2. Docker
  3. Any IDE if you want.

Note:- For this demo, I am using spring-boot:2.3.1 version, JDK-8 and maven.

and Always for spring start with https://start.spring.io/

I have imported the project into the VSCode. If you want to learn about Spring tools for Visual Studio Code, please go through this link: https://www.techwasti.com/spring-tools-4-for-visual-studio-code/

Create One REST Controller:-

As part of this article, our focus is on buildpack not on complex coding.

We have a simple controller this will return the current date.

package com.techwasti.spring.buildpackex.springboot23ocibuildpackex;

import java.util.Date;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CurrentDateController{


    @GetMapping("/gettodaysdate")
     public String getTodaysDate(){
        return new Date().toString();
     }
}

Output: Sun Jul 19 09:00:36 IST 2020

Build the image for our app:

As mentioned above and as per the spring boot documentation, we will build an image. Package the source code and build the image as per OCI standard using the maven task.

$ mvn spring-boot:build-image

When you fire this command everything will be taken by spring boot build-image task. After a successful building image.

In your log you will see similar logs:

Successfully built image ‘docker.io/library/spring-boot23-oci-buildpack-ex:0.0.1-SNAPSHOT’

We can validate our docker image using the below command.

$ docker images| grep spring

Output
spring-boot23-oci-buildpack-ex 0.0.1-SNAPSHOT ddabb93c2218 40 ago 231MB

Now our image is ready, let us run the image and create a container 

$ docker run -d -p 8080:8080  — name springbuildpackex spring-boot23-oci-buildpack-ex:0.0.1-SNAPSHOT

once a container ready verify using 

$ docker ps

Hit REST API endpoint http://localhost:8080/gettodaysdate

You can hit actuator endpoints 

http://localhost:8080/actuator/metrics

http://localhost:8080/actuator/info

Spring Boot 2.3.0.RC1 Paketo Java buildpack is used by default to create images.

you can check on your local docker when you fire below command you can see Paketo docker image was downloaded.

$ docker images| grep gcr
gcr.io/paketo-buildpacks/run       base-cnb                c8c8215efa6f        8 days ago          71.1MB
gcr.io/paketo-buildpacks/builder base-platform-api-0.3 e49209451fa6 40 years ago 696MB

Customize build pack configuration:

Now we have seen that by default the name of the image is based on artifactId and a tag is a version of our maven project. Image name if spring-boot23-oci-buildpack-ex 0.0.1-SNAPSHOT

docker.io/library/${project.artifactId}:{project.version}

In your real-life project, you would like to push the OCI image to a specific docker image registry, which is internal to your organization. Here I am using the docker hub is a public central registry. You configure parameters such as the name of the docker image in POM.xml

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>docker.io/maheshwarligade/spring-boot23-oci-buildpack-ex</builder>
</image>
</configuration>
</plugin>

With these custom tags and names, I can push this image to the docker hub.

docker push docker.io/maheshwarligade/spring-boot23-oci-buildpack-ex

Using the command line as well:

$ mvn spring-boot:build-image -Dspring-boot.build-image.imageName=enterprise.com/library/domain/sspring-boot23-oci-buildpack-ex

We can also configure build packs builder to build the image using below configuration param

<configuration>            
<image>
<builder>cloudfoundry/cnb</builder>
</image>
</configuration>

Proxy Configuration:

If any proxy is configured between the Docker daemon the builder runs in and network locations that build-packs download artifacts from, you will need to configure the builder to use the proxy. When using the default builder, this can be accomplished by setting the HTTPS_PROXY and/or HTTP_PROXY environment:

<configuration>      
<image>
<env>
<HTTP_PROXY>http://proxy.example.com</HTTP_PROXY> <HTTPS_PROXY>https://proxy.example.com</HTTPS_PROXY>
</env>
</image>
</configuration>

Layered Jars:

We have seen above to create the image we used to build-pack, but you might not want to use build pack to build an image, perhaps we want to use some tool which is used within your organization based docker file to create an application image, Spring wanted to make it also easier to create optimized Docker images that can be built with a regular dockerfile so Spring has added support for layered jars.

basically we follow approach to create a docker image using spring boot application fat jar and add that into docker file and add a command to execute this.

The jar is organized into three main parts:

  • Classes used to bootstrap jar loading
  • Your application classes in BOOT-INF/classes
  • Dependencies in BOOT-INF/lib

Since this format is unique to Spring Boot, In spring boot in 2.3.0.M1 providing a new layout type call LAYERED_JAR

As we know about the docker file its layered file and when we rebuild image for dev purpose it should build where changes had happened instead of rebuilding the fat jar layer again and again. The layered jar type is designed to separate code based on how likely it is to change between application builds. Library code is less likely to change between builds, so it is placed in its own layers to allow tooling to re-use the layers from the cache. Application code is more likely to change between builds so it is isolated in a separate layer.

  • dependencies (for regularly released dependencies)
  • snapshot-dependencies (for snapshot dependencies)
  • resources (for static resources)
  • application (for application classes and resources)

Build the docker file 

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>LAYERED_JAR</layout>
</configuration>
</plugin>
</plugins>
</build>

Build jar for application

$ mvn clean package

We can have layered jar using jarmode

$ java -Djarmode=layertools -jar target/spring-boot23-oci-buildpack-ex-0.0.1-SNAPSHOT.jar list

Output

dependencies
snapshot-dependencies
resources
application

based on this we can craft docker file which will be similar like below:

FROM adoptopenjdk:11-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM adoptopenjdk:11-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/resources/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

Summary:

Here we have seen multiple ways to create an image of our spring boot application. With buildpacks, docker files, & existing plugins such as jib, there is no conclusion that which is the best way. Each approach has pros and cons and we have to use these tools based on our easiness and simplification.

Source Code: https://github.com/maheshwarLigade/spring-boot23-oci-buildpack-ex

References: