
Python Microservice: API Gateway & Consul
API Gateway acts as a single entry point for all clients and handles the request routing, composition, and protocol translation in a microservices architecture, here I will create an API Gateway using Python Flask and the requests library, to route both "user" and "order" services.
Here I will create an API Gateway to handle the 2 services (user and order). By importing the requests
module, which allows us to send HTTP requests in Python. It's used for making API calls to other services.
Then with route
decorator to specify that the get_users
function should handle requests to the /users
URL endpoint.
Then define response
to send GET request to the user-service and order-service running on port 5001/5002 at each endpoint.
# Folder Structure 03-with-api-gatway/ ├── user_service/ │ ├── Dockerfile │ └── user_service.py ├── order_service/ │ ├── Dockerfile │ └── order_service.py ├── api_gateway/ │ ├── Dockerfile │ └── api_gateway.py ├── docker-compose.yml # api_gateway.py from flask import Flask, jsonify import requests app = Flask(__name__) @app.route('/users') def get_users(): response = requests.get('http://user-service:5001/users') return jsonify(response.json()) @app.route('/orders') def get_orders(): response = requests.get('http://order-service:5002/orders') return jsonify(response.json()) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) # Dockerfile_apigateway # Use an official Python runtime as a parent image FROM python:3.9-slim # Set the working directory in the container WORKDIR /app # Copy the current directory contents into the container at /app COPY . /app # Install flask requests RUN pip install --no-cache-dir flask requests # Make port 5000 available to the world outside this container EXPOSE 5000 # Define environment variable ENV FLASK_APP=api_gateway.py # Run api_gateway.py when the container launches CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"] # update docker-compose.yaml ..... api-gateway: build: context: . dockerfile: Dockerfile_apigateway ports: - "5000:5000" # run docker-compose up --build docker-compose up --build
Verify the 2 services can be accessed via API Gateway address and port plus /users
and /orders
by defining request functions.
About Consul
Consul is a popular open-source tool for service discovery and service registration, here I will update the py files to register both services with Consul
In both user_service.py and order_service.py, add service registration logic.
To import time
module, also define function named register_service
that will handle the service registration logic with Consul, use dictionary defines the payload
for the service registration. It includes the service ID, name, address, and port.
I will use while True
to start an infinite loop, which will keep trying to register the service with Consul until it succeeds, add try
, else
and if
to handle exceptions during the registration process.
# vim order_service.py import requests from flask import Flask, jsonify import time app = Flask(__name__) @app.route('/orders') def get_orders(): orders = [ {'id': 1, 'item': 'Laptop', 'price': 1200}, {'id': 2, 'item': 'Phone', 'price': 800} ] return jsonify(orders) def register_service(): payload = { "ID": "order-service", "Name": "order-service", "Address": "order-service", "Port": 5002 } while True: try: response = requests.put('http://consul:8500/v1/agent/service/register', json=payload) if response.status_code == 200: print("Successfully registered order-service with Consul") break else: print(f"Failed to register order-service with Consul, status code: {response.status_code}") except requests.exceptions.RequestException as e: print(f"Error registering order-service with Consul: {e}") time.sleep(5) if __name__ == '__main__': print("Registering order-service with Consul") register_service() app.run(host='0.0.0.0', port=5002) # vim user_service.py import requests from flask import Flask, jsonify import time app = Flask(__name__) @app.route('/users') def get_users(): users = [ {'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'} ] return jsonify(users) def register_service(): payload = { "ID": "user-service", "Name": "user-service", "Address": "user-service", "Port": 5001 } while True: try: response = requests.put('http://consul:8500/v1/agent/service/register', json=payload) if response.status_code == 200: print("Successfully registered user-service with Consul") break else: print(f"Failed to register user-service with Consul, status code: {response.status_code}") except requests.exceptions.RequestException as e: print(f"Error registering user-service with Consul: {e}") time.sleep(5) if __name__ == '__main__': print("Registering user-service with Consul") register_service() app.run(host='0.0.0.0', port=5001) # update docker-compose.yaml version: '3' services: consul: image: consul:1.15.4 ports: - "8500:8500" user-service: build: context: ./user_service depends_on: - consul environment: - CONSUL_HTTP_ADDR=consul:8500 ports: - "5001:5001" order-service: build: context: ./order_service depends_on: - consul environment: - CONSUL_HTTP_ADDR=consul:8500 ports: - "5002:5002" api-gateway: build: context: ./api_gateway depends_on: - consul - user-service - order-service environment: - CONSUL_HTTP_ADDR=consul:8500 ports: - "5000:5000"
Now run the docker-compose and verify in Consul via localhost:
docker-compose up --build Creating 04-with-consul_consul_1 ... done Creating 04-with-consul_user-service_1 ... done Creating 04-with-consul_order-service_1 ... done Creating 04-with-consul_api-gateway_1 ... done consul_1 | 2024-05-18T14:28:45.465Z [DEBUG] agent: Node info in sync consul_1 | 2024-05-18T14:28:45.465Z [DEBUG] agent: Service in sync: service=order-service consul_1 | 2024-05-18T14:28:45.465Z [DEBUG] agent: Service in sync: service=user-service
Conclusion
Now we can use API Gateway and Consul to manage routing and service discovery.
In the next post, I will see how to enable logging with the ELK stack and monitoring with the Prometheus and Grafana stack.
====================
====================