SentinelOps is a microservices health monitoring and anomaly-detection platform.
- Phase 1: FastAPI REST APIs for User, Order, and Payment services with API Gateway proxying and Docker Compose orchestration.
- Phase 2: Microservice Monitoring with Prometheus-compatible metrics collection across all services and automated Prometheus scraping.
- Phase 3: Controlled Failure Injection — seven safe, manually-triggerable chaos scenarios with automatic recovery, history tracking, and Prometheus observability.
| Service | Port | Description | Key Endpoints |
|---|---|---|---|
| API Gateway | 8000 |
Entry point for external requests; proxies traffic downstream. | GET /health/api/users → User Service/api/orders → Order Service/api/payments → Payment Service |
| User Service | 8001 |
Manages user profiles. | GET /healthGET /metricsGET /usersGET /users/{user_id} |
| Order Service | 8002 |
Manages orders; communicates with Payment Service. | GET /healthGET /metricsGET /ordersPOST /orders |
| Payment Service | 8003 |
Processes payment transactions and authorizations. | GET /healthGET /metricsPOST /payments |
| Prometheus | 9090 |
Time-series metrics collection and query engine. | GET /targetsGET /graphGET /api/v1/query |
Each microservice exports Prometheus-compatible metrics via its /metrics endpoint on its dedicated port.
| Metric Requirement | Prometheus Metric Name | Type | Description |
|---|---|---|---|
| CPU Usage | sentinel_cpu_usage_percent |
Gauge | Current process CPU usage percentage (0-100%) |
| Memory Usage |
sentinel_memory_usage_bytessentinel_memory_usage_percent
|
Gauge Gauge |
Process Resident Set Size (RSS) memory in bytes and memory percentage |
| Request Count |
sentinel_request_count_totalsentinel_requests_total
|
Counter | Total incoming HTTP requests labeled by service, method, endpoint, and status_code
|
| Request Rate |
sentinel_request_ratesentinel_request_rate_per_second
|
Gauge | Instantaneous request rate (requests/sec over a 60s sliding window) |
| Response Latency | sentinel_response_latency_seconds |
Histogram | Request-response latency distribution in seconds labeled by service, method, and endpoint
|
| Error Count |
sentinel_error_count_totalsentinel_errors_total
|
Counter | Total HTTP error responses (HTTP status service, method, endpoint, and status_code
|
| Error Rate |
sentinel_error_ratesentinel_error_rate_percent
|
Gauge | Error rate percentage (0-100% over a 60s sliding window) |
| Service Availability | sentinel_service_availability |
Gauge | Health availability indicator (1.0 = healthy/up, 0.0 = unavailable) |
From the project root directory:
# 1. Build and start all microservices and Prometheus in the background
docker compose up --build -d
# 2. Verify all 5 containers are running
docker compose ps
# 3. View live logs from all services (or specify a service like 'prometheus')
docker compose logs -f
# 4. Stop all services when finished
docker compose downIf developing locally outside Docker, install requirements and start each service:
# Terminal 1 - User Service
cd services/user-service
pip install -r requirements.txt
uvicorn app.main:app --port 8001 --reload
# Terminal 2 - Payment Service
cd services/payment-service
pip install -r requirements.txt
uvicorn app.main:app --port 8003 --reload
# Terminal 3 - Order Service
cd services/order-service
pip install -r requirements.txt
PAYMENT_SERVICE_URL=http://localhost:8003 uvicorn app.main:app --port 8002 --reload
# Terminal 4 - API Gateway
cd gateway
pip install -r requirements.txt
USER_SERVICE_URL=http://localhost:8001 ORDER_SERVICE_URL=http://localhost:8002 PAYMENT_SERVICE_URL=http://localhost:8003 uvicorn app.main:app --port 8000 --reload
# Terminal 5 - Prometheus
prometheus --config.file=monitoring/prometheus.ymlVerify that all services report healthy:
- API Gateway:
curl http://localhost:8000/health - User Service:
curl http://localhost:8001/health - Order Service:
curl http://localhost:8002/health - Payment Service:
curl http://localhost:8003/health
Expected response:
{"service": "<service-name>", "status": "healthy"}Inspect the raw Prometheus text exposition metrics directly from each microservice:
- User Service Metrics:
curl http://localhost:8001/metrics - Order Service Metrics:
curl http://localhost:8002/metrics - Payment Service Metrics:
curl http://localhost:8003/metrics
Each endpoint exposes the CPU, memory, request count, request rate, latency, error count, error rate, and availability metrics.
-
Open the Prometheus Web UI in your browser: http://localhost:9090
-
Navigate to Status
$\rightarrow$ Targets (or open http://localhost:9090/targets). You should see all targets with State UP:-
user-service(http://user-service:8001/metrics) → UP -
order-service(http://order-service:8002/metrics) → UP -
payment-service(http://payment-service:8003/metrics) → UP -
prometheus(http://localhost:9090/metrics) → UP
-
Enter these queries in the Prometheus Expression Browser (http://localhost:9090/graph) or query via HTTP:
-
Service Availability:
sentinel_service_availability(Returns
1for all active services) -
CPU Usage (%):
sentinel_cpu_usage_percent -
Memory Usage (in Megabytes):
sentinel_memory_usage_bytes / 1024 / 1024 -
Total Request Count:
sum by (service, endpoint, status_code) (sentinel_request_count_total) -
Request Rate (req/sec):
sentinel_request_rateOr calculated over time:
rate(sentinel_requests_total[1m]) -
Response Latency (95th percentile):
histogram_quantile(0.95, sum by (le, service) (rate(sentinel_response_latency_seconds_bucket[1m]))) -
Error Count:
sentinel_error_count_total -
Error Rate (%):
sentinel_error_rateOr calculated over time:
sum(rate(sentinel_errors_total[1m])) / sum(rate(sentinel_requests_total[1m])) * 100
Test the REST APIs through the API Gateway (port 8000) and observe Prometheus metrics update:
# 1. Fetch users (User Service)
curl -X GET http://localhost:8000/api/users
# 2. Fetch specific user (User Service)
curl -X GET http://localhost:8000/api/users/1
# 3. Create an order (Triggers Order Service -> Payment Service communication)
curl -X POST http://localhost:8000/api/orders \
-H "Content-Type: application/json" \
-d '{"user_id": 1, "items": ["Laptop", "Mouse"], "total_amount": 1299.99, "payment_method": "credit_card"}'
# 4. Fetch orders (Order Service)
curl -X GET http://localhost:8000/api/orders
# 5. Direct payment (Payment Service)
curl -X POST http://localhost:8000/api/payments \
-H "Content-Type: application/json" \
-d '{"order_id": 100, "amount": 99.50, "payment_method": "credit_card"}'
# 6. Trigger a 404 error to test error tracking
curl -X GET http://localhost:8000/api/users/999After running these commands, refresh the Prometheus query for sentinel_requests_total and sentinel_error_count_total to see the live metrics collected across all three services.
Each service has a ChaosManager that provides seven safe, manually-triggerable failure simulations. All chaos activity is:
- Stoppable — every scenario has a dedicated
/stopendpoint. - Auto-resetting — pass
duration_secondsfor automatic cleanup. - Recorded — every trigger/stop is timestamped in an in-memory history log.
- Non-destructive — no container exits, no persistent data loss; Prometheus keeps scraping throughout.
API Gateway (:8000) Microservices (:8001/8002/8003)
┌──────────────────────┐ ┌──────────────────────────────────┐
│ /api/chaos/users/* │──────▶ │ ChaosMiddleware (request-layer) │
│ /api/chaos/orders/* │ │ ↳ 503 if unavailability_active │
│ /api/chaos/payments/*│ │ ↳ asyncio.sleep if latency │
│ │ │ ↳ random 4xx/5xx if error_rate │
│ /api/chaos/status │◀────── │ ChaosManager (state + history) │
│ /api/chaos/history │ │ ↳ CpuStressWorker (threads) │
│ /api/chaos/reset │ │ ↳ bytearray memory blocks │
└──────────────────────┘ │ ↳ asyncio traffic tasks │
└──────────────────────────────────┘
Every endpoint below is available on each service directly or via the API Gateway proxy.
| Direct (per service) | Via Gateway (/api/chaos/<service>/<path>) |
|---|---|
POST http://localhost:8001/chaos/* |
POST http://localhost:8000/api/chaos/users/* |
POST http://localhost:8002/chaos/* |
POST http://localhost:8000/api/chaos/orders/* |
POST http://localhost:8003/chaos/* |
POST http://localhost:8000/api/chaos/payments/* |
| Endpoint | Method | Body (JSON) | Description |
|---|---|---|---|
/chaos/cpu |
POST |
{"duration_seconds":30,"load_percentage":85.0} |
Start CPU stress workers |
/chaos/cpu/stop |
POST |
— | Stop CPU stress immediately |
/chaos/memory |
POST |
{"mb_to_allocate":150,"duration_seconds":30} |
Allocate large memory blocks |
/chaos/memory/stop |
POST |
— | Release memory blocks (GC collect) |
/chaos/latency |
POST |
{"delay_seconds":2.0,"probability":1.0,"duration_seconds":60} |
Inject response delay |
/chaos/latency/stop |
POST |
— | Remove latency injection |
/chaos/error |
POST |
{"error_rate":0.5,"status_code":500,"message":"...","duration_seconds":60} |
Inject random HTTP errors |
/chaos/error/stop |
POST |
— | Stop error injection |
/chaos/unavailability |
POST |
{"duration_seconds":60} |
Return 503 on all non-chaos requests |
/chaos/unavailability/stop |
POST |
— | Restore service to healthy |
/chaos/dependency |
POST |
{"dependency_name":"bank-gateway","failure_mode":"timeout","duration_seconds":60} |
Simulate downstream failure |
/chaos/dependency/stop |
POST |
— | Restore dependency |
/chaos/traffic-spike |
POST |
{"requests_per_second":15,"duration_seconds":10,"target_path":"/users"} |
Generate synthetic load |
/chaos/traffic-spike/stop |
POST |
— | Cancel traffic task |
/chaos/stop |
POST |
— | Reset all failures for that service |
/chaos/status |
GET |
— | Current active failures & state booleans |
/chaos/history |
GET |
— | Full history of all chaos records |
Consolidated (across all three services via Gateway):
| Endpoint | Method | Description |
|---|---|---|
GET /api/chaos/status |
GET |
Aggregated status from all three services |
GET /api/chaos/history |
GET |
Merged + sorted history from all three services |
POST /api/chaos/reset |
POST |
Reset all failures on all three services at once |
failure_mode value |
HTTP status returned to caller |
|---|---|
timeout |
504 Gateway Timeout |
error_503 |
503 Service Unavailable |
connection_error |
502 Bad Gateway |
Replace <PORT> with 8001 (User), 8002 (Order), or 8003 (Payment) in the examples below.
All examples also work via the Gateway — substitute http://localhost:8000/api/chaos/<service>/<path>.
# Trigger: 70% CPU load on User Service for 20 seconds
curl -X POST http://localhost:8001/chaos/cpu \
-H "Content-Type: application/json" \
-d '{"duration_seconds": 20, "load_percentage": 70.0}'
# Verify: watch CPU in Prometheus
# PromQL: sentinel_cpu_usage_percent{service="user-service"}
# Stop manually (optional — auto-stops after duration)
curl -X POST http://localhost:8001/chaos/cpu/stop# Trigger: allocate 200 MB in Order Service for 30 seconds
curl -X POST http://localhost:8002/chaos/memory \
-H "Content-Type: application/json" \
-d '{"mb_to_allocate": 200, "duration_seconds": 30}'
# Verify in Prometheus:
# PromQL: sentinel_memory_usage_bytes{service="order-service"} / 1024 / 1024
# Stop manually
curl -X POST http://localhost:8002/chaos/memory/stop# Trigger: inject 3-second delay on ALL requests to Payment Service
curl -X POST http://localhost:8003/chaos/latency \
-H "Content-Type: application/json" \
-d '{"delay_seconds": 3.0, "probability": 1.0, "duration_seconds": 60}'
# Verify: time a health check — it should take ~3 seconds
curl -w "\nTotal time: %{time_total}s\n" http://localhost:8003/health
# Verify in Prometheus (95th percentile latency):
# PromQL: histogram_quantile(0.95, sum by(le, service) (rate(sentinel_response_latency_seconds_bucket[1m])))
# Stop
curl -X POST http://localhost:8003/chaos/latency/stop# Trigger: 50% of requests to User Service return HTTP 500
curl -X POST http://localhost:8001/chaos/error \
-H "Content-Type: application/json" \
-d '{"error_rate": 0.5, "status_code": 500, "message": "Simulated internal error", "duration_seconds": 60}'
# Verify: call the API several times — ~half should fail
curl http://localhost:8001/users # may return 500
curl http://localhost:8001/users # may return 200
# Verify in Prometheus:
# PromQL: sentinel_error_rate{service="user-service"}
# Stop
curl -X POST http://localhost:8001/chaos/error/stop# Trigger: Order Service returns 503 on all non-chaos endpoints
curl -X POST http://localhost:8002/chaos/unavailability \
-H "Content-Type: application/json" \
-d '{"duration_seconds": 30}'
# Verify: health check should return 503
curl -i http://localhost:8002/health
# HTTP/1.1 503 Service Unavailable
# Verify in Prometheus:
# PromQL: sentinel_service_availability{service="order-service"} → should be 0
# Restore
curl -X POST http://localhost:8002/chaos/unavailability/stop
# Verify recovery
curl http://localhost:8002/health
# {"service":"order-service","status":"healthy"}# Trigger: Payment Service simulates its payment gateway timing out
curl -X POST http://localhost:8003/chaos/dependency \
-H "Content-Type: application/json" \
-d '{"dependency_name": "bank-gateway", "failure_mode": "timeout", "duration_seconds": 30}'
# Verify: creating a payment should fail with 504
curl -X POST http://localhost:8003/payments \
-H "Content-Type: application/json" \
-d '{"order_id": 1, "amount": 99.99, "payment_method": "credit_card"}'
# {"detail":"Dependency failure: Timeout connecting to payment gateway bank-gateway"}
# Try other failure modes:
# "error_503" → 503, "connection_error" → 502
# Stop
curl -X POST http://localhost:8003/chaos/dependency/stop# Trigger: send 20 req/s to User Service /users for 10 seconds
curl -X POST http://localhost:8001/chaos/traffic-spike \
-H "Content-Type: application/json" \
-d '{"requests_per_second": 20, "duration_seconds": 10, "target_path": "/users"}'
# Verify in Prometheus (watch request rate spike):
# PromQL: sentinel_request_rate{service="user-service"}
# PromQL: rate(sentinel_requests_total{service="user-service"}[30s])
# Stop early (optional)
curl -X POST http://localhost:8001/chaos/traffic-spike/stop# Status of a single service
curl http://localhost:8001/chaos/status | python -m json.tool
# Consolidated status across all three services (via Gateway)
curl http://localhost:8000/api/chaos/status | python -m json.tool
# Full chaos history (all records, sorted newest first)
curl http://localhost:8000/api/chaos/history | python -m json.tool
# Reset all failures on all services at once
curl -X POST http://localhost:8000/api/chaos/reset{
"id": "chaos_a3f12b9c",
"service": "user-service",
"failure_type": "cpu_spike",
"parameters": {
"duration_seconds": 20,
"load_percentage": 70.0,
"workers": 4
},
"start_timestamp": "2026-09-27T07:10:00.000000+00:00",
"end_timestamp": "2026-09-27T07:10:20.412000+00:00",
"duration_seconds": 20.41,
"status": "stopped"
}During and after failure injection, query these metrics in the Prometheus UI at http://localhost:9090:
| Failure Type | PromQL to Watch |
|---|---|
| CPU Spike | sentinel_cpu_usage_percent |
| Memory Growth | sentinel_memory_usage_bytes / 1024 / 1024 |
| Latency | histogram_quantile(0.95, sum by(le, service)(rate(sentinel_response_latency_seconds_bucket[1m]))) |
| Error Rate | sentinel_error_rate or rate(sentinel_errors_total[1m]) |
| Unavailability | sentinel_service_availability |
| Dependency Failure | sentinel_error_count_total (will spike on the affected service) |
| Traffic Spike | rate(sentinel_requests_total[30s]) or sentinel_request_rate |
| Constraint | How it's enforced |
|---|---|
| CPU workers capped | Max 4 threads; load_percentage capped at 100% |
| Memory capped | mb_to_allocate limited to 500 MB per trigger |
| Traffic capped | requests_per_second limited to 50; duration_seconds limited to 60 |
| Latency capped | delay_seconds limited to 10 seconds |
| Auto-recovery | All scenarios accept duration_seconds for automatic stop |
| No process kill | "Unavailability" returns 503 — the container stays alive |
| Chaos endpoints bypass middleware | /metrics and /chaos/* are never blocked by chaos |