api gateway example done | maybe need some cleanup

This commit is contained in:
2026-01-10 23:20:04 +03:00
parent f76982bb57
commit 9ecf873877
9 changed files with 603 additions and 33 deletions
+12
View File
@@ -0,0 +1,12 @@
{
"workspace": {
"id": "18e02c0d-1edf-4046-b1c2-009c61873ba5"
},
"entities": {
"collections": [],
"environments": [],
"specs": [],
"flows": [],
"globals": []
}
}
+1 -14
View File
@@ -1,20 +1,7 @@
# Используем official Python runtime как базовый образ
FROM python:3.11-slim
# Устанавливаем рабочую директорию в контейнере
WORKDIR /app
# Копируем файл зависимостей
COPY requirements.txt .
# Устанавливаем зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Копируем код приложения
COPY service-1-main.py main.py
# Экспонируем порт
EXPOSE 8001
# Команда для запуска приложения
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
+1 -14
View File
@@ -1,20 +1,7 @@
# Используем official Python runtime как базовый образ
FROM python:3.11-slim
# Устанавливаем рабочую директорию в контейнере
WORKDIR /app
# Копируем файл зависимостей
COPY requirements.txt .
# Устанавливаем зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Копируем код приложения
COPY service-2-main.py main.py
# Экспонируем порт
EXPOSE 8002
# Команда для запуска приложения
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8002"]
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8002"]
+381
View File
@@ -0,0 +1,381 @@
# Примеры тестирования микросервисов через API Gateway KrakenD
## Структура проекта
```
.
├── service-1-main.py # User Service
├── service-2-main.py # Order Service
├── requirements.txt # Зависимости
├── Dockerfile.service1 # Docker образ для Service 1
├── Dockerfile.service2 # Docker образ для Service 2
├── docker-compose.yaml # Docker Compose без Gateway
├── docker-compose-krakend.yaml # Docker Compose с KrakenD Gateway
├── krakend-config.json # Конфигурация KrakenD
└── README.md # Документация
```
## Запуск контейнеров с KrakenD
```bash
# Построить образы и запустить с KrakenD Gateway
docker-compose -f docker-compose-krakend.yaml up --build
# Запустить в фоновом режиме
docker-compose -f docker-compose-krakend.yaml up -d --build
# Просмотреть логи всех сервисов
docker-compose -f docker-compose-krakend.yaml logs -f
# Просмотреть логи только KrakenD
docker-compose -f docker-compose-krakend.yaml logs -f api-gateway
# Остановить контейнеры
docker-compose -f docker-compose-krakend.yaml down
```
## API Gateway KrakenD
### Что такое KrakenD?
KrakenD - это высокопроизводительный API Gateway с открытым исходным кодом, написанный на Go. Он обеспечивает:
- **Маршрутизацию запросов** к микросервисам
- **Кэширование** ответов
- **Rate limiting** (ограничение частоты запросов)
- **CORS** (Cross-Origin Resource Sharing)
- **Трансформацию данных** перед отправкой клиенту
- **Логирование** запросов
- **Мониторинг** производительности
### Порты сервисов
- **KrakenD Gateway**: `http://localhost:8000` (главный точка входа)
- **Service 1**: `http://localhost:8001` (доступен также через Gateway)
- **Service 2**: `http://localhost:8002` (доступен также через Gateway)
## Тестирование через KrakenD Gateway
### Health Check Gateway
```bash
curl http://localhost:8000/health
```
### Статус всех сервисов
```bash
curl http://localhost:8000/gateway/status
```
## User Service Endpoints через Gateway
### Получить список пользователей
```bash
curl http://localhost:8000/users
```
### Получить пользователя по ID
```bash
curl http://localhost:8000/users/1
```
### Создать нового пользователя
```bash
curl -X POST http://localhost:8000/users \
-H "Content-Type: application/json" \
-d '{
"name": "Петр Иванов",
"email": "petr@example.com",
"age": 30
}'
```
### Обновить пользователя
```bash
curl -X PUT http://localhost:8000/users/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Иван Петров (обновлено)",
"email": "ivan.updated@example.com",
"age": 29
}'
```
### Удалить пользователя
```bash
curl -X DELETE http://localhost:8000/users/1
```
### Получить статистику пользователей
```bash
curl http://localhost:8000/users/stats
```
## Order Service Endpoints через Gateway
### Получить список продуктов
```bash
curl http://localhost:8000/products
```
### Получить продукт по ID
```bash
curl http://localhost:8000/products/1
```
### Создать новый продукт
```bash
curl -X POST http://localhost:8000/products \
-H "Content-Type: application/json" \
-d '{
"name": "Мышка беспроводная",
"price": 1500.00,
"quantity": 20
}'
```
### Обновить продукт
```bash
curl -X PUT http://localhost:8000/products/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Ноутбук (обновлено)",
"price": 55000.00,
"quantity": 3
}'
```
### Удалить продукт
```bash
curl -X DELETE http://localhost:8000/products/1
```
### Получить список заказов
```bash
curl http://localhost:8000/orders
```
### Получить заказ по ID
```bash
curl http://localhost:8000/orders/1
```
### Создать новый заказ
```bash
curl -X POST http://localhost:8000/orders \
-H "Content-Type: application/json" \
-d '{
"user_id": 2,
"product_id": 2,
"quantity": 2
}'
```
### Получить статистику заказов
```bash
curl http://localhost:8000/orders/stats
```
## Тестирование через Python requests
```python
import requests
import json
import time
# URL Gateway
GATEWAY_URL = "http://localhost:8000"
def test_users():
"""Тестирование User Service через Gateway"""
print("=== Testing User Service ===")
# Получение списка пользователей
response = requests.get(f"{GATEWAY_URL}/users")
print(f"GET /users: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
# Получение конкретного пользователя
response = requests.get(f"{GATEWAY_URL}/users/1")
print(f"GET /users/1: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
# Создание пользователя
user_data = {
"name": "Тест Пользователь",
"email": "test@example.com",
"age": 25
}
response = requests.post(f"{GATEWAY_URL}/users", json=user_data)
print(f"POST /users: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
# Получение статистики
response = requests.get(f"{GATEWAY_URL}/users/stats")
print(f"GET /users/stats: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
def test_products():
"""Тестирование Order Service (Products) через Gateway"""
print("=== Testing Product Service ===")
# Получение списка продуктов
response = requests.get(f"{GATEWAY_URL}/products")
print(f"GET /products: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
# Получение конкретного продукта
response = requests.get(f"{GATEWAY_URL}/products/1")
print(f"GET /products/1: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
# Создание продукта
product_data = {
"name": "Новый продукт",
"price": 5000.00,
"quantity": 10
}
response = requests.post(f"{GATEWAY_URL}/products", json=product_data)
print(f"POST /products: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
def test_orders():
"""Тестирование Order Service (Orders) через Gateway"""
print("=== Testing Order Service ===")
# Получение списка заказов
response = requests.get(f"{GATEWAY_URL}/orders")
print(f"GET /orders: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
# Получение конкретного заказа
response = requests.get(f"{GATEWAY_URL}/orders/1")
print(f"GET /orders/1: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
# Создание заказа
order_data = {
"user_id": 1,
"product_id": 2,
"quantity": 2
}
response = requests.post(f"{GATEWAY_URL}/orders", json=order_data)
print(f"POST /orders: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
# Получение статистики
response = requests.get(f"{GATEWAY_URL}/orders/stats")
print(f"GET /orders/stats: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
def test_gateway_status():
"""Проверка статуса Gateway и всех сервисов"""
print("=== Gateway Status ===")
# Health check Gateway
response = requests.get(f"{GATEWAY_URL}/health")
print(f"Gateway Health: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
# Статус всех сервисов
response = requests.get(f"{GATEWAY_URL}/gateway/status")
print(f"All Services Status: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}\n")
if __name__ == "__main__":
try:
test_gateway_status()
test_users()
test_products()
test_orders()
print("✓ Все тесты завершены успешно!")
except requests.exceptions.ConnectionError:
print("✗ Ошибка подключения. Убедитесь, что контейнеры запущены:")
print(" docker-compose -f docker-compose-krakend.yaml up -d --build")
except Exception as e:
print(f"✗ Ошибка: {e}")
```
## Конфигурация KrakenD (krakend-config.json)
### Основные параметры
- **version**: версия конфигурации (3)
- **timeout**: глобальный таймаут для запросов (3s)
- **cache_ttl**: время кэширования по умолчанию (300s)
- **output_encoding**: формат ответа (json)
### Endpoints
Для каждого endpoint определяются:
- **endpoint**: путь доступа через Gateway
- **method**: HTTP метод (GET, POST, PUT, DELETE)
- **backend**: список бэкенд сервисов для маршрутизации
- **cache_ttl**: время кэширования для этого endpoint
### Пример endpoint конфигурации
```json
{
"endpoint": "/users/{id}",
"method": "GET",
"backend": [
{
"url_pattern": "/users/{id}",
"method": "GET",
"host": ["service-1:8001"],
"disable_host_sanitize": false
}
],
"output_encoding": "json",
"cache_ttl": "60s"
}
```
### Extra Config (дополнительная конфигурация)
1. **Логирование** - INFO уровень с форматированием
2. **Rate Limiting** - максимум 100 запросов в минуту
3. **CORS** - разрешены все источники и методы
## Преимущества использования KrakenD
✅ **Высокая производительность** - написан на Go, обрабатывает тысячи RPS
✅ **Простая конфигурация** - JSON файл, легко редактировать
✅ **Встроенное кэширование** - улучшает скорость ответов
✅ **Rate limiting** - защита от перегрузки
✅ **CORS поддержка** - работа с веб-приложениями
✅ **Логирование** - отслеживание запросов
✅ **Health checks** - мониторинг состояния сервисов
✅ **Гибкая маршрутизация** - поддержка параметров в URL
## Мониторинг и отладка
```bash
# Проверить статус контейнеров
docker-compose -f docker-compose-krakend.yaml ps
# Просмотреть логи Gateway
docker-compose -f docker-compose-krakend.yaml logs api-gateway
# Подключиться к контейнеру Gateway
docker-compose -f docker-compose-krakend.yaml exec api-gateway /bin/sh
# Проверить подключение Gateway к Service 1
docker-compose -f docker-compose-krakend.yaml exec api-gateway curl http://service-1:8001/health
# Проверить подключение Gateway к Service 2
docker-compose -f docker-compose-krakend.yaml exec api-gateway curl http://service-2:8002/health
```
## Требования
- Docker 20.10+
- Docker Compose 2.0+
- Python 3.11+ (для локального запуска тестов)
- curl или PostMan для тестирования API
## Лицензия
MIT
+28 -2
View File
@@ -1,5 +1,3 @@
version: '3.8'
services:
# Service 1 - User Service (порт 8001)
service-1:
@@ -51,6 +49,34 @@ services:
service.name: "order-service"
service.port: "8002"
# KrakenD API Gateway (порт 8000)
api-gateway:
image: devopsfaith/krakend:latest
container_name: krakend-gateway
ports:
- "8000:8000"
volumes:
- ./krakend-config.json:/etc/krakend/configuration.json
environment:
- KRAKEND_LOG_LEVEL=INFO
networks:
- microservices-network
restart: unless-stopped
depends_on:
- service-1
- service-2
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
command: ["run", "-c", "/etc/krakend/configuration.json", "-d"]
labels:
service.type: "gateway"
service.name: "krakend-gateway"
service.port: "8000"
networks:
microservices-network:
driver: bridge
+40
View File
@@ -0,0 +1,40 @@
{
"$schema": "https://www.krakend.io/schema/v2.12/krakend.json",
"version": 3,
"name": "KrakenD API Gateway",
"timeout": "3s",
"cache_ttl": "300s",
"output_encoding": "json",
"port": 8000,
"endpoints": [
{
"endpoint": "/health",
"method": "GET",
"backend": [
{
"url_pattern": "/health",
"method": "GET",
"host": ["service-1:8001"],
"mapping": {
"status": "user-service-health"
},
"deny": [
"service"
]
},
{
"url_pattern": "/health",
"method": "GET",
"host": ["service-2:8002"],
"mapping": {
"status": "order-service-health"
},
"deny": [
"service"
]
}
]
}
]
}
@@ -0,0 +1,7 @@
{
"id": "95f5d4ab-abf8-4892-a888-2f1fcc80052b",
"name": "Globals",
"values": [],
"_postman_variable_scope": "globals",
"_postman_exported_at": "2026-01-05T18:54:22.002Z"
}
+128
View File
@@ -0,0 +1,128 @@
"""
Первый микросервис - Service 1
Предоставляет API для управления пользователями
"""
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import logging
app = FastAPI(
title="Service 1 - User Service",
description="Микросервис для управления пользователями",
version="1.0.0"
)
# Логирование
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Модели данных
class User(BaseModel):
id: int
name: str
email: str
age: Optional[int] = None
class UserCreate(BaseModel):
name: str
email: str
age: Optional[int] = None
# Имитация базы данных
users_db = {
1: {"id": 1, "name": "Иван Петров", "email": "ivan@example.com", "age": 28},
2: {"id": 2, "name": "Мария Сидорова", "email": "maria@example.com", "age": 25},
}
next_user_id = 3
# Маршруты
@app.get("/", tags=["Health"])
async def root():
"""Проверка работоспособности Service 1"""
logger.info("Health check для Service 1")
return {
"service": "Service 1 - User Service",
"status": "operational",
"version": "1.0.0"
}
@app.get("/health", tags=["Health"])
async def health_check():
"""Health check endpoint"""
return {"status": "healthy", "service": "user-service"}
@app.get("/users", response_model=List[User], tags=["Users"])
async def list_users(skip: int = 0, limit: int = 10):
"""Получить список всех пользователей"""
logger.info(f"Получение списка пользователей (skip={skip}, limit={limit})")
users_list = list(users_db.values())
return users_list[skip:skip + limit]
@app.get("/users/{user_id}", response_model=User, tags=["Users"])
async def get_user(user_id: int):
"""Получить пользователя по ID"""
logger.info(f"Получение пользователя с ID: {user_id}")
if user_id not in users_db:
logger.warning(f"Пользователь с ID {user_id} не найден")
raise HTTPException(status_code=404, detail="Пользователь не найден")
return users_db[user_id]
@app.post("/users", response_model=User, tags=["Users"])
async def create_user(user: UserCreate):
"""Создать нового пользователя"""
global next_user_id
logger.info(f"Создание нового пользователя: {user.name}")
new_user = {
"id": next_user_id,
"name": user.name,
"email": user.email,
"age": user.age
}
users_db[next_user_id] = new_user
next_user_id += 1
logger.info(f"Пользователь создан с ID: {new_user['id']}")
return new_user
@app.put("/users/{user_id}", response_model=User, tags=["Users"])
async def update_user(user_id: int, user: UserCreate):
"""Обновить пользователя"""
logger.info(f"Обновление пользователя с ID: {user_id}")
if user_id not in users_db:
raise HTTPException(status_code=404, detail="Пользователь не найден")
updated_user = {
"id": user_id,
"name": user.name,
"email": user.email,
"age": user.age
}
users_db[user_id] = updated_user
return updated_user
@app.delete("/users/{user_id}", tags=["Users"])
async def delete_user(user_id: int):
"""Удалить пользователя"""
logger.info(f"Удаление пользователя с ID: {user_id}")
if user_id not in users_db:
raise HTTPException(status_code=404, detail="Пользователь не найден")
del users_db[user_id]
return {"message": f"Пользователь {user_id} удален"}
@app.get("/stats", tags=["Stats"])
async def get_stats():
"""Получить статистику по пользователям"""
logger.info("Получение статистики")
return {
"total_users": len(users_db),
"service": "user-service",
"endpoint": "/users"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
+5 -3
View File
@@ -2,12 +2,14 @@
Второй микросервис - Service 2
Предоставляет API для управления заказами и продуктами
"""
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
import logging
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(
title="Service 2 - Order Service",
description="Микросервис для управления заказами и продуктами",
@@ -78,7 +80,7 @@ async def root():
@app.get("/health", tags=["Health"])
async def health_check():
"""Health check endpoint"""
return {"status": "healthy", "service": "order-service"}
return {"status": "healthy", "service": "order-service", "random-info": "yep"}
# Endpoints для продуктов
@app.get("/products", response_model=List[Product], tags=["Products"])