Creating Scalable Microservices with Docker

Introduction

Scalability is a key characteristic in building modern applications. Microservices architecture is a popular approach in building scalable applications. It lets developers break down a monolithic application into small, loosely coupled services. Each service is responsible for a particular functionality and can be independently deployed, scaled and maintained.

Docker is a containerization technology that simplifies the process of packaging, deploying and managing applications. Combining docker with microservices architecture can help developers build scalable applications that can be easily deployed, managed and scaled irrespective of the underlying infrastructure. In this tutorial, we will explore how to create scalable microservices with Docker.

Prerequisites

To follow along with this tutorial, you will need the following:
– Docker installed on your system.
– Basic knowledge of docker commands and microservices architecture.

Steps

1. Define the services

The first step in building a microservices application is to identify the services that make up the application. Each service should have a specific functionality. For this example, we will build a simple e-commerce application consisting of the following services:
– Product service: responsible for managing products.
– Order service: responsible for managing orders.
– Payment service: responsible for managing payments.

2. Build the services

Once we have identified the services, we can start building each service as a separate microservice. For each service, we need to write the code for the functionality it provides, define an interface (API) that other services can use to interact with it and package it as a docker image.

Let’s take an example of building the product service. We can define the API for the product service using the OpenAPI standard.

openapi: "3.0.0"
info:
  title: Product API
  version: "1.0.0"
paths:
  /product:
    get:
      summary: Get all products
      responses:
        '200':
          description: A list of products.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'
    post:
      summary: Create a new product.
      responses:
        '201':
          description: The created product.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
components:
  schemas:
    Product:
      type: object
      required:
        - name
        - price
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier for the product.
        name:
          type: string
          description: The name of the product.
        description:
          type: string
          description: A description of the product.
        price:
          type: number
          format: double
          minimum: 0
          description: The price of the product.

We can use this API definition to generate the code for the product service using a code generator like Swagger codegen. Once we have the code, we can package it as a docker image. We can define a Dockerfile for the product service as follows:

FROM openjdk:11-jre-slim

WORKDIR /app

# Copy the service code into the container
COPY target/product-service-1.0-SNAPSHOT.jar .

# Set the service command
CMD ["java", "-jar", "product-service-1.0-SNAPSHOT.jar"]

We can use the maven build tool to build the product service and package it as a docker image.

mvn clean package docker:build -DskipTests

We can repeat these steps to build the order and payment services and package them as docker images.

3. Define the infrastructure

To deploy and manage the microservices, we need to define the infrastructure for the application. There are several ways to do this. One way is to use docker-compose. Docker-compose is a tool for defining and running multi-container Docker applications.

We can define the infrastructure for our e-commerce application in a docker-compose file. Here’s an example docker-compose file:

version: '3.7'
services:
  product-service:
    image: <product-service-image>
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql/product?useSSL=false
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: password
    depends_on:
      - mysql
  order-service:
    image: <order-service-image>
    ports:
      - "8081:8081"
    environment:
      PRODUCT_SERVICE_URL: http://product-service:8080
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql/order?useSSL=false
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: password
    depends_on:
      - product-service
      - mysql
  payment-service:
    image: <payment-service-image>
    ports:
      - "8082:8082"
    environment:
      ORDER_SERVICE_URL: http://order-service:8081
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql/payment?useSSL=false
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: password
    depends_on:
      - order-service
      - mysql
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: product
      MYSQL_ROOT_PASSWORD: password
    volumes:
      - db-data:/var/lib/mysql
volumes:
  db-data:

In the above docker-compose file, we have defined the services for the product, order and payment microservices. We have also defined the mysql service as a dependency for the other services. The environment variables for the services are also defined. The mysql service uses a named volume to persist the data.

4. Deploy the services

Once we have defined the services and infrastructure, we can deploy the services using docker-compose.

docker-compose up -d

This command will start all the services defined in the docker-compose file in detached mode. We can check the status of the services using the docker-compose ps command.

docker-compose ps

5. Scale the services

One of the benefits of using docker and microservices architecture is the ability to scale the services horizontally. We can scale the services by increasing the number of instances of a service.

For example, if we want to scale the product service to three instances, we can use the docker-compose scale command like this:

docker-compose scale product-service=3

6. Monitor the services

Monitoring the microservices is essential to ensure the application is running smoothly. We can use several tools to monitor the services. One such tool is Prometheus.

Prometheus is a monitoring and alerting system. It collects metrics from the services and stores them in a time-series database. It provides a query language to extract and aggregate the metrics. We can use Prometheus to monitor the services in our application.

To use Prometheus, we need to add the Prometheus client library to each service. We can add the following dependency to each service:

<dependency>
    <groupId>io.prometheus</groupId>
    <artifactId>simpleclient_spring_boot</artifactId>
    <version>0.10.0</version>
</dependency>

We also need to add the following configuration to each service’s application.properties file:

management.endpoint.metrics.enabled=true
management.metrics.export.prometheus.enabled=true

Once we have added the Prometheus client library and configuration, we can start the services and navigate to the /actuator/prometheus endpoint of each service to verify that Prometheus is collecting metrics.

We can also set up a Prometheus server to collect and display the metrics. We can define a docker-compose file for the Prometheus server as follows:

version: '3.7'
services:
  prometheus:
    image: prom/prometheus:v2.28.1
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

In the above docker-compose file, we have defined the prometheus service and mounted a configuration file that specifies the targets to scrape. The Prometheus server will scrape the metrics from the services defined as targets in the configuration file.

7. Conclusion

In this tutorial, we have explored how to build scalable microservices with Docker. We have defined the services, built the services as docker images, defined the infrastructure using docker-compose and deployed the services. We have also explored how to scale the services and monitor them using Prometheus.

Using Docker and microservices architecture can help developers build scalable, easily deployable and manageable applications. With tools like docker-compose and Prometheus, managing the infrastructure and monitoring the services can be simplified.

Related Post