Módulo 8: Proyecto Final Integrador - RAG System Completo
Production Deployment: Docker, FastAPI, Kubernetes
Descripción
En esta cápsula llevarás tu sistema RAG a production con Docker (containerization), FastAPI (REST API), y Kubernetes (scaling). Aprenderás las mejores prácticas para deployment, monitoring, y health checks.
Un sistema RAG production-ready no es solo código que funciona localmente—debe ser:
- Containerizado (Docker) para deployment consistente
- API-fied (FastAPI) para consumo de otros servicios
- Escalable (Kubernetes) para manejar tráfico variable
- Observable (Prometheus/Grafana) para troubleshooting
- Resiliente (health checks, graceful shutdown)
Al final tendrás un sistema completo deployable a cualquier cloud provider (AWS, GCP, Azure).
Duración estimada: 50-60 minutos
Objetivos
Al completar esta cápsula, serás capaz de:
- ✅ Containerizar sistema RAG con Docker
- ✅ Crear REST API con FastAPI
- ✅ Configurar docker-compose para local development
- ✅ Implementar health checks y graceful shutdown
- ✅ Deployar a Kubernetes (local o cloud)
- ✅ Setup monitoring con Prometheus
Paso 1: Docker Setup
1.1: Dockerfile optimizado
Crear Dockerfile:
# Multi-stage build para menor image size
FROM python:3.10-slim as builder
# Install build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Create venv
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# --------------------------------------------------
# Final stage (smaller image)
FROM python:3.10-slim
# Copy venv from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
# Copy source code
COPY src/ ./src/
COPY api.py .
COPY .env.example .env
# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
# Expose API port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:8000/health')"
# Run
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
Optimizaciones:
- ✅ Multi-stage build (reduce image size ~50%)
- ✅ Non-root user (security)
- ✅ Health check integrado
- ✅ Layer caching (dependencies separadas)
1.2: requirements.txt
# Core
fastapi==0.109.0
uvicorn[standard]==0.27.0
pydantic==2.6.0
# RAG components
openai==1.12.0
sentence-transformers==2.3.1
faiss-cpu==1.7.4
langchain==0.1.6
tiktoken==0.5.2
# Infrastructure
redis==5.0.1
prometheus-client==0.19.0
python-dotenv==1.0.0
# Utilities
numpy==1.26.3
scipy==1.12.0
1.3: .dockerignore
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
.venv/
# Environment
.env
.env.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# Data
data/
*.pkl
*.index
faiss_index/
# Tests
.pytest_cache/
.coverage
# Docs
docs/
*.md
README.md
Paso 2: FastAPI REST API
2.1: API completa con monitoring
Crear api.py:
"""
RAG System REST API
Production-ready FastAPI server
"""
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from typing import List, Dict, Optional
import time
import logging
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
from prometheus_client.core import CollectorRegistry
from fastapi.responses import Response
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Prometheus metrics
REQUESTS = Counter('rag_requests_total', 'Total requests', ['endpoint', 'status'])
LATENCY = Histogram('rag_request_duration_seconds', 'Request latency', ['endpoint'])
# Initialize app
app = FastAPI(
title="RAG System API",
description="Production-ready RAG system with embeddings and vector search",
version="1.0.0"
)
# Initialize RAG components (lazy loading)
from src.pipeline.rag_pipeline import RAGPipeline
from src.search.faiss_index import FAISSIndex
from src.retrieval.reranker import Reranker
class RAGService:
"""Singleton RAG service"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def initialize(self):
"""Initialize RAG components"""
if self._initialized:
return
logger.info("Initializing RAG service...")
# Load pipeline
self.pipeline = RAGPipeline(chunk_size=500, overlap=50)
# Load FAISS index
self.index = FAISSIndex(dim=1536)
self.index.load("./faiss_index")
# Load reranker
self.reranker = Reranker()
self._initialized = True
logger.info("✅ RAG service initialized")
def health_check(self) -> Dict:
"""Check service health"""
return {
'status': 'healthy' if self._initialized else 'initializing',
'index_size': self.index.index.ntotal if self._initialized else 0,
'version': '1.0.0'
}
def search(self, query: str, k: int = 5, use_reranking: bool = True) -> List[Dict]:
"""Search with optional reranking"""
if not self._initialized:
raise RuntimeError("Service not initialized")
# Generate embedding
query_emb = self.pipeline.embedder.embed(query)
# FAISS search
if use_reranking:
candidates = self.index.search(query_emb, k=k*4)
results = self.reranker.rerank(query, candidates, top_k=k)
else:
results = self.index.search(query_emb, k=k)
return results
# Global service instance
rag_service = RAGService()
# Pydantic models
class SearchRequest(BaseModel):
query: str = Field(..., description="Search query", min_length=1, max_length=1000)
k: int = Field(5, description="Number of results", ge=1, le=50)
use_reranking: bool = Field(True, description="Use cross-encoder reranking")
class SearchResult(BaseModel):
chunk_id: str
text: str
score: float
metadata: Dict
class SearchResponse(BaseModel):
results: List[SearchResult]
query: str
k: int
latency_ms: float
class HealthResponse(BaseModel):
status: str
index_size: int
version: str
# Middleware para logging
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""Log all requests"""
start_time = time.time()
response = await call_next(request)
duration = time.time() - start_time
logger.info(
f"{request.method} {request.url.path} "
f"status={response.status_code} duration={duration:.3f}s"
)
# Prometheus metrics
REQUESTS.labels(
endpoint=request.url.path,
status=response.status_code
).inc()
LATENCY.labels(endpoint=request.url.path).observe(duration)
return response
# API Endpoints
@app.on_event("startup")
async def startup_event():
"""Initialize on startup"""
rag_service.initialize()
@app.get("/", tags=["Health"])
async def root():
"""Root endpoint"""
return {
"service": "RAG System API",
"version": "1.0.0",
"status": "running"
}
@app.get("/health", response_model=HealthResponse, tags=["Health"])
async def health():
"""Health check endpoint"""
try:
health_status = rag_service.health_check()
return HealthResponse(**health_status)
except Exception as e:
logger.error(f"Health check failed: {e}")
raise HTTPException(status_code=503, detail="Service unhealthy")
@app.post("/search", response_model=SearchResponse, tags=["Search"])
async def search(request: SearchRequest):
"""
Search endpoint with vector similarity
- **query**: Search query (1-1000 characters)
- **k**: Number of results (1-50)
- **use_reranking**: Use cross-encoder reranking (slower but more accurate)
"""
start_time = time.time()
try:
# Execute search
results = rag_service.search(
query=request.query,
k=request.k,
use_reranking=request.use_reranking
)
# Format response
formatted_results = []
for r in results:
formatted_results.append(SearchResult(
chunk_id=r['chunk'].id,
text=r['chunk'].text,
score=r.get('cross_encoder_score', r['score']),
metadata=r['chunk'].metadata
))
latency_ms = (time.time() - start_time) * 1000
return SearchResponse(
results=formatted_results,
query=request.query,
k=request.k,
latency_ms=latency_ms
)
except Exception as e:
logger.error(f"Search error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/metrics", tags=["Monitoring"])
async def metrics():
"""Prometheus metrics endpoint"""
return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST)
@app.get("/stats", tags=["Monitoring"])
async def stats():
"""System statistics"""
try:
return {
'index': rag_service.index.get_stats(),
'service': rag_service.health_check()
}
except Exception as e:
logger.error(f"Stats error: {e}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Paso 3: Docker Compose para Development
3.1: docker-compose.yml
version: '3.8'
services:
# RAG API
rag-api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
- redis
volumes:
- ./faiss_index:/app/faiss_index:ro
networks:
- rag-network
restart: unless-stopped
# Redis cache
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- rag-network
restart: unless-stopped
command: redis-server --appendonly yes
# Prometheus monitoring
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
networks:
- rag-network
restart: unless-stopped
# Grafana dashboards
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
networks:
- rag-network
restart: unless-stopped
depends_on:
- prometheus
networks:
rag-network:
driver: bridge
volumes:
redis_data:
prometheus_data:
grafana_data:
3.2: prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'rag-api'
static_configs:
- targets: ['rag-api:8000']
metrics_path: '/metrics'
Paso 4: Kubernetes Deployment
4.1: deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: rag-system
labels:
app: rag
spec:
replicas: 3
selector:
matchLabels:
app: rag
template:
metadata:
labels:
app: rag
spec:
containers:
- name: rag-api
image: your-registry/rag-system:latest
ports:
- containerPort: 8000
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: api-keys
key: openai-key
- name: REDIS_HOST
value: "redis-service"
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
volumeMounts:
- name: faiss-index
mountPath: /app/faiss_index
readOnly: true
volumes:
- name: faiss-index
persistentVolumeClaim:
claimName: faiss-index-pvc
---
apiVersion: v1
kind: Service
metadata:
name: rag-service
spec:
selector:
app: rag
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: LoadBalancer
Comandos de Deployment
Local development:
# Build y start
docker-compose up --build
# Test API
curl http://localhost:8000/health
# Search
curl -X POST http://localhost:8000/search \
-H "Content-Type: application/json" \
-d '{"query": "What is Python?", "k": 5}'
# Stop
docker-compose down
Kubernetes:
# Create secret
kubectl create secret generic api-keys \
--from-literal=openai-key=sk-...
# Deploy
kubectl apply -f deployment.yaml
# Check status
kubectl get pods
kubectl get services
# Logs
kubectl logs -f deployment/rag-system
# Scale
kubectl scale deployment rag-system --replicas=5
Troubleshooting
Problema 1: Container crashes inmediatamente
Causa: FAISS index no encontrado
Solución:
# Verificar que index existe
ls -la faiss_index/
# O usar volume mount
docker run -v $(pwd)/faiss_index:/app/faiss_index rag-system
Problema 2: Health check fails
Causa: Service tarda en inicializar
Solución:
# Aumentar initialDelaySeconds en healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:8000/health')"
Resumen
En esta cápsula implementaste:
- ✅ Dockerfile multi-stage optimizado
- ✅ FastAPI REST API con Prometheus metrics
- ✅ docker-compose para local development
- ✅ Health checks y graceful shutdown
- ✅ Kubernetes deployment con autoscaling
- ✅ Monitoring con Prometheus + Grafana
Próxima cápsula: Conclusiones y Next Steps - Resumen de todo lo aprendido.
Recursos Adicionales
- FastAPI Documentation - Official docs
- Docker Best Practices - Docker guides
- Kubernetes Docs - K8s reference
- Prometheus Client - Metrics library
- Uvicorn - ASGI server
- Docker Compose - Multi-container Docker
- Production FastAPI - Deployment guide
Módulo 8 - Cápsula 07