Wednesday, April 22, 2020

Containerizing your apps 3 - Multiple service integration with docker compose

The previous post, explained how to run a simple REST service with the Docker environment. Deploying a single entity in the Docker environment is not the purpose of using docker. Docker is a container orchestration platform that can address auto-scalable, fault-tolerant features that cannot be addressed in monolithic models. Bellow diagram includes spring boot based microservice components including Spring cloud gateway, eureka service, config service, and app service. Docker will expose 8080 port to external connectivity so the client can send requests on this port.


Cloud components in bellow diagram

1. Docker - Container orchestration framework
2. Host - Machine that installed docker
3. Eureka service - Keeps service information deployed services including IP, Ports, scaling information
4. Config service - Connects with config repositories
5. App service - This has custom logic that serves external requests came from cloud gateway
6. Spring Cloud Gateway - This receives requests from the host's client app and route it and load balance to each app service. This service will expose host system via 8080 port


                                       
                                         Figure 1 - Scaled microservice with API Gateway

In order to set up and deploy the above solution, we required setup the Docker compose file.


What is the Docker compose?

1. It is a YAML file
2. Includes a group of Docker files of each service
3. Has service dependencies ( service A depends on Eureka service and Config service )
4. Has startup order ( service A will start after Eureka and Config service )
5. Has an internal network ( How each service links )
6. Has scaleling port ranges of each service


List of files to setup above services

Spring BOOT Jar Files 

1. config-service.jar

Code

@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {

public static void main(String[] args) {

SpringApplication.run(ConfigApplication.class, args);
}

}

Major Dependencies

spring-boot-starter-web
spring-boot-starter-actuator
spring-cloud-config-server


2. eureka-service.jar

Code

@SpringBootApplication
@EnableEurekaServer
public class EurekaApp {

public static void main(String[] args) {

SpringApplication.run(EurekaApp.class, args);
}

}

Major Dependencies

spring-boot-starter-web
spring-boot-starter-actuator
spring-cloud-netflix-eureka-server



3. app-service.jar

Code ( It has the main method and rest endpoint return the node's port. So we can check load balancing)

@SpringBootApplication
@EnableDiscoveryClient
@EnableAutoConfiguration
public class AppService {

public static void main(String[] args) {

SpringApplication.run(AppService.class, args);
}

}

@Controller
@RestController
@ControllerAdvice
public class RestService{

@GetMapping(value = "/testAPI", produces = "application/json")
public ResponseEntity testAPI(@RequestParam Map receiptParams,HttpServletRequest request) {
CommonResponse response = new CommonResponse();
response.setStatusCode("1");
response.setStatusDesc("Success. Request received. Node details : port="+env.getProperty("local.server.port"));
ResponseEntity responseEntity = new ResponseEntity<>(response, HttpStatus.OK);
return responseEntity;
}

}

@Data
public class CommonResponse {

private String statusCode;
private String statusDesc;

}


Major Dependencies

spring-boot-starter-web
spring-cloud-starter-netflix-eureka-client
spring-boot-starter-actuator
spring-cloud-starter-config


4. spring-gateway-client-service.jar


Code( This has router and load balancer, lb is for load balancing to app-service which is mentioned in the docker compose )

@SpringBootApplication
@RestController
@EnableDiscoveryClient
@EnableAutoConfiguration
public class Application {

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

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes().route(p -> p.path("/testAPI/**").filters(f -> f.stripPrefix(1)).uri("lb://app-service/")).build();

}

}

Major Dependencies

spring-cloud-starter-gateway
spring-cloud-starter-netflix-eureka-client
spring-cloud-starter-config


Docker Files

1. Dockerfile.configservice

FROM openjdk:13-alpine
EXPOSE 8888
ADD config-service.jar ./config-service.jar
ENTRYPOINT ["java","-jar","-Duser.timezone=GMT+0530","config-service.jar"]

2. Dockerfile.eurekaservice

FROM openjdk:13-alpine
EXPOSE 8080
RUN apk --no-cache add netcat-openbsd
COPY check-entrypoint-eureka.sh ./check-entrypoint-eureka.sh
ADD eureka-service.jar ./eureka-service.jar
RUN chmod 755 check-entrypoint-eureka.sh

We include check-entrypoint-eureka.sh checks whether config service is up and running. Eureka service will be started

3. Dockerfile.appservice

FROM openjdk:13-alpine
EXPOSE 8080
RUN apk --no-cache add netcat-openbsd
COPY check-entrypoint-app-service.sh ./check-entrypoint-app-service.sh
ADD app-service.jar ./app-service.jar
RUN chmod 755 check-entrypoint-app-service.sh

We include check-entrypoint-app-service.sh checks whether config service and eureka services are up and running. Then only app-service will be started

4. Dockerfile.gatewayclient

FROM openjdk:13-alpine
EXPOSE 8090
RUN apk --no-cache add netcat-openbsd
COPY check-entrypoint-gatewayclient.sh ./check-entrypoint-gatewayclient.sh
ADD spring-gateway-client-service.jar ./spring-gateway-client-service.jar
RUN chmod 755 check-entrypoint-gatewayclient.sh

We include check-entrypoint-gatewayclient.sh checks whether config service and eureka service, and app services are up and running. Then gateway client will be started


Linux script files ( This is to check dependent services are up and running )

These Linux scripts run from the container itself. Container build by docker container knows how to access other services by service name and port


check-entrypoint-eureka.sh

#!/bin/sh
while ! nc -z config-service 8888 ; do
    echo "<------------ span="" style="background-color: #ffd966;">Euerka service is waiting for upcoming Config service
---------------->"    sleep 2
done
java -jar eureka-service.jar


check-entrypoint-app-service.sh

#!/bin/sh
while ! nc -z config-service 8888 ; do
    echo "<------------ span="" style="background-color: #ffd966;">App service is waiting for upcoming Config service
---------------->"    sleep 2
done
while ! nc -z eureka-service 8761 ; do
    echo "<------------ span="" style="background-color: #ffd966;">App service is waiting for upcoming Eureka service
---------------->"    sleep 2
done
java -jar app-service.jar


check-entrypoint-gatewayclient.sh

#!/bin/sh
while ! nc -z config-service 8888 ; do
    echo "<------------ span="" style="background-color: #ffd966;">App service is waiting for upcoming Config service
---------------->"    sleep 2
done
while ! nc -z eureka-service 8761 ; do
    echo "<------------ span="" style="background-color: #ffd966;">App service is waiting for upcoming Eureka service
---------------->"    sleep 2
done
while ! nc -z app-service 8080 ; do
    echo "<------------ span="" style="background-color: #ffd966;">App service is waiting for upcoming App service
---------------->"    sleep 2
done

java -jar spring-gateway-client-service.jar



Docker Compose YML


version: '2'
services:
    config-service:
        container_name: config-service
        build:
            context: .
            dockerfile: Dockerfile.configservice
        image: config-service:latest
        expose:
            - 8888
        ports:
            - 8888:8888
        networks:
            - spring-cloud-network
        volumes:
            - spring-cloud-config-repo:/var/lib/spring-cloud/config-repo
        logging:
            driver: json-file
    eureka-service:
        container_name: eureka-service
        build:
            context: .
            dockerfile: Dockerfile.eurekaservice
        image: eureka-service:latest
        expose:
            - 8761
        ports:
            - 8761:8761
        networks:
            - spring-cloud-network
        links:
            - config-service:config-service
        depends_on:
            - config-service
        command: './check-entrypoint-eureka.sh'
        logging:
            driver: json-file
    app-service:
        build:
            context: .
            dockerfile: Dockerfile.appservice
        image: app-service:latest

        ports:
            - "8080"
        networks:
            - spring-cloud-network
        links:
            - config-service:config-service
            - eureka-service:eureka-service
        depends_on:
            - config-service
            - eureka-service
        command: './check-entrypoint-app-service.sh'
        logging:
            driver: json-file

    spring-gateway-client-service:
        build:
            context: .
            dockerfile: Dockerfile.gatewayclient
        image: spring-gateway-client-service:latest

        expose:
            - 8090
        ports:
            - 8090:8090
        networks:
            - spring-cloud-network
        links:
            - eureka-service:eureka-service
            - app-service:app-service
        depends_on:
            - eureka-service
            - app-service
        command: './check-entrypoint-gatewayclient.sh'
        logging:
            driver: json-file
networks:
    spring-cloud-network:
        driver: bridge
volumes:
    spring-cloud-config-repo:
        external: true



How to build docker compose

command: sudo docker-compose build

output :
 
Building config-service
Step 1/4 : FROM openjdk:13-alpine
 ---> c4b0433a01ac
Step 2/4 : EXPOSE 8888
 ---> Using cache
 ---> 326abacc404e
Step 3/4 : ADD config-service.jar ./config-service.jar
 ---> Using cache
 ---> 3db14ff99098
Step 4/4 : ENTRYPOINT ["java","-jar","-Duser.timezone=GMT+0530","config-service.jar"]
 ---> Using cache
 ---> c566948166a0
Successfully built c566948166a0
Successfully tagged config-service:latest
Building eureka-service
Step 1/6 : FROM openjdk:13-alpine
 ---> c4b0433a01ac
Step 2/6 : EXPOSE 8080
 ---> Using cache
 ---> e638ee32b31e
Step 3/6 : RUN apk --no-cache add netcat-openbsd
 ---> Using cache
 ---> 879e12e2b629
Step 4/6 : COPY check-entrypoint-eureka.sh ./check-entrypoint-eureka.sh
 ---> Using cache
 ---> dfcd25b502ec
Step 5/6 : ADD eureka-service.jar ./eureka-service.jar
 ---> Using cache
 ---> 44c826f61f35
Step 6/6 : RUN chmod 755 check-entrypoint-eureka.sh
 ---> Using cache
 ---> 660062ba9463
Successfully built 660062ba9463
Successfully tagged eureka-service:latest
Building app-service
Step 1/6 : FROM openjdk:13-alpine
 ---> c4b0433a01ac
Step 2/6 : EXPOSE 8080
 ---> Using cache
 ---> e638ee32b31e
Step 3/6 : RUN apk --no-cache add netcat-openbsd
 ---> Using cache
 ---> 879e12e2b629
Step 4/6 : COPY check-entrypoint-app-service.sh ./check-entrypoint-app-service.sh
 ---> Using cache
 ---> 638c51bfdba2
Step 5/6 : ADD app-service.jar ./app-service.jar
 ---> Using cache
 ---> 1f9710fefe1e
Step 6/6 : RUN chmod 755 check-entrypoint-app-service.sh
 ---> Using cache
 ---> 0557299d4fcf
Successfully built 0557299d4fcf
Successfully tagged app-service:latest
Building spring-gateway-client-service
Step 1/6 : FROM openjdk:13-alpine
 ---> c4b0433a01ac
Step 2/6 : EXPOSE 8090
 ---> Using cache
 ---> eab248ba6443
Step 3/6 : RUN apk --no-cache add netcat-openbsd
 ---> Using cache
 ---> 72051b97ed40
Step 4/6 : COPY check-entrypoint-gatewayclient.sh ./check-entrypoint-gatewayclient.sh
 ---> Using cache
 ---> 00dfde72cadf
Step 5/6 : ADD spring-gateway-client-service.jar ./spring-gateway-client-service.jar
 ---> Using cache
 ---> 2fb78b235394
Step 6/6 : RUN chmod 755 check-entrypoint-gatewayclient.sh
 ---> Using cache
 ---> c7126edc4109
Successfully built c7126edc4109
Successfully tagged spring-gateway-client-service:latest


How to start services with scaling to app-service

command : sudo docker-compose up -d --scale app-service=4 --remove-orphans

output: ( You can see services are running based on dependency first approach, Green colored areas show how scale nodes are running )

Starting config-service ...
Starting config-service ... done
Starting eureka-service ...
Starting eureka-service ... done
Starting tmp_app-service_1 ...
Starting tmp_app-service_2 ...
Starting tmp_app-service_3 ...
Starting tmp_app-service_4 ...
Starting tmp_app-service_1 ... done
Starting tmp_app-service_2 ... done
Starting tmp_app-service_3 ... done
Starting tmp_app-service_4 ... done
Starting tmp_spring-gateway-client-service_1 ...
Starting tmp_spring-gateway-client-service_1 ... done


Eureka service registration

















You can see there are 4 nodes are up and running from app-service. It shows the port is 8080 for all nodes. Docker can differentiate these nodes as separate container ids are assigned.


Sending multiple requests

command:

curl http://localhost:8090/testAPI/get/
curl http://localhost:8090/testAPI/get/
curl http://localhost:8090/testAPI/get/
curl http://localhost:8090/testAPI/get/
curl http://localhost:8090/testAPI/get/
curl http://localhost:8090/testAPI/get/

outputs : ( Load balancing works )

Success. Request received. Node details : port=8080
Success. Request received. Node details : port=8081
Success. Request received. Node details : port=8082
Success. Request received. Node details : port=8083
Success. Request received. Node details : port=8080
Success. Request received. Node details : port=8081

How to stop services 


command: sudo docker-compose down

output : ( Shutting down started from service has no external dependency, config-service is providing dependency for all modules, so it will be shut down in the last stage  )

Stopping tmp_spring-gateway-client-service_1 ... done
Stopping tmp_app-service_4                   ... done
Stopping tmp_app-service_3                   ... done
Stopping tmp_app-service_2                   ... done
Stopping tmp_app-service_1                   ... done
Stopping eureka-service                      ... done
Stopping config-service                      ... done
Removing tmp_spring-gateway-client-service_1 ... done
Removing tmp_app-service_4                   ... done
Removing tmp_app-service_3                   ... done
Removing tmp_app-service_2                   ... done
Removing tmp_app-service_1                   ... done
Removing eureka-service                      ... done
Removing config-service                      ... done
Removing network tmp_spring-cloud-network