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)
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" ]
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.
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-serversuperdev-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-serversuperdev-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.