Build Spring Microservices and Dockerize Them for Production

Build Spring Microservices and Dockerize Them for Production

In this post, you’ll learn about microservices architecture and how to implement it using Spring Boot. After creating some projects with the technique, you will deploy the artifacts as Docker containers and will simulate a container orchestrator (such as Kubernetes) using Docker Compose for simplification

In this post, you’ll learn about microservices architecture and how to implement it using Spring Boot. After creating some projects with the technique, you will deploy the artifacts as Docker containers and will simulate a container orchestrator (such as Kubernetes) using Docker Compose for simplification

The icing on the cake will be authentication integration using Spring Profiles; you will see how to enable it with a production profile.

But first, let’s talk about microservices.

Understand a Modern Microservice Architecture

Microservices, as opposed to a monolith architecture, dictates you have to divide your application into small, logically related, pieces. These pieces are independent software that communicates with other pieces using HTTP or messages, for example.

There is some discussion of what size micro is. Some say a microservice is software that can be created in a single sprint; others say microservices can have bigger size if it is logically related (you can’t mix apples and oranges, for example). I agree with Martin Fowler and think size doesn’t matter that much, and it’s more related to the style.

There are many advantages to microservices:

  • No high coupling risk - Since each app lives in a different process, it is impossible to create classes that talk to each other.
  • Easy scaling - As you already know, every service is an independent piece of software. As such, it can be scaled up or down on demand. Moreover, since the code is smaller than a monolith, it probably will start up faster.
  • Multiple stacks - You can use the best software stack for every service. No more need to use Java when, say, Python is better for what you’re building.
  • Fewer merges and code conflicts - As every service is a different repository, it is easier to handle and review commits.

However, there are some drawbacks:

  • You have a new enemy - network issues. Is the service up? What can you do if the service is down?
  • Complex deployment process - OK CI/CD is here, but you now have one workflow for each service. If they use different stacks, it’s possible you can’t even replicate a workflow for each.
  • More complex and hard-to-understand architecture - it depends on how you design it, but consider this: if you don’t know what a method is doing, you can read its code. In a microservice architecture, this method may be in another project, and you may not even have the code.

Nowadays, it’s commonly accepted that you should avoid a microservice architecture at first. After some iterations, the code division will become clearer as will the demands of your project. It is often too expensive to handle microservices until your development team starts into small projects.

Build Microservices in Spring with Docker

You’ll build two projects in this tutorial: a service (school-service) and a UI (school_ui). The service provides the persistent layer and business logic, and the UI provides the graphical user interface. Connecting them is possible with minimal configuration.

After the initial setup, I’ll talk about discovery and configuration services. Both services are an essential part of any massively distributed architecture. To prove this point, you will integrate it with OAuth 2.0 and use the configuration project to set the OAuth 2.0 keys.

Finally, each project will be transformed into a Docker image. Docker Compose will be used to simulate a container orchestrator as Compose will manage every container with an internal network between the services.

Lastly, Spring profiles will be introduced to change configuration based on the environment currently appropriately assigned. That way, you will have two OAuth 2.0 environments: one for development, and other for production.

Fewer words, more code! Clone this tutorial’s repository and check out the start branch.

git clone -b start https://github.com/oktadeveloper/okta-spring-microservices-docker-example.git 


The root pom.xml file is not a requirement. However, it can be helpful to manage multiple projects at once. Let’s look inside:


<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">
    4.0.0
    com.okta.developer.docker_microservices
    parent-pom
    0.0.1-SNAPSHOT
    pom
    parent-project
    
        school-service
        school-ui    
    



This is called an aggregate project because it aggregates child projects. It is useful for running the same Maven task on all declared modules. The modules do not need to use the root module as a parent.

There are two modules available: a school service, and a school UI.

The School Service Microservice

The school-service directory contains a Spring Boot project that acts as the project’s persistence layer and business rules. In a more complex scenario, you would have more services like this. The project was created using the always excellent Spring Initializr with the following configuration:

  • Group - com.okta.developer.docker_microservices
  • Artifact - school-service
  • Dependencies - JPA, Web, Lombok, H2

You can get more details about this project by reading Spring Boot with PostgreSQL, Flyway, and JSONB. To summarize, it has the entities TeachingClass, Course, Student and uses TeachingClassServiceDB and TeachingClassController to expose some data through a REST API. To test it, open a terminal, navigate to the school-service directory, and run the command below:

./mvnw spring-boot:run


The application will start on port 8081 (as defined in file school-service/src/main/resources/application.properties), so you should be able to navigate to [http://localhost:8081](http://localhost:8081) and see the returned data.

> curl http://localhost:8081
[
   {
      "classId":13,
      "teacherName":"Profesor Jirafales",
      "teacherId":1,
      "courseName":"Mathematics",
      "courseId":3,
      "numberOfStudents":2,
      "year":1988
   },
   {
      "classId":14,
      "teacherName":"Profesor Jirafales",
      "teacherId":1,
      "courseName":"Spanish",
      "courseId":4,
      "numberOfStudents":2,
      "year":1988
   },
   {
      "classId":15,
      "teacherName":"Professor X",
      "teacherId":2,
      "courseName":"Dealing with unknown",
      "courseId":5,
      "numberOfStudents":2,
      "year":1995
   },
   {
      "classId":16,
      "teacherName":"Professor X",
      "teacherId":2,
      "courseName":"Dealing with unknown",
      "courseId":5,
      "numberOfStudents":1,
      "year":1996
   }
]


The Spring-Based School UI Microservice

The school UI is, as the name says, the user interface that utilizes School Service. It was created using Spring Initializr with the following options:

  • Group - com.okta.developer.docker_microservices
  • Artifact - school-ui
  • Dependencies - Web, Hateoas, Thymeleaf, Lombok

The UI is a single web page that lists the classes available on the database. To get the information, it connects with the school-service through a configuration in file school-ui/src/main/resources/application.properties.

service.host=localhost:8081


The class SchoolController class has all the logic to query the service:

package com.okta.developer.docker_microservices.ui.controller;

import com.okta.developer.docker_microservices.ui.dto.TeachingClassDto;
import org.springframework.beans.factory.annotation.*;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;

@Controller
@RequestMapping("/")
public class SchoolController {
    private final RestTemplate restTemplate;
    private final String serviceHost;

    public SchoolController(RestTemplate restTemplate, @Value("${service.host}") String serviceHost) {
        this.restTemplate = restTemplate;
        this.serviceHost = serviceHost;
    }

    @RequestMapping("")
    public ModelAndView index() {
        return new ModelAndView("index");
    }

    @GetMapping("/classes")
    public ResponseEntity> listClasses(){
        return restTemplate
                .exchange("http://"+ serviceHost +"/class", HttpMethod.GET, null,
                        new ParameterizedTypeReference>() {});
    }
}


As you can see, there is a hard-coded location for the service. You can change the property setting with an environment variable like this -Dservice.host=localhost:9090. Still, it has to be manually defined. How about having many instances of school-service application? Impossible at the current stage.

With school-service turned on, start school-ui, and navigate to it in a browser at [http://localhost:8080](http://localhost:8080):

./mvnw spring-boot:run


You should see a page like the following:

Build a Discovery Server with Spring Cloud and Eureka

Now you have a working application that uses two services to provide the information to end-user. What is wrong with it? In modern applications, developers (or operations) usually don’t know where or what port an application might be deployed on. The deployment should be automated so that no one cares about server names and physical location. (Unless you work inside a data center. If you do, I hope you care!)

Nonetheless, it is essential to have a tool that helps the services to discover their counterparts. There are many solutions available, and for this tutorial, we are going to use Eureka from Netflix as it has outstanding Spring support.

Go back to start.spring.io and create a new project as follows:

  • Group: com.okta.developer.docker_microservices
  • Artifact: discovery
  • Dependencies: Eureka Server

Edit the main DiscoveryApplication.java class to add an @EnableEurekaServer annotation:

package com.okta.developer.docker_microservices.discovery;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {
    public static void main(String[] args) {
        SpringApplication.run(DiscoveryApplication.class, args);
    }
}


And, you’ll need to update its application.properties file so it runs on port 8761 and doesn’t try to register with itself.

spring.application.name=discovery-server
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false


Let’s define each property:

  • spring.application.name - The name of the application, also used by the discovery service to discover a service. You’ll see that every other application has an application name too.
  • server.port - The port the server is running. 8761 is the default port for Eureka server.
  • eureka.client.register-with-eureka - Tells Spring not to register itself into the discovery service.
  • eureka.client .fetch-registry - Indicates this instance should not fetch discovery information from the server.

Now, run and access [http://localhost:8761](http://localhost:8761).

./mvnw spring-boot:run


The screen above shows the Eureka server ready to register new services. Now, it is time to change school-service and school-ui to use it.

NOTE: If you receive a ClassNotFoundException: javax.xml.bind.JAXBContext error on startup, it’s because you’re running on Java 11. You can add JAXB dependencies to your pom.xml to fix this.


  javax.xml.bind
  jaxb-api
  2.3.1


  org.glassfish.jaxb
  jaxb-runtime
  2.3.2



Use Service Discovery to Communicate Between Microservices

First, it is important to add the required dependencies. Add the following to both pom.xml file (in the school-service and school-ui projects):


    org.springframework.cloud
    spring-cloud-starter-netflix-eureka-client



This module is part of the Spring Cloud initiative and, as such, needs a new dependency management node as follows (don’t forget to add to both projects):


    
        
            org.springframework.cloud
            spring-cloud-dependencies
            ${spring-cloud.version}
            pom
            import
        
    



Now you need to configure both applications to register with Eureka.

In the application.properties file of both projects, add the following lines:

eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://localhost:8761/eureka}
spring.application.name=school-service


Don’t forget to change the application name from school-service to school-ui in the school-ui project. Notice there is a new kind of parameter in the first line: {EUREKA_SERVER:[http://localhost:8761/eureka}](http://localhost:8761/eureka}). It means “if environment variable EUREKA_SERVER exists, use its value, if not, here’s a default value.” This will be useful in future steps. ;)

You know what? Both applications are ready to register themselves into the discovery service. You don’t need to do anything more. Our primary objective is that school-ui project does not need to know where school-service is. As such, you need to change SchoolController (in the school-ui project) to use school-service in its REST endpoint. You can also remove the serviceHost variable in this class.

package com.okta.developer.docker_microservices.ui.controller;

import com.okta.developer.docker_microservices.ui.dto.TeachingClassDto;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
@RequestMapping("/")
public class SchoolController {
    private final RestTemplate restTemplate;

    public SchoolController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @RequestMapping("")
    public ModelAndView index() {
        return new ModelAndView("index");
    }

    @GetMapping("/classes")
    public ResponseEntity> listClasses() {
        return restTemplate
                .exchange("http://school-service/classes", HttpMethod.GET, null,
                        new ParameterizedTypeReference>() {});
    }
}


Before integrating Eureka, you had a configuration pointing out where school-service was. Now, you’ve changed the service calls to use the name used by the other service: no ports, no hostname. The service you need is somewhere, and you don’t need to know where.

The school-service may have multiple instances of and it would be a good idea to load balance the calls between the instances. Thankfully, Spring has a simple solution: on the RestTemplate bean creation, add @LoadBalanced annotation as follows. Spring will manage multiple instance calls each time you ask something to the server.

package com.okta.developer.docker_microservices.ui;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.*;

@SpringBootApplication
public class UIWebApplication implements WebMvcConfigurer {

    public static void main(String[] args) {
        SpringApplication.run(UIWebApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if(!registry.hasMappingForPattern("/static/**")) {
            registry.addResourceHandler("/static/**")
                    .addResourceLocations("classpath:/static/", "classpath:/static/js/");
        }
    }
}


Now, start restart school-service and school-ui (and keep the Discovery service up). Have a quick look at [http://localhost:8761](http://localhost:8761) again:

Now your services are sharing info with the Discovery server. You can test the application again and see that it work as always. Just go to [http://localhost:8080](http://localhost:8080) in your favorite browser.

Add a Configuration Server to Your Microservices Architecture

While this configuration works, it’s even better to remove any trace of configuration values in the project’s source code. First, the configuration URL was removed from the project and became managed by a service. Now, you can do a similar thing for every configuration on the project using Spring Cloud Config.

First, create the configuration project using Spring Initializr and the following parameters:

  • Group: com.okta.developer.docker_microservices
  • Artifact: config
  • Dependencies: Config Server, Eureka Discovery

In the main class, add @EnableConfigServer:

package com.okta.developer.docker_microservices.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {
    ...
}


Add the following properties and values in the project’s application.properties:

spring.application.name=CONFIGSERVER
server.port=8888
spring.profiles.active=native
spring.cloud.config.server.native.searchLocations=.
eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://localhost:8761/eureka}


Some explanation about the properties:

  • spring.profiles.active=native - Indicates Spring Cloud Config must use the native file system to obtain the configuration. Normally Git repositories are used, but we are going to stick with native filesystem for simplicity sake.
  • spring.cloud.config.server.native.searchLocations - The path containing the configuration files. If you change this to a specific folder on your hard drive, make sure and create the school-ui.properties file in it.

Now, you need something to configure and apply to this example. How about Okta’s configuration? Let’s put our school-ui behind an authorization layer and use the property values provided by the configuration project.

You can register for a free-forever developer account that will enable you to create as many user and applications you need to use! After creating your account, create a new Web Application in Okta’s dashboard (Applications > Add Application):

And fill the next form with the following values:

The page will return you an application ID and an secret key. Keep then safe and create a file called school-ui.properties in the root folder of the config project with the following contents. Do not forget to populate the variable values:

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId={yourClientId}
okta.oauth2.clientSecret={yourClientSecret}


Now, run the config project and check if its getting the configuration data properly:

./mvnw spring-boot:run
> curl http://localhost:8888/school-ui.properties
okta.oauth2.clientId: YOUR_CLIENT_ID
okta.oauth2.clientSecret: YOUR_CLIENT_SECRET
okta.oauth2.issuer: https://YOUR_DOMAIN/oauth2/default


Change School UI to Use Spring Cloud Config and OAuth 2.0

Now you need to change the Spring UI project a little bit.

First, you need to change school-ui/pom.xml and add some new dependencies:


    org.springframework.cloud
    spring-cloud-starter-config


    com.okta.spring
    okta-spring-boot-starter
    1.1.0


    org.thymeleaf.extras
    thymeleaf-extras-springsecurity5



Create a new SecurityConfiguration class in the com.okta...ui.config package:

package com.okta.developer.docker_microservices.ui;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

   @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated()
            .and()
                .logout().logoutSuccessUrl("/")
            .and()
                .oauth2Login();
    }
}


Change your SchoolController so only users with scope profile will be allowed (every authenticated user will have it).

import org.springframework.security.access.prepost.PreAuthorize;

....

@GetMapping("/classes")
@PreAuthorize("hasAuthority('SCOPE_profile')")
public ResponseEntity> listClasses(){
    return restTemplate
        .exchange("http://school-service/class", HttpMethod.GET, null,
                new ParameterizedTypeReference>() {});
}


Some configurations need to be defined at project boot time. Spring had a clever solution to locate properly and extract configuration data before context startup. You need to create a file src/main/resources/bootstrap.yml like this:

eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}
spring:
  application:
    name: school-ui
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIGSERVER


The bootstrap file creates a pre-boot Spring Application Context responsible for extracting configuration before the real application starts. You need to move all properties from application.properties to this file as Spring needs to know where your Eureka Server is located and how it should search for configuration. In the example above, you enabled configuration over discovery service (spring.cloud.config.discovery.enabled) and specified the Configuration service-id.

Change your application.properties file so it only has one OAuth 2.0 property:

okta.oauth2.redirect-uri=/authorization-code/callback


The last file to modify is src/main/resources/templates/index.hml. Adjust it to show a login button if the user is not authenticated, and a logout button if the user is logged in.




    
    
    

    
    

    Hello, world!



    
        
        Logout
    
    
        Login
    



    # School classes


    
        
        
            Course
            Teacher
            Year
            Number of students
        
        
        

        
    

    
    

    
    
    
    






There are some Thymeleaf properties you should know about in this HTML:

  • @{/logout} - returns the logout URL defined on the backend
  • th:if="${#authorization.expression('isAuthenticated()')}" - only print the HTML if the user is logged in
  • @{//oauth2/authorization/okta} - this is the URL that Spring Security redirects to for Okta. You could link to /login as well, but that just renders the same link and you have to click on it.
  • th:unless="${#authorization.expression('isAuthenticated()')}" - only print the HTML inside the node if the user is logged off

Now restart the configuration project and school-ui again. If you navigate to typing [http://localhost:8080](http://localhost:8080), you should see the following screen:

After logged in, the screen should appear like this one:

Congratulations, you created a microservices architecture using Spring Cloud config and Eureka for service discovery! Now, let’s go one step further and Dockerize every service.

Use Docker to Package Your Spring Apps

Docker is a marvelous technology that allows creating system images similar to Virtual Machines but that shares the same Kernel of the host operating system. This feature increases system performance and startup time. Also, Docker provided an ingenious built system that guarantees once an image is created; it won’t be changed, ever. In other words: no more “it works on my machine!”

TIP: Need a deeper Docker background? Have a look at our Developer’s Guide To Docker.

You’ll need to create one Docker image for each project. Each image should have the same Maven configuration and Dockerfile content in the root folder of each project (e.g., school-ui/Dockerfile).

In each project’s pom, add the dockerfile-maven-plugin:


    ...
    
        com.spotify
        dockerfile-maven-plugin
        1.4.9
        
            
                default
                
                    build
                    push
                
            
        
        
            developer.okta.com/microservice-docker-${project.artifactId}
            ${project.version}
            
                ${project.build.finalName}.jar
            
        
    



This XML configures the Dockerfile Maven plugin to build a Docker image every time you run ./mvnw install. Each image will be created with the name developer.okta.com/microservice-docker-${project.artifactId} where project.artifactId varies by project.

Create a Dockerfile file in the root directory of each project.

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/*.jar app.jar
ENV JAVA_OPTS="
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]


The Dockerfile follows what is recommended by Spring Boot with Docker.

Now, change school-ui/src/main/resources/bootstrap.yml to add a new failFast setting:

eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}
spring:
  application:
    name: school-ui
  cloud:
    config:
      discovery:
        enabled: true
        serviceId: CONFIGSERVER
      failFast: true


The spring.cloud.failFast: true setting tells Spring Cloud Config to terminate the application as soon as it can’t find the configuration server. This will be useful for the next step.

Add Docker Compose to Run Everything

Create a new file called docker-compose.yml that defines how each project starts:

version: '3'
services:
  discovery:
    image: developer.okta.com/microservice-docker-discovery:0.0.1-SNAPSHOT
    ports:
      - 8761:8761
  config:
    image: developer.okta.com/microservice-docker-config:0.0.1-SNAPSHOT
    volumes:
      - ./config-data:/var/config-data
    environment:
      - JAVA_OPTS=
         -DEUREKA_SERVER=http://discovery:8761/eureka
         -Dspring.cloud.config.server.native.searchLocations=/var/config-data
    depends_on:
      - discovery
    ports:
      - 8888:8888
  school-service:
    image: developer.okta.com/microservice-docker-school-service:0.0.1-SNAPSHOT
    environment:
      - JAVA_OPTS=
        -DEUREKA_SERVER=http://discovery:8761/eureka
    depends_on:
      - discovery
      - config
  school-ui:
    image: developer.okta.com/microservice-docker-school-ui:0.0.1-SNAPSHOT
    environment:
      - JAVA_OPTS=
        -DEUREKA_SERVER=http://discovery:8761/eureka
    restart: on-failure
    depends_on:
      - discovery
      - config
    ports:
      - 8080:8080


As you can see, each project is now a declared service in Docker compose the file. It’ll have its ports exposed and some other properties.

  • All projects besides discovery will have a variable value -DEUREKA_SERVER=[http://discovery:8761/eureka](http://discovery:8761/eureka). This will tell where to find the Discovery server. Docker Compose creates a virtual network between the services and the DNS name used for each service is its name: that’s why it’s possible to use discovery as the hostname.
  • The Config service will have a volume going to configuration files. This volume will be mapped to /var/config-data inside the docker container. Also, the property spring.cloud.config.server.native.searchLocations will be overwritten to the same value. You must store the file school-ui.properties in the same folder specified on the volume mapping (in the example above, the relative folder ./config-data).
  • The school-ui project will have the property restart: on-failure. This set Docker Compose to restart the application as soon as it fails. Using together with failFast property allows the application to keep trying to start until the Discovery and Config projects are completely ready.

And that’s it! Now, build the images:

cd config && ./mvnw clean install
cd ../discovery && ./mvnw clean install
cd .. && ./mvnw clean install


The last command will likely fail with the following error in the school-ui project:

java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: java.lang.IllegalStateException: No instances found of configserver (CONFIGSERVER)


To fix this, create a school-ui/src/test/resources/test.properties file and add properties so Okta’s config passes, and it doesn’t use discovery or the config server when testing.

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId=TEST
spring.cloud.discovery.enabled=false
spring.cloud.config.discovery.enabled = false
spring.cloud.config.enabled = false


Then modify UIWebApplicationTests.java to load this file for test properties:

import org.springframework.test.context.TestPropertySource;

...
@TestPropertySource(locations="classpath:test.properties")
public class UIWebApplicationTests {
    ...
}


Now you should be able to run ./mvnw clean install in the school-ui project.

Once that completes, run Docker Compose to start all your containers (in the same directory where docker-compose.yml is).

docker-compose up -d
Starting okta-microservice-docker-post-final_discovery_1 ... done
Starting okta-microservice-docker-post-final_config_1    ... done
Starting okta-microservice-docker-post-final_school-ui_1      ... done
Starting okta-microservice-docker-post-final_school-service_1 ... done


Now you should be able to browse the application as you did previously.

Use Spring Profiles to Modify Your Microservices’ Configuration

Now you’ve reached the last stage of today’s journey through microservices. Spring Profiles is a powerful tool. Using profiles, it is possible to modify program behavior by injecting different dependencies or configurations completely.

Imagine you have a well-architected software that has its persistence layer separated from business logic. You also provide support for MySQL and PostgreSQL, for example. It is possible to have different data access classes for each database that will be only loaded by the defined profile.

Another use case is for configuration: different profiles might have different configurations. Take authentication, for instance. Will your test environment have authentication? If it does, it shouldn’t use the same user directory as production.

Change your configuration project to have two apps in Okta: one default (for development) and another for production. Create a new Web application on Okta website and name it “okta-docker-production.”

Now, in your config project, create a new file called school-ui-production.properties. You already have school-ui.properties, which will be used by every School UI instance. When adding the environment at the end of the file, Spring will merge both files and take precedence over the most specific file. Save the file with your production app’s client ID and secret, like this:

school-ui-production.properties

okta.oauth2.clientId={YOUR_PRODUCTION_CLIENT_ID}
okta.oauth2.clientSecret={YOUR_PRODUCTION_CLIENT_SECRET}


Now, run the configuration project using Maven, then run the following two curl commands:

./mvnw spring-boot:run

> curl http://localhost:8888/school-ui.properties

okta.oauth2.issuer: https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId: ==YOUR DEV CLIENT ID HERE==
okta.oauth2.clientSecret: ==YOUR DEV CLIENT SECRET HERE==

> curl http://localhost:8888/school-ui-production.properties
okta.oauth2.issuer: https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId: ==YOUR PROD CLIENT ID HERE==
okta.oauth2.clientSecret: ==YOUR PROD CLIENT SECRET HERE==


As you can see, even though the file school-ui-production has two properties, the config project displays three (since the configurations are merged).

Now, you can change the school-ui service in the docker-compose.yml to use the production profile:

school-ui:
  image: developer.okta.com/microservice-docker-school-ui:0.0.1-SNAPSHOT
  environment:
    - JAVA_OPTS=
      -DEUREKA_SERVER=http://discovery:8761/eureka
      -Dspring.profiles.active=production
  restart: on-failure
  depends_on:
    - discovery
    - config
  ports:
    - 8080:8080


You’ll also need to copy school-ui-production.properties to your config-data directory. Then shut down all your Docker containers and restart them.

docker-compose down
docker-compose up -d


You should see the following printed in the logs of the school-ui container:

The following profiles are active: production


That’s it! Now you have your microservices architecture running with a production profile. Huzzah!

TIP: If you want to prove your okta-docker-production app is used and not okta-docker, you can deactivate the okta-docker app in Okta and confirm you can still log in at [http://localhost:8080](http://localhost:8080).

Build Spring Boot Microservice application on Docker

Build Spring Boot Microservice application on Docker

Build Spring Boot Microservice application on Docker - Learn about the benefits of Docker containers for deploying Spring Boot microservices and how to get started with a simple Spring Boot app.

Docker is currently a hot topic in container-based deployment, whereas Spring Boot is the same for microservice development. Both Spring Boot and Docker together form a great combo for developing microservice-based applications. In this article, I will try to explain in very simple words

  • What is Docker and what are its benefits?
  •  What Spring Boot is and how to create a simple Spring Boot application.
  • Hosting the Spring Boot application in a Docker container.
Docker Containers

A Docker container is a tool that makes it very easy to deploy and run an application using containers. A container allows a developer to create an all-in-one package of the developed application with all its dependencies. For example, a Java application requires Java libraries, and when we deploy it on any system or VM, we need to install Java first. But, in a container, everything is kept together and shipped as one package, such as in a Docker container. Read the documentation for more information about Docker containers.

Spring Boot Applications

Spring Boot is a framework that eases the development of web applications. It has a lot of pre-configured modules that eliminate the manual addition of dependencies for developing an application with Spring. This is the sole reason for this being one of the favorites for creating microservices. Let's see how to create a Spring Boot Application in a few minutes.

Open Spring Starter to create a Java Maven application with Spring Starter libraries.

Provide the Artifact Group and Name, and in dependencies, add “Web” and leave everything else with the default, which will create a Maven project with Java and Spring Boot. This will generate a ZIP which is to be imported into STS as a Maven project.

That’s it! You have just created a Spring Boot application in the workspace. Now, we need to add a simple RestController so we can test the API.

Upon running the application and accessing the endpoint of the API, we will see the output “Simple Spring Boot Application” shown in the browser.

We have successfully created and run the application in the embedded server of the IDE, but now we will deploy it in a Docker container. For this, we have to create a Dockerfile that will contain the steps to be executed by Docker to create an image of this application and run that image from Docker.


You may also like: Spring Boot: Run and Build in Docker


JAR File of This Application

In the pom.XML, we defined that the packaging will be JAR. Let us run the Maven commands to create a JAR file for us.

To do so, first clean up the target folder with mvn clean (this can also be done from the IDE by running Maven Clean) and mvn install (this can also be done from IDE by running Maven Install).

This command will create a “dockerdemo.jar” in the target directory of the working directory.

Docker gives the user the capability to create their own Docker images and deploy them in Docker. To create your own Docker image, we have to create our own Dockerfile. Basically, a Dockerfile is a simple text file with all the instructions required to build the image.

Here is our Dockerfile. Create a simple file in the project folder and add these steps in that file:

  1. FROM java:8 means this is a Java application and will require all the Java librariesk so it will pull all the Java-related libraries and add them to the container.
  2. EXPOSE 8080 means that we would like to expose 8080 to the outside world to access our application.
  3. ADD /target/dockerdemo.jar dockerdemo.jar
  4. ADD
  5. ENTRYPOINT [“java”, “-jar”, “dockerdemo.jar”] will run the command as the entry point as this is a JAR and we need to run this JAR from within Docker.

These are the four steps for that will create an image of our Java application to be able to run Docker.

Okay, we have two pieces ready:

  1. FROM java:8 means this is a Java application and will require all the Java librariesk so it will pull all the Java-related libraries and add them to the container.
  2. EXPOSE 8080 means that we would like to expose 8080 to the outside world to access our application.
  3. ADD /target/dockerdemo.jar dockerdemo.jar
  4. ADD
  5. ENTRYPOINT [“java”, “-jar”, “dockerdemo.jar”] will run the command as the entry point as this is a JAR and we need to run this JAR from within Docker.

To load these up in the Docker container, we have to first create the image and then run that image from the Docker container. We need to run certain commands in the folder that contains the Dockerfile.

This will create our image in Docker and load it up to the container.

Now that we have the image ready to run, let’s do that with the following command:

 There you go — the Spring Boot application boots up and the server is running on port 8080.

The Spring Boot Application is running from the Docker container.

Here is the repository that contains the code for this article. 

Originally published by Sovan Misra  at dzone.com

Build a Java 8 Spring Boot App with Docker

Build a Java 8 Spring Boot App with Docker

Build a Java 8 Spring Boot App with Docker - In this article, I am going to show you how to develop and run a simple Spring web application using Java 8 without installing Java 8 on your local machine.

Python developers use virtual environments for creating and managing separate environments for different projects, and each using different versions of Python for execution and storing and resolving Python dependencies. Java and many other technologies do not support a virtual environment concept. At this point, Docker comes to our aid.

Docker is a virtualization platform. I am not going dive into details of docker's details. you can find basic information and installation guide from the docker official site.

Once you have the Docker toolbox installed, you do not need install Java 8 or MySQL which are needed in our sample application.

Now, you can download my codes from GitHub.

First, let's check the Docker-compose file:

version : '2'
services:

  springappserver:
    build:
      context: . 
      dockerfile: springapp.dockerfile
    ports: 
      - "8080:8080"
    networks:
      - net-spring-db
    volumes:
      - .:/vol/development
    depends_on:
      - mysqldbserver

  mysqldbserver:
    build:
      context: . 
      dockerfile: mysqldb.dockerfile
    ports:
      - "3306:3306"
    networks:
      - net-spring-db
    environment:
      MYSQL_DATABASE: testdb
      MYSQL_USER: myuser
      MYSQL_PASSWORD: mypassword
      MYSQL_ROOT_PASSWORD: myrootpassword
    container_name: mysqldbserver

networks:
  net-spring-db:
    driver: bridge

We have two servers each on the 'net-spring-db' network. The first one is named 'springappserver' and configured with the springapp.dockerfile, which will be described later. The second one is named as mysqldbserver and configured with the mysqldb.dockerfile, which will be described later.

Now, let's have a look at the springapp.dockerfile:

#
# Java 1.8 & Maven Dockerfile
#
#

# pull base image.
FROM java:8

# maintainer
MAINTAINER Dursun KOC "[email protected]"

# update packages and install maven
RUN  \
  export DEBIAN_FRONTEND=noninteractive && \
  sed -i 's/# \(.*multiverse$\)/\1/g' /etc/apt/sources.list && \
  apt-get update && \
  apt-get -y upgrade && \
  apt-get install -y vim wget curl maven

# attach volumes
VOLUME /vol/development

# create working directory
RUN mkdir -p /vol/development
WORKDIR /vol/development

# maven exec
CMD ["mvn", "clean", "package", "exec:java"]

This Docker file configures a Docker image, which is inherited from a Java 8 image from Docker Hub. Over that Java 8 image, I have installed vim, wget, curl, Maven, and set the volume in order to put my existing projects code. And finally, execute the Maven command to run my application.

Now let's check the mysqldb.dockerfile:

FROM mysql/mysql-server

MAINTAINER Dursun KOC <[email protected]>

# Copy the database initialize script: 
# Contents of /docker-entrypoint-initdb.d are run on mysqld startup
ADD  mysql/ /docker-entrypoint-initdb.d/

This Dockerfile configures a Docker image, which is inherited from the MySQL/mysql-server image from Docker Hub. Over the MySQL image, I put my db-schema creation scripts, which are located in the MySQL folder. I have a single SQL file at this folder — data.sql — in order to create the 'person' table.

Now, let's see application structure.

Our application is started from the src/com/turkcell/softlab/Application.java file, and our only Controller is the PersonController(src/com/turkcell/softlab/controller/PersonController.java).

You can run the whole project with a simple command:

docker-compose up -d

For testing, use the following two commands in your local machine:

  • Create new person:
curl -H "Content-Type: application/json" -X POST -d "{\"first\": \"Mustafa\",\"last\": \"KOÇ\",\"dateofbirth\": 381110400000,\"placeofbirth\": \"Erzincan\"}" "http://192.168.99.100:8080/people"
  • List existing people in the database:
curl -H "Content-Type: application/json" -X GET "http://192.168.99.100:8080/people"

Now, it's your turn! You can dive deeper into Java 8 and Spring Boot using this template.

Build Microservice Architecture With Kubernetes, Spring Boot , and Docker

Build Microservice Architecture With Kubernetes, Spring Boot , and Docker

In this article we learn how to start the Spring Boot microservice project and run it fast with Kubernetes and Docker

The topics covered in this article are:

  • Using Spring Boot 2.0 in cloud-native development

  • Providing service discovery for all microservices using a Spring Cloud Kubernetes project

  • Injecting configuration settings into application pods using Kubernetes Config Maps and Secrets

  • Building application images using Docker and deploying them on Kubernetes using YAML configuration files

  • Using Spring Cloud Kubernetes together with a Zuul proxy to expose a single Swagger API documentation for all microservices

Spring Cloud and Kubernetes may be threatened as competitive solutions when you build a microservices environment. Such components like Eureka, Spring Cloud Config, or Zuul provided by Spring Cloud may be replaced by built-in Kubernetes objects like services, config maps, secrets, or ingresses. But even if you decide to use Kubernetes components instead of Spring Cloud, you can take advantage of some interesting features provided throughout the whole Spring Cloud project.

The one really interesting project that helps us in development is Spring Cloud Kubernetes. Although it is still in the incubation stage, it is definitely worth dedicating some time to it. It integrates Spring Cloud with Kubernetes. I'll show you how to use an implementation of the discovery client, inter-service communication with the Ribbon client, and Zipkin discovery using Spring Cloud Kubernetes.

Before we proceed to the source code, let's take a look at the following diagram. It illustrates the architecture of our sample system. It is quite similar to the architecture presented in the mentioned article about microservices on Spring Cloud. There are three independent applications (employee-service, department-service, organization-service), which communicate with each other through a REST API. These Spring Boot microservices use some built-in mechanisms provided by Kubernetes: config maps and secrets for distributed configuration, etcd for service discovery, and ingresses for the API gateway.

Let's proceed to the implementation. Currently, the newest stable version of Spring Cloud is Finchley.RELEASE. This version of spring-cloud-dependencies should be declared as a BOM for dependency management.

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Spring Cloud Kubernetes is not released under Spring Cloud Release Trains, so we need to explicitly define its version. Because we use Spring Boot 2.0 we have to include the newest SNAPSHOT version of spring-cloud-kubernetes artifacts, which is 0.3.0.BUILD-SNAPSHOT.

The source code of sample applications presented in this article is available on GitHub in this repository.

Pre-Requirements

In order to be able to deploy and test our sample microservices, we need to prepare a development environment. We can realize that in the following steps:

  • You need at least a single node cluster instance of Kubernetes (Minikube) or Openshift (Minishift) running on your local machine. You should start it and expose the embedded Docker client provided by both of them. The detailed instructions for Minishift may be found in my Quick guide to deploying Java apps on OpenShift. You can also use that description to run Minikube — just replace word "minishift" with "minikube." In fact, it does not matter if you choose Kubernetes or Openshift — the next part of this tutorial will be applicable for both of them.

  • Spring Cloud Kubernetes requires access to the Kubernetes API in order to be able to retrieve a list of addresses for pods running for a single service. If you use Kubernetes, you should just execute the following command:

$ kubectl create clusterrolebinding admin --clusterrole=cluster-admin --serviceaccount=default:default

If you deploy your microservices on Minishift, you should first enable admin-user add-on, then log in as a cluster admin and grant the required permissions.

$ minishift addons enable admin-user
$ oc login -u system:admin
$ oc policy add-role-to-user cluster-reader system:serviceaccount:myproject:default
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongodb
  labels:
    app: mongodb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      containers:
      - name: mongodb
        image: mongo:latest
        ports:
        - containerPort: 27017
        env:
        - name: MONGO_INITDB_DATABASE
          valueFrom:
            configMapKeyRef:
              name: mongodb
              key: database-name
        - name: MONGO_INITDB_ROOT_USERNAME
          valueFrom:
            secretKeyRef:
              name: mongodb
              key: database-user
        - name: MONGO_INITDB_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongodb
              key: database-password
---
apiVersion: v1
kind: Service
metadata:
  name: mongodb
  labels:
    app: mongodb
spec:
  ports:
  - port: 27017
    protocol: TCP
  selector:
    app: mongodb
1. Inject the Configuration With Config Maps and Secrets

When using Spring Cloud, the most obvious choice for realizing a distributed configuration in your system is Spring Cloud Config. With Kubernetes, you can use Config Map. It holds key-value pairs of configuration data that can be consumed in pods or used to store configuration data. It is used for storing and sharing non-sensitive, unencrypted configuration information. To use sensitive information in your clusters, you must use Secrets. Use of both these Kubernetes objects can be perfectly demonstrated based on the example of MongoDB connection settings. Inside a Spring Boot application, we can easily inject it using environment variables. Here's a fragment of application.yml file with URI configuration.

spring:
  data:
    mongodb:
      uri: mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@mongodb/${MONGO_DATABASE}

While username and password are sensitive fields, a database name is not, so we can place it inside the config map.

apiVersion: v1
kind: ConfigMap
metadata:
  name: mongodb
data:
  database-name: microservices

Of course, username and password are defined as secrets.

apiVersion: v1
kind: Secret
metadata:
  name: mongodb
type: Opaque
data:
  database-password: MTIzNDU2
  database-user: cGlvdHI=

To apply the configuration to the Kubernetes cluster, we run the following commands.

$ kubectl apply -f kubernetes/mongodb-configmap.yaml
$ kubectl apply -f kubernetes/mongodb-secret.yaml

After that, we should inject the configuration properties into the application's pods. When defining the container configuration inside the Deployment YAML file, we have to include references to environment variables and secrets, as shown below.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: employee
  labels:
    app: employee
spec:
  replicas: 1
  selector:
    matchLabels:
      app: employee
  template:
    metadata:
      labels:
        app: employee
    spec:
      containers:
      - name: employee
        image: piomin/employee:1.0
        ports:
        - containerPort: 8080
        env:
        - name: MONGO_DATABASE
          valueFrom:
            configMapKeyRef:
              name: mongodb
              key: database-name
        - name: MONGO_USERNAME
          valueFrom:
            secretKeyRef:
              name: mongodb
              key: database-user
        - name: MONGO_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongodb
              key: database-password
2. Building Service Discovery With Kubernetes

We are usually running microservices on Kubernetes using Docker containers. One or more containers are grouped by pods, which are the smallest deployable units created and managed in Kubernetes. A good practice is to run only one container inside a single pod. If you would like to scale up your microservice, you would just have to increase the number of running pods. All running pods that belong to a single microservice are logically grouped with Kubernetes Service. This service may be visible outside the cluster and is able to load balance incoming requests between all running pods. The following service definition groups all pods labeled with the field app equal to employee.

apiVersion: v1
kind: Service
metadata:
  name: employee
  labels:
    app: employee
spec:
  ports:
  - port: 8080
    protocol: TCP
  selector:
    app: employee

Service can be used to access the application outsidethe Kubernetes cluster or for inter-service communication inside a cluster. However, the communication between microservices can be implemented more comfortably with Spring Cloud Kubernetes. First, we need to include the following dependency in the project pom.xml.

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
<version>0.3.0.BUILD-SNAPSHOT</version>
</dependency>

Then we should enable the discovery client for an application, the same as we have always done for discovery in Spring Cloud Netflix Eureka. This allows you to query Kubernetes endpoints (services) by name. This discovery feature is also used by Spring Cloud Kubernetes Ribbon or Zipkin projects to fetch, respectively, the list of the pods defined for a microservice to be load balanced or the Zipkin servers available to send the traces or spans.

@SpringBootApplication
@EnableDiscoveryClient
@EnableMongoRepositories
@EnableSwagger2
public class EmployeeApplication {
 public static void main(String[] args) {
  SpringApplication.run(EmployeeApplication.class, args);
 }
 // ...
}

The last important thing in this section is to guarantee that the Spring application name will be exactly the same as the Kubernetes service name for the application. For the application employee-service, it is employee.

spring:
  application:
    name: employee
3. Building Microservices Using Docker and Deploying on Kubernetes

There is nothing unusual in our sample microservices. We have included some standard Spring dependencies for building REST-based microservices, integrating with MongoDB, and generating API documentation using Swagger2.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

In order to integrate with MongoDB, we should create an interface that extends standard Spring Data CrudRepository.

public interface EmployeeRepository extends CrudRepository {
 List findByDepartmentId(Long departmentId);
 List findByOrganizationId(Long organizationId);
}

The entity class should be annotated with Mongo @Document and a primary key field with @Id.

@Document(collection = "employee")
public class Employee {
 @Id
 private String id;
 private Long organizationId;
 private Long departmentId;
 private String name;
 private int age;
 private String position;
 // ...
}

The repository bean has been injected to the controller class. Here's the full implementation of our REST API inside employee-service.

@RestController
public class EmployeeController {
 private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeController.class);
 @Autowired
 EmployeeRepository repository;
 @PostMapping("/")
 public Employee add(@RequestBody Employee employee) {
  LOGGER.info("Employee add: {}", employee);
  return repository.save(employee);
 }
 @GetMapping("/{id}")
 public Employee findById(@PathVariable("id") String id) {
  LOGGER.info("Employee find: id={}", id);
  return repository.findById(id).get();
 }
 @GetMapping("/")
 public Iterable findAll() {
  LOGGER.info("Employee find");
  return repository.findAll();
 }
 @GetMapping("/department/{departmentId}")
 public List findByDepartment(@PathVariable("departmentId") Long departmentId) {
  LOGGER.info("Employee find: departmentId={}", departmentId);
  return repository.findByDepartmentId(departmentId);
 }
 @GetMapping("/organization/{organizationId}")
 public List findByOrganization(@PathVariable("organizationId") Long organizationId) {
  LOGGER.info("Employee find: organizationId={}", organizationId);
  return repository.findByOrganizationId(organizationId);
 }
}

In order to run our microservices on Kubernetes, we should first build the whole Maven project with the mvn clean install command. Each microservice has a Dockerfile placed in the root directory. Here's the Dockerfile definition for employee-service.

FROM openjdk:8-jre-alpine
ENV APP_FILE employee-service-1.0-SNAPSHOT.jar
ENV APP_HOME /usr/apps
EXPOSE 8080
COPY target/$APP_FILE $APP_HOME/
WORKDIR $APP_HOME
ENTRYPOINT ["sh", "-c"]
CMD ["exec java -jar $APP_FILE"]

Let's build Docker images for all three sample microservices.

$ cd employee-service
$ docker build -t piomin/employee:1.0 .
$ cd department-service
$ docker build -t piomin/department:1.0 .
$ cd organization-service
$ docker build -t piomin/organization:1.0 .

The last step is to deploy Docker containers with applications on Kubernetes. To do that, just execute the commands kubectl apply on YAML configuration files. The sample deployment file for employee-service has been demonstrated in step 1. All required deployment fields are available inside the project repository in the kubernetes directory.

$ kubectl apply -f kubernetes\employee-deployment.yaml
$ kubectl apply -f kubernetes\department-deployment.yaml
$ kubectl apply -f kubernetes\organization-deployment.yaml
4. Communication Between Microservices With Spring Cloud Kubernetes Ribbon

All the microservices are deployed on Kubernetes. Now, it's worth it to discuss some aspects related to inter-service communication. The application employee-service, in contrast to other microservices, did not invoke any other microservices. Let's take a look at other microservices that call the API exposed by employee-service and communicate between each other ( organization-service calls department-service API).

First, we need to include some additional dependencies in the project. We use Spring Cloud Ribbon and OpenFeign. Alternatively, you can also use Spring@LoadBalancedRestTemplate.

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
<version>0.3.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Here's the main class of department-service. It enables Feign client using the @EnableFeignClients annotation. It works the same as with discovery based on Spring Cloud Netflix Eureka. OpenFeign uses Ribbon for client-side load balancing. Spring Cloud Kubernetes Ribbon provides some beans that force Ribbon to communicate with the Kubernetes API through Fabric8 KubernetesClient.

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableMongoRepositories
@EnableSwagger2
public class DepartmentApplication {
 public static void main(String[] args) {
  SpringApplication.run(DepartmentApplication.class, args);
 }
 // ...
}

Here's the implementation of Feign client for calling the method exposed by employee-service.

@FeignClient(name = "employee")
public interface EmployeeClient {
 @GetMapping("/department/{departmentId}")
 List findByDepartment(@PathVariable("departmentId") String departmentId);
}

Finally, we have to inject Feign client's beans into the REST controller. Now, we may call the method defined inside EmployeeClient, which is equivalent to calling REST endpoints.

@RestController
public class DepartmentController {
 private static final Logger LOGGER = LoggerFactory.getLogger(DepartmentController.class);
 @Autowired
 DepartmentRepository repository;
 @Autowired
 EmployeeClient employeeClient;
 // ...
 @GetMapping("/organization/{organizationId}/with-employees")
 public List findByOrganizationWithEmployees(@PathVariable("organizationId") Long organizationId) {
  LOGGER.info("Department find: organizationId={}", organizationId);
  List departments = repository.findByOrganizationId(organizationId);
  departments.forEach(d -> d.setEmployees(employeeClient.findByDepartment(d.getId())));
  return departments;
 }
}
5. Building API Gateway Using Kubernetes Ingress

Ingress is a collection of rules that allow incoming requests to reach the downstream services. In our microservices architecture, ingress is playing the role of an API gateway. To create it, we should first prepare a YAML description file. The descriptor file should contain the hostname under which the gateway will be available and mapping rules to the downstream services.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: gateway-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  backend:
    serviceName: default-http-backend
    servicePort: 80
  rules:
  - host: microservices.info
    http:
      paths:
      - path: /employee
        backend:
          serviceName: employee
          servicePort: 8080
      - path: /department
        backend:
          serviceName: department
          servicePort: 8080
      - path: /organization
        backend:
          serviceName: organization
          servicePort: 8080

You have to execute the following command to apply the configuration above to the Kubernetes cluster.

$ kubectl apply -f kubernetes\ingress.yaml

To test this solution locally, we have to insert the mapping between the IP address and hostname set in the ingress definition inside the hosts file, as shown below. After that, we can test services through ingress using defined hostname just like that: http://microservices.info/employee.

192.168.99.100 microservices.info

You can check the details of the created ingress just by executing the command kubectl describe ing gateway-ingress.

6. Enabling API Specification on the Gateway Using Swagger2

What if we would like to expose a single Swagger documentation for all microservices deployed on Kubernetes? Well, here things are getting complicated... We can run a container with Swagger UI, and map all paths exposed by the ingress manually, but it is not a good solution...

In that case, we can use Spring Cloud Kubernetes Ribbon one more time, this time together with Spring Cloud Netflix Zuul. Zuul will act as a gateway only for serving the Swagger API.
Here's the list of dependencies used in my gateway-service project.

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
<version>0.3.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
<version>0.3.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>

Kubernetes discovery client will detect all services exposed on the cluster. We would like to display documentation only for our three microservices. That's why I defined the following routes for Zuul.

zuul:
  routes:
    department:
      path: /department/**
    employee:
      path: /employee/**
    organization:
      path: /organization/**

Now we can use the ZuulProperties bean to get the routes' addresses from Kubernetes discovery and configure them as Swagger resources, as shown below.

@Configuration
public class GatewayApi {
 @Autowired
 ZuulProperties properties;
 @Primary
 @Bean
 public SwaggerResourcesProvider swaggerResourcesProvider() {
  return () -> {
   List resources = new ArrayList();
   properties.getRoutes().values().stream()
   .forEach(route -> resources.add(createResource(route.getId(), "2.0")));
   return resources;
  };
 }
 private SwaggerResource createResource(String location, String version) {
  SwaggerResource swaggerResource = new SwaggerResource();
  swaggerResource.setName(location);
  swaggerResource.setLocation("/" + location + "/v2/api-docs");
  swaggerResource.setSwaggerVersion(version);
  return swaggerResource;
 }
}

The application gateway-service should be deployed on the cluster the same as the other applications. You can see the list of running services by executing the command kubectl get svc. Swagger documentation is available under the address http://192.168.99.100:31237/swagger-ui.html.

Learn More

Thanks for reading !

Originally published by Piotr Mińkowski at dzone.com