Let’s Build Microservices Part III

How to add NGINX API Gateway for microservices

Amol Limaye
The Startup
12 min readJul 19, 2020

--

Photo by Grant Durr on Unsplash — Controlled gateway

This series of ‘How-to’ blog posts is my effort to learn and share the knowledge and working code of building various components of a microservices ecosystem.

This is a complete end-to-end explanation and ‘open-source’ code for microservices using NGINX + Consul + Spring boot. So, I think, this would be useful to fellow software engineers.

Let me know in the comments or message me if you want me to cover any more aspects of microservices.

In part I of this series, we built spring boot microservices that have service discovery and external configuration. In part II, we containerized these microservices.

In this part, I will describe how to add an NGINX API Gateway in front of microservices.

Part I : — https://medium.com/swlh/lets-build-microservices-part-1-a6d1b37154a9

Part II : — https://medium.com/@amol.limaye/lets-build-microservices-part-ii-9620c930587

Source code:

https://github.com/amollimaye/microservices-ecommerce-2

Blog Structure

Part 1

  • Build spring boot microservices
  • Use Consul for external configuration and service discovery

Part 2

  • Explaining what we will build — What we built in part I
  • Why Containerize ?
  • About Docker
  • Step 1: Containerize microservices

Part 3

  • Explaining what we will build — What we build in this part
  • Need for API gateway
  • About NGINX
  • About Consul-Template and Docker-Compose
  • Step 2: Updates to service properties
  • Step 3: Build NGINX + Consul Template container
  • Step 4: docker-compose
  • See the application in action

Explaining what we will build

Microservices block diagram

What we build in this part

In part II, I explained docker and how to dockerize the three spring boot microservices.

In this part, we add an API gateway using NGINX. It will act as a single entry point for our services. In this simplistic demo application, we expose one endpoint of one service(eCommerce) to the client. Other services are not needed and hence won’t be accessible to client. This is API Gateway pattern.

We will configure NGINX to do load balancing as well. I explain and show how the client requests are load balanced across multiple instances of eCommerce service, whose count, hosts and ports could change dynamically. Consul-template will dynamically update the nginx configuration using consul service registry, thus enabling NGINX to do load balancing.

We containerize each of these components using docker. The images are orchestrated(managed) using docker compose.

Below, I explain code for each component in detail and then finally show steps to run these components together.

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.
  • 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.

  1. Nginx + consul template (in a single docker container)
  2. Consul
  3. ECommerce service
  4. Image service
  5. 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.

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.

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.
  • location — Here, we define allowed URLs and what to do when NGINX receives that request. Below is location for ecommerce service.

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.
  • 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.

Step 4: docker-compose

Docker compose tool helps us to define containers of multi-container applications and start/stop/scale-up or down with a single command.

We define all the needed docker images in this yaml file. When instantiated, docker-compose creates a bridge network. Instances of these images can communicate over this network to each other.

docker-compose.yml for microservices

When I give command docker-compose up -d , docker-compose will start instances of each of the defined docker image.

Docker compose adds all these instances in a single network. Any instance inside the docker machine can reach to another using its container name e.g. consul container can be reached by its name : ‘consul’ .

Since we don’t need any custom behavior for consul, we use prebuilt consul dockerhub image.

For NGINX and consul, we expose ports 8080 and 8500 respectively to the host machine. So, I will be able to access NGINX using <Docker-Machine-IP>:8080 and consul using<Docker-Machine-IP>:8500 port from my machine when I start these instances on my machine.

Run the code

1. Build docker images

We need to build 4 docker images. I have explained how to build docker images for three spring boot services. Fourth is NGINX. Go into ‘nginx’ folder to build the docker image.

docker build -t nginx .

You can see the nginx image in the list of locally available images using

docker images

2. Run docker-compose

Set the HOST_IP env variable needed for this app.

export HOST_IP=192.168.99.100

Run docker-compose

The network name will be created using the root folder name, which is ‘microservices’ in this case. We can see that all the instances are up. The three microservices will also start up once their docker container is created.

3. See the applications starting up

The stderr and stdout logs of the processes(i.e. services) running in containers are collected by docker engine and sent to the logging driver. Here, we can see the logs of applications starting up using command

Container Id of all running containers can be seen using docker ps command. I can see the logs of product service as below using product service container ID.

Once the services startup, consul service registry will be updated with their entries. Consul-template will update this info and reload NGINX configuration. We can verify this by checking the NGINX configuration.

Check NGINX

First, see the NGINX status using command service nginx status

This command needs to be executed inside the container having NGINX . We can do this by using docker exec <container-name> <command>

We can also check the NGINX configuration status.

See NGINX configuration (-t just validates the configuration. -T validates and prints the configuration).

4. See the application in action !

NGINX

We have exposed port 8080 for NGINX . We can see the static welcome page by hitting http://192.168.99.100:8080/

Services

Now, let‘s see NGINX working as an API gateway. We can see in NGINX configuration that below URLs are enabled.

/ecommerceApp , /imageApp , /productApp

So, we can access respective services through NGINX api gateway.

http://192.168.99.100:8080/ecommerceApp/ecommerce-service/ecommerceProducts
http://192.168.99.100:8080/imageApp/image-service/images
http://192.168.99.100:8080/productApp/product-service/products

You will see the respective JSON responses.

Consul

We have also opened up consul on port 8500 via docker-compose.yml file.

We can see the consul UI using http://192.168.99.100:8500/

5. Limit the access to only ecommerce app

Now, we want only ecommerce service to be accessible from outside NGINX. For this, we filter out that service in our nginx/nginx.conf file that is used by consul-template.

We rebuild nginx image and restart that container. Now, only the url of ecommerce service will be seen in NGINX configuration ‘location’.

If you open http://192.168.99.100:8080/productApp/product-service/products , you will get a 404 response from NGINX.

Whereas you will still be able to access ecommerce service, which is able to aggregate data from product and image services and respond back correctly.

http://192.168.99.100:8080/ecommerceApp/ecommerce-service/ecommerceProducts

This means the product and image services are accessible by other services inside the docker network, but not to the world outside this docker network.

6. Scale-up

Next, let us scale up the ecommerceService

Once the instances of ecommerce spring boot app start up, we can view the updated NGINX configuration. Notice the upstream block for ecommerce, you will see 3 servers now, corresponding to 3 running ecommerce service instances.

NGINX will load balance the requests, from external clients to ecommerce service, within these three instances.

😎 This concludes part III .

Coming up next

In next part , we will add following functionalities:

  • Tracing
  • Monitoring
  • Log aggregation
  • Circuit breaker
  • Security

I have taken a lot of efforts to explain each component and the code in detail. Let me know your feedback on this.

Note: The contents of this blog are my personal views and do not reflect that of my employer in any way.

Resources

--

--

Amol Limaye
The Startup

I love to write code, build stuff and share that knowledge. More about me on my profile: https://in.linkedin.com/in/amolrlimaye