Scale-Up and Load-Balance a Spring-Boot MicroService With Docker-Compose, Persist Data and Enable Netflix Tools Like EUREKA and ZUUL API Gateway (PART 2)

Vutlhari Ndlovhu
9 min readNov 28, 2020

Introduction

Finally the second part of an amazing article where we put all the puzzles together. We are going to make sure by the end of this article, all you see in the image above is running locally on your computer. We will start by changing the property file of Customer service to utilize the Postgresql database. Then customize logs and where to be stored. Then containerize ZUUL, EUREKA, and Customer service. Then run them in a docker-compose network. And finally, scale up the Customer service container instance.

Prerequisite

Please go through part 1(link below) of this article to fully understand.

Customer Service Changing from H2 database to Postgresql Database

The first step, clone the Customer Service from Github with the url below

git clone https://github.com/vhutie/superdev-crm-customer.git

Also, clone Eureka

git clone https://github.com/vhutie/superdev-eureka-server.git

And finally, clone ZUUL

git clone https://github.com/vhutie/superdev-zuul.git

Then change the application.properties file of Customer Service to look exactly as below to accommodate the Postgresql connection settings.

# Data Source Propertiesspring.jpa.database=POSTGRESQLspring.datasource.platform=postgresspring.datasource.url=jdbc:postgresql://localhost:5432/superdevdbspring.datasource.username=superdevuserspring.datasource.password=P@ssword1spring.jpa.show-sql=truespring.jpa.generate-ddl=truespring.jpa.hibernate.ddl-auto=create-dropspring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true# JPA Propertiesspring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=falsespring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImplspring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy#EDIT TO COMPUTER IP ADDRESSeureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://localhost:8761/eureka}spring.application.name=superdev-crm-customer

Then on the pom.xml file, we need to replace the H2 database dependency with Postgresql dependency.

<!-- Add postgres dependency for db connection --><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency>

Then to test, we need to start up the database image we created in the article below

Then start up Eureka Service, followed by the Customer Service, and finish off by starting up the ZUUL Service. After all, services are up and running, then do a post customer and get customer via Postman. All the steps are included in Part 1 of this article.

Once you have done the testing, you can stop all running instances of all three services.

Containerize all MicroServices

In this section, we will containerize all our microservices and make sure they run as separate docker container instances.

The first step is to add a Dockerfile on each microservice. Create a Dockerfile on the same level as pom.xml on the parent folder of each project and add the same contents as below on each.

FROM openjdk:8-jdk-alpineVOLUME /tmpADD target/*.jar app.jarENV JAVA_OPTS "-Xmx192m -Xms192m -Djava.security.egd=file:///dev/./urandom -XX:+HeapDumpOnOutOfMemoryError "ENV TZ Africa/Johannesburg# NOT USE YET -XX:+UseG1GCENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]
Dockerfile included

The Dockerfile will be used as instructions when we build each microservice’s image via maven. The instructions in the Dockerfile are simple, we will be building our images based on the openjdk image and once the container is up we will run the jar we included when building the image.

The second step is to change the pom.xml file and enable maven to assist in building the image on each project. On each project(Customer, Eureka and ZUUL) replace the <build> tags with the below

<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>com.spotify</groupId><artifactId>dockerfile-maven-plugin</artifactId><version>1.4.9</version><executions><execution><id>default</id><goals><goal>build</goal><goal>push</goal></goals></execution></executions><configuration><repository>superdev/${project.artifactId}</repository><tag>latest</tag><buildArgs><JAR_FILE>${project.build.finalName}.jar</JAR_FILE></buildArgs></configuration></plugin></plugins></build>

Then the last step, we need to change the application.properties files of Customer and ZUUL to replace localhost with your computer’s IP address. Reason being when they run as microservices, they will not be able to access the other microservices, Eureka, or database as localhost but Global IP address. When we get to the docker-compose section we will change this again to a hostname instead.

Customer application.properties to look as below(DONT FORGET TO EDIT IP ADDRESS)

# Data Source Propertiesspring.jpa.database=POSTGRESQLspring.datasource.platform=postgres#EDIT TO COMPUTER IP ADDRESSspring.datasource.url=jdbc:postgresql://192.XXX.XXX.1:5432/superdevdbspring.datasource.username=superdevuserspring.datasource.password=P@ssword1spring.jpa.show-sql=truespring.jpa.generate-ddl=truespring.jpa.hibernate.ddl-auto=create-dropspring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true# JPA Propertiesspring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=falsespring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImplspring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy#EDIT TO COMPUTER IP ADDRESSeureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://192.XXX.XXX.1:8761/eureka}spring.application.name=superdev-crm-customer

And ZUUL’s application.properties to look as below. (DONT FORGET TO EDIT IP ADDRESS)

server.port=8081#EDIT TO COMPUTER IP ADDRESSeureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://192.XXX.XXX.1:8761/eureka}spring.application.name=superdev-zuul#EDIT TO COMPUTER IP ADDRESSzuul.routes.customer.url=http://192.XXX.XXX.1:8080ribbon.eureka.enabled=false

Building the Microservices’ docker images

For ZUUL- Go to your terminal/command prompt and navigate to the parent folder of the ZUUL project, make sure the folder has the pom.xml file, or else the following command won’t work. Provided you have installed maven on your computer run the below command

mvn clean install

You should see the results below

Then run docker images command and should see superdev/superdev-zuul:latest as part of the image list

docker images

For EUREKA- Go to your terminal/command prompt and navigate to the parent folder of the EUREKA project, make sure the folder has the pom.xml file, or else the following command won’t work. Provided you have installed maven on your computer run the below command

mvn clean install

You should see the results below

Then run docker images command and should see superdev/superdev-eureka-server:latest as part of the image list

docker images

For Customer Service- Go to your terminal/command prompt and navigate to the parent folder of the Customer project, make sure the folder has the pom.xml file, or else the following command won’t work. Provided you have installed maven on your computer run the below command

mvn clean install

You should see the results below

Then run docker images command and should see superdev/superdev-crm-customer:latest as part of the image list

docker images

Running the Microservice Docker Images

We will start by silently running the Eureka Image as below and port forward 8761

docker run -d --rm -p 8761:8761 superdev/superdev-eureka-server:latest &

Then followed by Customer Service Image as below and port forward 8080

docker run -d --rm -p 8080:8080 superdev/superdev-crm-customer:latest &

Then finally silently run ZUUL image and port forward 8081

docker run -d --rm -p 8081:8081 superdev/superdev-zuul:latest &

Check if all 4(including database) images are up and running

docker ps -a

And you should get results similar to the below image

After all container instances are up and running, then do a post customer and get customer via Postman. All the steps are included in Part 1 of this article.

Running images via docker-compose and scaling up

Step 1 — Install docker-compose following the steps on the link below

Step 2 — Move logs to a permenart docker mounted directory

To enable logging outside of your docker container, add logback.xml file in your resources folder on each project and rebuild all your microservice images followiling the same instructions we used when building the images the first time.

logback.xml file

Make sure your file has your preferred logging settings like below

<?xml version="1.0" encoding="UTF-8"?><configuration><property name="LOG_PATH" value="logs"/><property name="APP_NAME" value="superdev-crm-customer"/><timestamp key="currentTimestamp" datePattern="yyyy-MM-dd'_'HH"/><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger.%M\(%line\) - %msg%n</pattern></encoder></appender><appender name="SAVE-TO-FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/${APP_NAME}.log</file><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><Pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n</Pattern></encoder><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><maxFileSize>100MB</maxFileSize><fileNamePattern>${LOG_PATH}/archived/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern><maxHistory>30</maxHistory><totalSizeCap>1GB</totalSizeCap></rollingPolicy></appender><root level="INFO"><appender-ref ref="STDOUT"/><appender-ref ref="SAVE-TO-FILE"/></root></configuration>

After rebuilding, your logs will be stored in /logs/superdev-crm-customer.log

Repeat the above for the other projects and rename the APP-NAME

Step 3— Create a docker-compose file with all the images we created

Create a folder in the path of your choice

mkdir /opt/workspace/nerdcode/docker/docker-compose/sudper-devs

Navigate to the directory and create a docker-compose file

cd /opt/workspace/nerdcode/docker/docker-compose/sudper-devs
touch docker-compose.yml

Open the file with an editor of your choice and paste the below content

version: '2.2'
services:
superdev-postgres: #name of the first service
image: medium/database:latest
container_name: postgres_superdev
restart: always
volumes:
- /opt/workspace/nerdcode/docker/data/medium:/var/lib/postgresql/data:rw
environment:
- POSTGRES_PASSWORD=P@ssword1
ports:
- "5432:5432" #specify ports forewarding
mem_limit: 1g
mem_reservation: 512m
cpus: 1.5
networks:
microservice_network:
aliases:
- postgres-server
superdev-eureka:
image: 'superdev/superdev-eureka-server:latest'
restart: always
volumes:
- /opt/workspace/nerdcode/docker/data/logs:/logs:rw
ports:
# both ports must match the port from external_url above
- "8761:8761"
mem_limit: 512m
mem_reservation: 256m
cpus: 1
networks:
microservice_network:
aliases:
- eureka-server
superdev-crm-customer:
image: 'superdev/superdev-crm-customer:latest'
restart: always
volumes:
- /opt/workspace/nerdcode/docker/data/logs:/logs:rw
mem_limit: 256m
mem_reservation: 128m
cpus: 0.5
depends_on:
- "superdev-eureka"
networks:
microservice_network:
superdev-zuul:
image: 'superdev/superdev-zuul:latest'
restart: always
volumes:
- /opt/workspace/nerdcode/docker/data/logs:/logs:rw
ports:
# both ports must match the port from external_url above
- "8081:8081"
mem_limit: 256m
mem_reservation: 128m
cpus: 0.5
depends_on:
- "superdev-eureka"
- "superdev-crm-customer"
networks:
microservice_network:
networks:
microservice_network:
ipam:
driver: default

The docker file above has all the setting we need for our different images, We mounted the database volume and also mounted our microservice logs directory.

Step 4 — Check if the docker-compose file has no errors

Run the following command in the directory wchich contains your docker-compose file

docker-compose config

If you have errors, it will indicate but if all is well it will print the contents of your docker-compose file.

Step 5 — Run docker-compose

Run the following command in the directory wchich contains your docker-compose file

docker-compose up &

If all is well, all your images should be up and running.

Step 6— Stop docker-compose

Run the following command in the directory wchich contains your docker-compose file

docker-compose down

Now all your containers should have stopped running

Step 7 — Lets change how the images see each other

We once had them seeing each other via your computer’s IP address, now since we have a docker-compose network enabled, we will use their network hostname.

We need to change how we reference Eureka, Customer Service and database.

On the application.property of each project referencing Eureka, change IP address to Eureka hostname like below

For Customer Service’s application.properties

# Data Source Propertiesspring.jpa.database=POSTGRESQLspring.datasource.platform=postgres#EDIT TO DATABASE HOSTNAMEspring.datasource.url=jdbc:postgresql://postgres-server:5432/superdevdbspring.datasource.username=superdevuserspring.datasource.password=P@ssword1spring.jpa.show-sql=truespring.jpa.generate-ddl=true#WE EDIT THE BELOW TO KEEP DATA PERMANENTLYspring.jpa.hibernate.ddl-auto=updatespring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true# JPA Propertiesspring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=falsespring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImplspring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy#EDIT TO HOSTNAME eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://eureka-server:8761/eureka}spring.application.name=superdev-crm-customer

And change ZUUL’s application.properties

server.port=8081#EDIT TO DOCKER_COMPOSE HOSTNAMEeureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://eureka-server:8761/eureka}spring.application.name=superdev-zuul#CHANGED FROM URL#zuul.routes.customer.url=http://superdev-crm-customer:8080#NOW USING SERVICE IDzuul.routes.customer.serviceId=superdev-crm-customereureka.client.registerWithEureka=trueeureka.client.fetchRegistry=true#TO AVAID TIMEOUT ERRORS PLEASE ADD THE 2 LINES BELOWzuul.host.socket-timeout-millis=60000ribbon.ReadTimeout=60000

Rebuild images and run docker-compose

Step 8 — Scaling up and down

Finally we get to this exciting part, scaling up and down.

If you look at our docker-compose file closely, you will realize that Customer service doesn’t expose any ports externally, which leaves us with one entry point which is our ZUUL API gateway. This also means we can scale up the microservice od Customer since there won’t be any external port clushes.

To scale up 4 instances of the Customer Service run the below command, you can play around with the number

docker-compose up --scale superdev-crm-customer=4

There are many ways to verify if your instances are running

Command you can use to check statistics of docker engine

docker stats

As you can see from the results above, Customer Service is running with 4 instances

or docker ps command

docker ps -a

or you can check on the Eureka dashboard

Resources of PART 2 can be cloned from GitHub

git clone https://github.com/vhutie/superdev-crm-customer-part2.git
git clone https://github.com/vhutie/superdev-eureka-server-part2.git
git clone https://github.com/vhutie/superdev-zuul-part2.git

Conclusion

Part 2 of this article has finally come to an end, you can now test your services if they working and load balanced. Every time you issue a request to ZUUL, it will not call the same instance of your Customer Service twice in a row.

Now you are all set. Share, like, and teach others… The next article will focus on using this article to have multiple microservices orchestrated using a workflow engine. We honestly cannot wait.

--

--

Vutlhari Ndlovhu

Full Stack developer for over 15 years, I build Mobile Apps, Web Apps, Microservice, Middleware Integration, Workflow Engine, ETL and More...