Developing A Spring Boot Application for Kubernetes Cluster: A Tutorial [Part 2]
As we proceed... The second part of this tutorial begins the installation and configuration of the Spring Boot application.
Join the DZone community and get the full member experience.
Join For FreeThis is the second part of this tutorial. Take a look at part one, part three, and part four here.
Spring Boot Application
Our sample application will provide zip code information utilizing an outside service provider, zipcodeapi.com. While the web layer exposes the service to end users, the service layer is responsible for interacting with the outside service provider. The web and service layers are loosely coupled via REST calls. We will develop each of the layers as separate Spring Boot applications to run inside their own Docker containers.
In order to develop and build the application we used Java 8, Maven version 3.3.9, and Docker Community Edition version 18.06.0-ce in a Mac OS development machine. To store the application images we utilized a private repository in Docker Hub. Hence, the prerequisites are:
- Java 8 or later
- Docker Community Edition
- A private repository in Docker Hub.
The resources needed for Spring Boot application can be found in Github.
Service Layer
The file and folder structure is as follows.
|_ Dockerfile
|_ pom.xml
|_ src
|_____ main
|_________ java
|______________ com
|__________________ demo
|______________________ kubernetes
|_________________________________ springcloud
|_____________________________________________ service
|_____________________________________________________ ServiceController.java
|_____________________________________________________ ServiceConfiguration.java
|_____________________________________________________ ZipcodeServer.java
|_________ resources
|___________________ zipcodeservice-server.yml
|___________________ logback.xml
ServiceController.java
This class is a @RestController and provides methods to get zip code information from zipcodeapi.com. The URLPrefix consists of a token to access zipcodeapi.com services, which is redacted in the listing below. (You can get your own free token for development purposes.) The service provides two API calls, to return basic demographic information about a particular zip code, public String getZipcodeInfo()
and to return nearby zip codes in the vicinity of a particular zip code within a specified distance, public String getNearbyZipcodes()
@RestController
public class ServiceController {
private static final String URLPrefix = "https://www.zipcodeapi.com/rest/<REDACTED>/";
private static final String GET = "GET";
protected Logger logger = Logger.getLogger(ServiceController.class.getName());
@Autowired
public ServiceController() {
logger.info("ServiceController initiated");
}
@RequestMapping(value = "/zipcodeservice/info/{zipcode}", produces = { "application/json" })
public String getZipcodeInfo(@PathVariable("zipcode") Integer zipcode) {
return getURLResponse("info.json/" + zipcode + "/degrees");
}
@RequestMapping(value = "/zipcodeservice/nearby/{zipcode}/{distance}", produces = { "application/json" })
public String getNearbyZipcodes(@PathVariable("zipcode") Integer zipcode,
@PathVariable("distance") Integer distance) {
return getURLResponse("radius.json/" + zipcode + "/" + distance + "/mile");
}
private String getURLResponse(String path) {
try {
URL url = new URL(URLPrefix + path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(GET);
StringBuilder result = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
reader.close();
return result.toString();
} catch (MalformedURLException e1) {
logger.log(Level.SEVERE, e1.getMessage(), e1);
return null;
} catch (IOException e2) {
logger.log(Level.SEVERE, e2.getMessage(), e2);
return null;
}
}
}
ServiceConfiguration.java
A Spring Boot application would typically contain a class to perform basic configuration tasks, e.g. establishing connectivity to a data source. Due to simplicity of our sample application no such configuration is needed, however, we leave it in place for completeness.
@Configuration
@ComponentScan
public class ServiceConfiguration {
protected Logger logger;
public ServiceConfiguration() {
logger = Logger.getLogger(getClass().getName());
logger.info("ServiceConfiguration initialized");
}
}
ZipcodeServer.java
This class is the main entry point to service layer. It starts up the application listening at the default port or the port supplied at the command line.
@SpringBootApplication
public class ZipcodeServer {
protected Logger logger = Logger.getLogger(ZipcodeServer.class.getName());
public static void main(String[] args) {
if (args.length > 0 && args.length != 1) {
errorMessage();
return;
} else if (args.length == 1) {
System.setProperty("server.port", args[0]);
}
System.setProperty("spring.config.name", "zipcodeservice-server");
SpringApplication.run(ZipcodeServer.class, args);
}
protected static void errorMessage() {
System.out.println("Usage: java -jar [jar file name] <port> OR");
System.out.println("Usage: java -jar [jar file name]");
}
}
zipcodeservice-server.yml
This is our application configuration file where the default port is defined.
spring:
application:
name: zipcode-service
# HTTP Server
server:
port: 2222 # HTTP (Tomcat) port
logback.xml
We skip reviewing this file, which simply configures the logger. (See Github.)
Dockerfile
Our application has a very simple Docker configuration. It will start listening at port 2223.
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar","2223"]
pom.xml
The Maven build file utilizes Finchley release, the latest version for Spring Cloud as of writing this article. An interesting part is the dockerfile-maven-plugin from com.spotify, which allows building a Docker image from the project, storing the image in local Docker repository, and pushing it into the private repository in Docker Hub. The value of repository
element gives name of the private repository. The value of tag
and imageTag
is the tag name for the service layer application, zipcode-service.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Finchley.RELEASE</version>
</parent>
<groupId>com.demo.kubernetes.springcloud</groupId>
<artifactId>service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<start-class>com.demo.kubernetes.springcloud.service.ZipcodeServer</start-class>
</properties>
<dependencies>
<dependency>
<!-- Setup Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<!-- Setup Spring MVC & REST, use Embedded Tomcat -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<!-- Setup Spring Data common components -->
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<!-- Testing starter -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<!-- Spring Cloud starter -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<tag>zipcode-service</tag>
<repository>konuratdocker/spark-examples</repository>
<imageTags>
<imageTag>zipcode-service</imageTag>
</imageTags>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
Building the Service Application and Creating Docker Image
In the root folder where the pom.xml file exists, execute:
mvn install dockerfile:build -DpushImageTag
At this point, the application has been built and the image stored locally. If we want to locally run the application, we could execute:
docker run -it -p <local port>:<target port> konuratdocker/spark-examples:zipcode-service
e.g.
docker run -it -p 8081:2223 konuratdocker/spark-examples:zipcode-service
Note that target port must be 2223 because that is the port specified in Dockerfile, ENTRYPOINT instruction. The local port could be the same as target port or different, 8081 in the example. Finally, observe that konuratdocker/spark-examples:zipcode-service is constructed from the values of <repository>, <tag> and <imageTag> elements in pom.xml.
You should see the following after docker run
is executed. (Observe the line printed by Tomcat's HTTP NIO connector, ["http-nio-2223"], the target port in Dockerfile.)
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.3.RELEASE)
1: INFO ZipcodeServer - No active profile set, falling back to default profiles: default
1: INFO Http11NioProtocol - Initializing ProtocolHandler ["http-nio-2223"]
1: INFO StandardService - Starting service [Tomcat]
1: INFO StandardEngine - Starting Servlet Engine: Apache Tomcat/8.5.31
1: INFO AprLifecycleListener - The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/usr/lib/jvm/java-1.8-openjdk/jre/lib/amd64/server:/usr/lib/jvm/java-1.8-openjdk/jre/lib/amd64:/usr/lib/jvm/java-1.8-openjdk/jre/../lib/amd64:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib]
1: INFO [/] - Initializing Spring embedded WebApplicationContext
1: INFO ServiceController - ServiceController initiated
1: INFO ServiceConfiguration$$EnhancerBySpringCGLIB$$5ec92564 - ServiceConfiguration initialized
1: INFO Http11NioProtocol - Starting ProtocolHandler ["http-nio-2223"]
1: INFO NioSelectorPool - Using a shared selector for servlet write/read
1: INFO ZipcodeServer - Started ZipcodeServer in 7.089 seconds (JVM running for 9.044)
1: INFO [/] - Initializing Spring FrameworkServlet 'dispatcherServlet'
Open a browser window to view http://localhost:8081/zipcodeservice/info/33301. You should see the following response comping from zipcodeapi.com:
{
"zip_code": "33301",
"lat": 26.121317,
"lng": -80.128146,
"city": "Fort Lauderdale",
"state": "FL",
"timezone": {
"timezone_identifier": "America/New_York",
"timezone_abbr": "EDT",
"utc_offset_sec": -14400,
"is_dst": "T"
},
"acceptable_city_names": [
{
"city": "Ft Lauderdale",
"state": "FL"
}
]
}
Having conducted the local testing, we can push the service layer image to Docker Hub. Log in to Docker Hub via docker login
in development machine. After login succeeds, execute:
mvn dockerfile:push -Ddockerfile.useMavenSettingsForAuth=true
You should see something similar to the below in the response.
[INFO] Image 4d76760644e2: Pushed
Now, inspection of the Docker Hub private repository shows the newly pushed image as follows.
Look for part 3 soon!
Opinions expressed by DZone contributors are their own.
Comments