Quick Guide to Microservices with Kubernetes, Spring Boot 2 and Docker

Quick Guide to Microservices with Kubernetes, Spring Boot 2 and Docker

Here’s the next article in a series of “Quick Guide to…”. This time we will discuss and run examples of Spring Boot microservices on Kubernetes. The structure of that article will be quite similar to this one Quick Guide to Microservices with Spring Boot 2.0, Eureka and Spring Cloud, as they are describing the same aspects of applications development. I’m going to focus on showing you the differences and similarities in development between Spring Cloud and Kubernetes.

The topics covered in this article are:

  • Using Spring Boot 2 in cloud-native development
  • Providing service discovery for all microservices using 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 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 microservices environments. 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 (https://github.com/spring-cloud/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 the implementation of discovery client, inter-service communication with 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 already mentioned article about microservices on Spring Cloud. There are three independent applications (employee-service, department-service, organization-service), which communicate between each other through REST API. These Spring Boot microservices use some build-in mechanisms provided by Kubernetes: config maps and secrets for distributed configuration, etcd for service discovery, and ingresses for API gateway.

kubernetes-microservices-spring-boot-arch

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 Spring Boot microservices on Kubernetes presented in this article is available on GitHub in repository https://github.com/piomin/sample-spring-microservices-kubernetes.git.

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 instruction for Minishift may be found there: 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 would be applicable for both of them
  • Spring Cloud Kubernetes requires access to Kubernetes API in order to be able to retrieve a list of addresses of 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 addon, then login as a cluster-admin and grant required permissions.

$ minishift addons enable admin-user
$ oc login -u system:admin
$ oc policy add-role-to-user cluster-reader system:serviceaccount:myproject:default
  • All our sample microservices use MongoDB as a backend store. So, you should first run an instance of this database on your node. With Minishift it is quite simple, as you can use predefined templates just by selecting service Mongo on the Catalog list. With Kubernetes, the task is more difficult. You have to prepare deployment configuration files by yourself and apply it to the cluster. All the configuration files are available under kubernetes directory inside sample Git repository. To apply the following YAML definition to the cluster you should execute command kubectl apply -f kubernetes\mongo-deployment.yaml. After it, Mongo database would be available under the name mongodb inside Kubernetes cluster.
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 configuration with Config Maps and Secrets

When using Spring Cloud the most obvious choice for realizing distributed configuration in your system is Spring Cloud Config. With Kubernetes, you can use the 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. Any usage of both these Kubernetes objects can be perfectly demonstrated based on the example of MongoDB connection settings. Inside the 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 or password are sensitive fields, a database name is not. So we can place it inside a 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 it we should inject the configuration properties into application’s pods. When defining container configuration inside 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 Spring Boot microservices discovery with Kubernetes

We usually run 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 a number of running pods. All running pods that belong to a single microservice are logically grouped by 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 labelled with field app equaled 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 for accessing applications outside a 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 to 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 a discovery client for an application – the same as we have always done for discovery Spring Cloud Netflix Eureka. This allows you to query Kubernetes endpoints (services) by name. This discovery feature is also used by the 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 Spring Boot microservices name would be exactly the same as the Kubernetes service name for the application. For application employee-service it is employee.

spring:
  application:
    name: employee

3. Building microservice 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 interface that extends standard Spring Data CrudRepository.

public interface EmployeeRepository extends CrudRepository<Employee, String> {
   
   List<Employee> findByDepartmentId(Long departmentId);
   List<Employee> findByOrganizationId(Long organizationId);
   
}

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<Employee> findAll() {
      LOGGER.info("Employee find");
      return repository.findAll();
   }
   
   @GetMapping("/department/{departmentId}")
   public List<Employee> findByDepartment(@PathVariable("departmentId") Long departmentId) {
      LOGGER.info("Employee find: departmentId={}", departmentId);
      return repository.findByDepartmentId(departmentId);
   }
   
   @GetMapping("/organization/{organizationId}")
   public List<Employee> 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 mvn clean install command. Each microservice has Dockerfile placed in the root directory. Here’s 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 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 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 microservice are deployed on Kubernetes. Now, it’s worth discussing some aspects related to inter-service communication. Application employee-service in contrast to other microservices did not invoke any other microservices. Let’s take a look at other microservices that call API exposed by employee-service and communicate between each other (organization-service calls department-service API).
First we need to include some additional dependencies to the project. We use Spring Cloud Ribbon and OpenFeign. Alternatively you can also use Spring @LoadBalanced RestTemplate.

<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 @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 forces Ribbon to communicate with 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 implementation of Feign client for calling method exposed by employee-service.

@FeignClient(name = "employee")
public interface EmployeeClient {

   @GetMapping("/department/{departmentId}")
   List<Employee> findByDepartment(@PathVariable("departmentId") String departmentId);
   
}

Finally, we have to inject Feign client’s beans to 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<Department> findByOrganizationWithEmployees(@PathVariable("organizationId") Long organizationId) {
      LOGGER.info("Department find: organizationId={}", organizationId);
      List<Department> departments = repository.findByOrganizationId(organizationId);
      departments.forEach(d -> d.setEmployees(employeeClient.findByDepartment(d.getId())));
      return departments;
   }
   
}

5. Building API gateway using Kubernetes Ingress

An Ingress is a collection of rules that allow incoming requests to reach the downstream services. In our microservices architecture ingress is playing a 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 visible above to the Kubernetes cluster.

$ kubectl apply -f kubernetes\ingress.yaml

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

192.168.99.100 microservices.info

You can check the details of created ingress just by executing command kubectl describe ing gateway-ingress.
kubernetes-microservices-spring-boot-2

6. Enabling API specification on gateway using Swagger2

Ok, what if we would like to expose single swagger documentation for all microservices deployed on Kubernetes? Well, here the things are getting complicated… We can run a container with Swagger UI, and map all paths exposed by the ingress manually, but it is rather 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 gateway only for serving 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>

A 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 ZuulProperties bean to get route’s 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<SwaggerResource> 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;
   }

}

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

Conclusion

I’m actually rooting for the Spring Cloud Kubernetes project, which is still at the incubation stage. Kubernetes’ popularity as a platform is rapidly growing during the last months, but it still has some weaknesses. One of them is inter-service communication. Kubernetes doesn’t give us many mechanisms out-of-the-box, which allows us to configure more advanced rules. This is a reason for creating frameworks for service mesh on Kubernetes like Istio or Linkerd. While these projects are still relatively new solutions, Spring Cloud is a stable, opinionated framework. Why not to provide service discovery, inter-service communication or load balancing? Thanks to Spring Cloud Kubernetes it is possible.

28 COMMENTS

comments user
Microservices Training

This is really great informative blog. Keep sharing.

    comments user
    Piotr Mińkowski

    Thanks 🙂

comments user
Jonathan

This sample Spring Cloud K8S integration is no long available. Is there a problem with it? https://github.com/piomin/sample-spring-microservices-kubernetes.git.

    comments user
    Piotr Mińkowski

    No there is no problem since it has been moved to Spring repository

comments user
Marek

Out of curiosity – is there any alternative to suggested service-discovery, apart from RBAC changes?

    comments user
    Piotr Mińkowski

    Yes, there are some alternatives. For example , you can use Consul or Spring Cloud Eureka as a discovery server.

comments user
syrcold

Firstly, Thank you in advance for your sharing. I have a small question
I got error when deploy gateway-service :
“`
io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: GET at: https://kubernetes.default.svc/api/v1/namespaces/default/services. Message: Forbidden!Configured service account doesn’t have access. Service account may have been revoked. services is forbidden: User “system:serviceaccount:default:default” cannot list services in the namespace “default”.
“`
Although I’ve enabled ingress in minikube:
minikube addons enable ingress

comments user
Milenko Jovanovic

Hi Piotr,

I followed your blog and when I start pods with department / employee service I am getting following error (Did you maybe encounter something similar):

2019-02-07 16:08:38 ERROR [-,,,] Application run failed
java.lang.AbstractMethodError: null
at org.springframework.core.OrderComparator.findOrder(OrderComparator.java:142) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.annotation.AnnotationAwareOrderComparator.findOrder(AnnotationAwareOrderComparator.java:65) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:125) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:113) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.OrderComparator.doCompare(OrderComparator.java:82) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.OrderComparator.compare(OrderComparator.java:68) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at java.util.TimSort.countRunAndMakeAscending(TimSort.java:360) ~[na:1.8.0_191]
at java.util.TimSort.sort(TimSort.java:220) ~[na:1.8.0_191]
at java.util.Arrays.sort(Arrays.java:1512) ~[na:1.8.0_191]
at java.util.ArrayList.sort(ArrayList.java:1462) ~[na:1.8.0_191]
at org.springframework.boot.SpringApplication.asUnmodifiableOrderedSet(SpringApplication.java:1313) [spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
at org.springframework.boot.SpringApplication.getListeners(SpringApplication.java:1222) [spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
at org.springframework.boot.context.event.EventPublishingRunListener.contextLoaded(EventPublishingRunListener.java:85) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
at org.springframework.boot.SpringApplicationRunListeners.contextLoaded(SpringApplicationRunListeners.java:66) ~[spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:384) [spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:325) [spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234) [spring-boot-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
at pl.piomin.services.employee.EmployeeApplication.main(EmployeeApplication.java:28) [classes!/:1.0-SNAPSHOT]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) [employee-service-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) [employee-service-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:50) [employee-service-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51) [employee-service-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]

    comments user
    Piotr Mińkowski

    Hi, how didyou run it? Which version of kubernetes you are using?

    comments user
    Kevin Kieva

    Im getting same problem, how did you fix this?

      comments user
      Piotr Mińkowski

      I didn’t fix it, because I haven’t got such an issue

comments user
Hongliang Wang

Great article!

The source repo link is wrong to Spring instead of k8s.

comments user
Kevin Kieva

I get same problem when deploying pretty much all the services,:

2019-03-01 08:25:47.001 INFO [-,,,] 1 — [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f4a7090: startup date [Fri Mar 01 08:25:46 GMT 2019]; root of context hierarchy
2019-03-01 08:25:47.351 INFO [-,,,] 1 — [ main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 ‘javax.inject.Inject’ annotation found and supported for autowiring
2019-03-01 08:25:47.431 INFO [-,,,] 1 — [ main] trationDelegate$BeanPostProcessorChecker : Bean ‘org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration’ of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$b5dc3fbb] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
. ____ _ __ _ _
/\\ / ___’_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | ‘_ | ‘_| | ‘_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
‘ |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.0.RELEASE)
2019-03-01 08:25:47 INFO [-,,,] The following profiles are active: kubernetes
2019-03-01 08:25:47 ERROR [-,,,] Application run failed
java.lang.AbstractMethodError: null
at org.springframework.core.OrderComparator.findOrder(OrderComparator.java:142) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.annotation.AnnotationAwareOrderComparator.findOrder(AnnotationAwareOrderComparator.java:65) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:125) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:113) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.OrderComparator.doCompare(OrderComparator.java:82) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at org.springframework.core.OrderComparator.compare(OrderComparator.java:68) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
at java.util.TimSort.countRunAndMakeAscending(TimSort.java:360) ~[na:1.8.0_191]
at java.util.TimSort.sort(TimSort.java:220) ~[na:1.8.0_191]

comments user
gfalco77

Hello
Do we still need to use the netflix api for ribbon and gateway? Or could we use spring cloud gateway and spring cloud load balancer?
Thanks

    comments user
    Piotr Mińkowski

    Hello,
    Although Spring Cloud Ribbon has been moved to the maintenance mode in the end of past year it is still the default client-side load balancer for all Spring Cloud projects. The development of Spring Cloud Loadbalancer is in progress, but I don’t think it has been finished yet. There is no any article about that, except my post on blog: https://piotrminkowski.wordpress.com/2019/04/05/the-future-of-spring-cloud-microservices-after-netflix-era/. If you need API gateway you can always use Kubernetes Ingress. In case you need Swagger API documentation you can probably use Spring Cloud Gateway. The only problem with that during writing this article was the lack of support for SpringFox Swagger project since Spring Cloud Gateway is based on asynchronous Netty server. However, I have already heard that this support has been implemented (I didn’t check it by myself).

comments user
SARVESH

This is really a nice and informative article. I tried this example with Google Kubernetes Engine. When I am deploying mongodb on it, it is getting failed.
Error logs in mongo pod:
E QUERY [js] Error: couldn’t add user: Error preflighting normalization: U_STRINGPREP_PROHIBITED_ERROR :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
DB.prototype.createUser@src/mongo/shell/db.js:1491:15
@(shell):1:1
Can you help me with this?

    comments user
    Piotr Mińkowski

    Hello, I didn’t try to deploy on GKE. But it’s the standard Mongo template

comments user
Prashanth Rai

Great article. Thanks a lot Piotr for putting this together making it easy for us to follow

comments user
djibril diallo

Un excellent article

comments user
Varun Gupta (@vrungupta)

I think the kubernetes directory is missing from repo. I couldn’t find it on github and had to create it on my own.

comments user
Mandar Lad

Hi Piotr,

I could make the pods, services and deployments running on my Windows 10 (enterprise) laptop. And I had added the IP – DNS mapping to C:\Windows\System32\drivers\etc\hosts. However for some reason it is not working as expected. So I have a couple of questions as below –

1> Do we need to set up this IP address and DNS mapping in the hosts file? If yes, which IP address should I map to “microservices.info” in the hosts file. Currently I mapped the one which is returned by IP Config.

2> I am using the Kubernetes engine that comes packed with Docker for Desktop (windows installer). I could not run Minikube (though I could install it ), because my laptop does not have sufficient memory. Can your project be deployed and tested, with this constraint ?

Sorry about my ignorance. Please help. Thank you!

Mandar

    comments user
    Piotr Mińkowski

    Hi,
    1. You need to map your VM with Kubernetes ip address. I don’t know what’s ip address when you use Docker Desktop (maybe localhost?), but for minikube it is returned by commany minikube ip.
    2. Generally the applications require around 300-400 MB to start. There are 3 apps and Mongo, what gives us around 1.2 GB. Assuming it you can start minikube with minimum RAM requirement – 2gb. Of course you should also be able to run them on Kubernetes in Docker Desktop

Leave a Reply