In this last article for our current microservices series, we'll examine how to combine our Ruby-based and Node-based microservice APIs, into a single Docker-based stack on Cloud 66. The stack will support both microservices and a front-end Nginx gateway to distribute incoming requests, all deployed within the same Cloud 66 stack.
Building a service gateway using Docker + Nginx
Since we already have our two microservices built, we need to put an API gateway in front, to distribute incoming requests to the right microservice based on the path requested. Below is a diagram of what this will look like when we're done:
Creating the Nginx gateway container
For our Nginx-based API gateway, we first need to setup the
nginx.conf file that will send requests to the proper microservice:
Some notes on this Nginx config:
- We're using the
daemon off;directive to force nginx to run in the foreground, preventing the container from stopping after the process has spawned. This recommendation comes from a Digital Ocean article on containerizing with Nginx
- We're enabling gzip for all browsers except older versions of IE
- We then declare both upstream API servers: one for each microservice. Since Cloud 66 offers DNS entries for each service in our stack (servicename.cloud66.local), we'll use this as the hostname for each microservice we're deploying. This also makes it is easy to point to the deployed service, while hiding the number of containers we may have deployed currently (and their individual IP addresses)
- Finally, we need to define the base URLs we want to route to each microservice, telling Nginx to use the upstream server to handle the request. For this example, we're routing /products to the Products microservice, and product_quantities to the Inventory microservice
The config file will be installed to
/etc/nginx/nginx.conf as part of our
Dockerfile uses the latest Ubuntu image, ensures all of the latest packages are installed, installs Nginx and then installs the config file using the
FROM ubuntu:latest # keep the container updated RUN apt-get update -qq && apt-get install -y build-essential # install the latest nginx RUN apt-get install -y nginx # Install a customized nginx config ENV NGINX_CONFIG_HOME /etc/nginx WORKDIR $NGINX_CONFIG_HOME COPY nginx.conf $NGINX_CONFIG_HOME
Assuming you've cloned the two microservice repositories into the same root-level directory as this repository, we can define a
docker-compose.yml that allows us to work with the environment locally:
products: build: ../microservices-sinatra-products/ command: bundle exec ruby products_service.rb ports: - "4567:4567" links: - mongodb - mongodb:mongodb.cloud66.local environment: - RAILS_ENV=production - RACK_ENV=production inventory: build: ../microservices-node-inventory/ command: npm start ports: - "8080:8080" links: - mongodb - mongodb:mongodb.cloud66.local environment: - NODE_ENV=production - MONGODB_ADDRESS=mongodb mongodb: image: mongo nginxgw: build: . command: service nginx start ports: - "80:80" links: - products:products.cloud66.local - inventory:inventory.cloud66.local environment:
We can now use Docker Compose to build our 2 microservice images, the Nginx gateway image, and pull the mongodb image locally:
Followed by running all of the containers:
You should see the typical output to STDOUT indicating that all containers have started. To verify everything's working, you can try to reach the Products microservice by running:
curl -X GET http://127.0.0.1:80/products
Notice that we're going through port 80 so we're using Nginx to route the request, rather than 4567 as we would if we were going directly to the microservice.
When you're done running Docker Compose, press CTRL-C or run the following command to shutdown both containers:
Deploying the microservice architecture to Cloud 66
With everything running locally using Docker Compose, we'll now deploy everything using Cloud 66. First, we need to ensure that we have an accessible git repository with all of our Nginx gateway artifacts. For reference, I've setup a public Github repository with everything you need, including the files mentioned in this article, above. One of the files in this repository is a
service.yml file that can be used to deploy to Cloud 66 using a Docker-based stack:
services: products: git_url: email@example.com:launchany/microservices-sinatra-products.git git_branch: master command: bundle exec ruby products_service.rb build_root: . ports: - container: 4567 env_vars: RAILS_ENV: production RACK_ENV: production inventory: git_url: firstname.lastname@example.org:launchany/microservices-node-inventory.git git_branch: master command: npm start build_root: . ports: - container: 8080 env_vars: NODE_ENV: production nginxgw: git_url: email@example.com:launchany/microservices-nginx-gateway.git git_branch: master command: service nginx start build_root: . ports: - container: 80 http: 80 https: 443 databases: - mongodb
Similar to Docker Compose, this configuration tells the Cloud 66 deployment system several important facts:
- How to build each service image using my public Github repositories for the sourcecode (Note: you can change the Git URLs to point to your own forked version of each repository if you wish)
- The ports exposed by each microservice container
- The port to externalize to the internet for our Nginx-based API gateway (in this case, port 80 and 443)
- The MongoDB instance that will be deployed to the host server, alongside our containers
With everything in place, it's time to login to the Cloud 66 dashboard and launch our stack:
- Login or Signup for Cloud66
- Create a New Stack, selecting the Docker Stack option
- Give your stack a name and select an environment (e.g. "Microservices Example" in production mode)
- Switch to the advanced tab and paste the contents of the
- Click the button to go to the next step
- Select your deployment target and cloud provider as normal
After a few minutes, your server will be provisioned and all three containers will be launched. You can then verify everything is working by finding your server's hostname and running curl:
curl -X GET http://your.host.name/products
Note: To find your server's hostname, you can use the Dashboard to browse to the stack, then select the Docker cluster, and view the server details provisioned to deploy the Docker stack.
Once deployed, you can redeploy all services through the dashboard with one click using the 'Deploy All' feature. Need to make a change to a microservice and redeploy it? Not a problem with Cloud 66.
Redeploying a single microservice
Most microservices are designed to be deployed in isolation. Cloud 66 supports deploying a single service, two or more specific services, or all services at once. Simply define a new Deploy Profile and you can redeploy them in any way you require.
Even better, setup redeployment hooks to automatically deploy the latest version of your microservice whenever you push your changes to git.
Alternative deployment options
While this example is simple and can share the same database, there is another approach you can take when using Cloud 66.
Rather than deploying a single application stack for all 3 services, each one could be split into separate stacks. Each stack could then have its own dedicated database, in isolation from other services.
Taking this approach requires you understand how to configure cross-stack security access. This article outlines the steps for how to do this, so that your Nginx service gateway will be able to route incoming requests across your microservice stacks.
As you can see, Cloud 66 provides flexibility in how we can configure and deploy our applications. You may want to explore API gateway and management solutions such as Kong or Tyk to better secure and manage your APIs and microservices.