Let’s Build Microservices Part III
By: Amol Limaye
Tags: Docker, Microservices, Nginx, Technology, Software Engineering

The need for API Gateway

We have three dockerized microservices and a consul server, all running on same machine. When we start them, the services register their IP and hosts to consul service registry. These services will work together to vend out the information we need.

Next, I can deploy this setup on a hosted production server so that the world can access my eCommerce application.

But how do I make sure only eCommerce service is accessible to outside world ? Since, other 2 services i.e. product and image are used only internally and not for direct use by client.

What if I want multiple instances of eCommerce service to cater to increasing load ? How do I load-balance client requests coming to eCommerce service and distribute them evenly across instances?

API Gateway will solve these problems. An API gateway will serve as a single entry point to our system and expose only the required endpoints. It will also load balance the client requests and distribute them across multiple instances of a service. API gateway will leverage the discovery service to know the dynamically changing hosts and ports of the service instances.

We will use NGINX as API gateway. Below is a nice video explaining API gateway.

NGINX API Gateway

About NGINX

NGINX is a web server. But it does a lot more than just serving content.

NGINX is a multifunction tool. With NGINX, you can use the same tool as your load balancer, reverse proxy, content cache, and web server, minimizing the amount of tooling and configuration your organization needs to maintain.

In this use case, we will use it as an API gateway and load balancer.

So, client requests will go through NGINX api gateway and will be routed to respective services.These services will be discovered by consul.

In addition, we will use consul-template and docker-compose.

About Consul-Template and Docker-Compose

consul-template : Consul template runs as a daemon service. It listens to consul service registry for changes. It accordingly updates the nginx configuration file and reloads the nginx configuration. Thus, nginx knows the locations of the ‘running’ service instances. NGINX routes requests according to this configuration. This is especially important in a dynamic infrastructure where the number of instances of a service, their hosts and ports may keep changing.

Consul template runs as a daemon service. It listens to consul service registry for changes. It accordingly updates the nginx configuration file and reloads the nginx configuration. Thus, nginx knows the locations of the ‘running’ service instances. NGINX routes requests according to this configuration. This is especially important in a dynamic infrastructure where the number of instances of a service, their hosts and ports may keep changing. docker-compose : This tool enables us to define and manage a multi-container docker application using a single YAML file. Using this, we won’t need to start, stop or manage each image individually. We could start all docker instances, scale them up/down, with a single command. This would make application deployment very easy.

To sum up, below are the components we will have. Each will be in a single docker container.

Nginx + consul template (in a single docker container) Consul ECommerce service Image service Product service

Deployment of these images is managed by docker-compose.

Now, that we have got the big picture, lets build these components

Step 2: Updates to service properties

We will need to change bootstrap.properties of all three services. Earlier, we referred to consul using localhost or local machine IP address. Hardcoding is not good. And, once we dockerize consul, this is not going to work. So, will call it using http://consul:8500 . This request will be routed to the container named ‘consul’ , which contains the consul service.

We also add instanceId property so that multiple instances of a service will have unique instance id when registering themselves with consul.

spring.cloud.consul.discovery.instanceId=

${spring.application.name}:${random.value} #Consul configuration#

spring.cloud.consul.host=consul

spring.cloud.consul.port=8500

Rebuild docker images for all three services after these changes.

Step 3: Build NGINX + consul template container

I have re-used and modified the code from an nginx demo app to build this container (code link at the end of blog).

We create a DockerFile for this container. We also create a template file for NGINX configuration (nginx.conf). This file uses consul templating language. This template will be populated with real-time consul service registry data and then set as NGINX configuration by consul template. Detailed explanation follows:

nginx.conf

This file uses consul templating language. It queries consul’s service registry for all the registered services, their host names and ports. This data will then be used to build NGINX configuration file.

Configuration like below will be generated using this template. (This is resulting NGINX configuration from services running on my machine)

Resulting nginx.conf

This is the nginx configuration when I have 1 instance of productApp and imageApp each and 3 instances of eCommerceApp.

p.s. I have given application name as productApp for product service and same way to other services.

spring.application.name=productApp

Above NGINX Configuration explained

Let me explain each part of above mentioned nginx configuration. I explain the configuration using IP 192.168.99.100, which is my docker host IP. I can access the running dockerized applications using this IP.

From NGINX docs:: Nginx consists of modules which are controlled by directives specified in the configuration file. Directives are divided into simple directives and block directives. A simple directive consists of the name and parameters separated by spaces and ends with a semicolon ( ; ). A block directive has the same structure as a simple directive, but instead of the semicolon it ends with a set of additional instructions surrounded by braces ( { and } ).

server — This defines a virtual server. It ‘listen’s on port 8080. So, NGINX can be accessed on port 8080 of docker host IP, 192.168.99.100 . NGINX will then proxy these requests to respective services as defined in location block.

server {

listen 8080 default_server; location ...<more configuration here>

}

location — Here, we define allowed URLs and what to do when NGINX receives that request. Below is location for ecommerce service.



proxy_pass

rewrite ^/ecommerceApp/(.*)$ /$1 break;

} location /ecommerceApp {proxy_pass http://ecommerceApp; rewrite ^/ecommerceApp/(.*)$ /$1 break;

As per above configuration, ‘192.168.99.100:8080/ecommerceApp’ can be used to call eCommerce service. The 192.168.99.100:8080/ecommerceApp request will be proxied to service defined as ‘ecommerceApp’ using the ‘proxy_pass’ directive.

upstream — In this block, a group of servers is defined. We see 3 ‘server’ entries for ‘ecommerceApp’ application. This is because I have 3 running docker instances of ecommerce service. (This server entry is a simple directive, different from ‘server’ block directive explained above.) NGINX will distribute(load-balance) requests coming to ‘ecommerceApp’ in a round robin manner among these 3 instances.

upstream ecommerceApp {

zone upstream-ecommerceApp 64k;

least_conn;

server 7ec3c83093be:9080 max_fails=3 fail_timeout=60 weight=1;

server 174af4c72f4f:9080 max_fails=3 fail_timeout=60 weight=1;

server c3411a956f8b:9080 max_fails=3 fail_timeout=60 weight=1;

}

NGINX DockerFile

This is the DockerFile for NGINX + consul template with explanations. I have shortened the code and added description. You can view the entire DockerFile in the linked source code.