Ollama: Implantação e Otimização do Projeto
Implantação e otimização é a cerimônia de formatura do SupportBot — do desenvolvimento à produção, do funcional ao excelente.
curl.
📋 Pré-requisitos: Você deve dominar o seguinte primeiro
1. O Que Você Vai Aprender
- Configuração Docker Compose de produção: Ollama + FastAPI + Chroma + Nginx
- Benchmarking de desempenho: TTFT / Tokens/s / otimização de capacidade concorrente
- Fortalecimento de segurança: Autenticação por API Key, rate limiting, filtragem de saída
- Integração de monitoramento: Coleta de métricas Prometheus + dashboards Grafana
- Checklist de entrada em produção e runbook de operações
2. Contexto do Projeto
.env ou um Secrets Manager, nunca codificadas no código ou arquivos Docker Compose. Adicione .env ao .gitignore; forneça apenas um template .env.example no repositório.
(1) Questões-Chave Pré-Implantação
O SupportBot da Alice está desenvolvido mas enfrenta desafios de implantação:
| Questão | Risco | Solução |
|---|---|---|
| Implantação em máquina única, sem redundância | Interrupção do serviço | Docker Compose multi-serviço |
| Sem autenticação, API exposta | Vulnerabilidade de segurança | Nginx + API Key |
| Pontos cegos de monitoramento | Detecção lenta de falhas | Prometheus + Grafana |
| Desempenho não verificado | Latência excede o alvo | Benchmarking + ajuste |
| Sem documentação de operações | Recuperação lenta de falhas | Runbook de operações |
3. Configuração Docker Compose de Produção
OLLAMA_HOST=0.0.0.0 do Ollama só é seguro dentro da rede interna Docker; a porta 11434 nunca deve ser mapeada diretamente para a internet pública. Nginx é o único ponto de entrada externo e deve ser configurado com autenticação por API Key e HTTPS.
(1) Arquitetura de Produção
flowchart TD
A[Internet<br/>:443] --> B[Nginx<br/>SSL + Auth + Rate Limit]
B --> C[FastAPI SupportBot<br/>:8000]
C --> D[Ollama<br/>:11434<br/>GPU Accelerated]
C --> E[Chroma<br/>:8001<br/>Vector Store]
F[Prometheus<br/>:9090] --> G[All Services<br/>Metrics Collection]
G --> H[Grafana<br/>:3001<br/>Dashboards]
(2) Lista de Serviços
| Serviço | Imagem | Porta | Recursos | Persistência |
|---|---|---|---|---|
| nginx | nginx:alpine | 80/443 | 1 vCPU, 512MB | Arquivos de configuração |
| supportbot | custom | 8000 | 2 vCPU, 4GB | — |
| ollama | ollama/ollama | 11434 | 8 vCPU, 16GB, 1x GPU | ollama_data |
| chroma | chromadb/chroma | 8001 | 2 vCPU, 4GB | chroma_data |
| prometheus | prom/prometheus | 9090 | 1 vCPU, 2GB | prometheus_data |
| grafana | grafana/grafana | 3001 | 1 vCPU, 1GB | grafana_data |
▶ Exemplo 1: Docker Compose de Produção
# docker-compose.prod.yml
version: "3.8"
services:
nginx:
image: nginx:alpine
container_name: supportbot-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/ssl/certs:ro
depends_on:
supportbot:
condition: service_healthy
restart: unless-stopped
supportbot:
build:
context: ./app
dockerfile: Dockerfile
container_name: supportbot-api
ports:
- "8000:8000"
environment:
- OLLAMA_HOST=http://ollama:11434
- CHROMA_HOST=http://chroma:8000
- API_KEY=${SUPPORTBOT_API_KEY}
- CHAT_MODEL=qwen2.5
- CLASSIFIER_MODEL=llama3.2:3b
- EMBED_MODEL=nomic-embed-text
depends_on:
ollama:
condition: service_healthy
chroma:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 15s
timeout: 5s
retries: 3
restart: unless-stopped
ollama:
image: ollama/ollama
container_name: supportbot-ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
environment:
- OLLAMA_HOST=0.0.0.0:11434
- OLLAMA_NUM_PARALLEL=4
- OLLAMA_KEEP_ALIVE=30m
- OLLAMA_MAX_LOADED_MODELS=2
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:11434/api/tags"]
interval: 30s
timeout: 10s
retries: 5
restart: unless-stopped
chroma:
image: chromadb/chroma
container_name: supportbot-chroma
ports:
- "8001:8000"
volumes:
- chroma_data:/chroma/chroma
environment:
- ANONYMIZED_TELEMETRY=FALSE
restart: unless-stopped
prometheus:
image: prom/prometheus
container_name: supportbot-prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
restart: unless-stopped
grafana:
image: grafana/grafana
container_name: supportbot-grafana
ports:
- "3001:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
depends_on: [prometheus]
restart: unless-stopped
volumes:
ollama_data:
chroma_data:
prometheus_data:
grafana_data:
admin — você deve alterá-la imediatamente após o primeiro login! Em produção, defina uma senha forte via variável de ambiente GRAFANA_PASSWORD, ex.: GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}, e configure um valor de senha gerado aleatoriamente no arquivo .env.
Saída:
Docker Compose production deployment successful, all services healthy
▶ Exemplo 2: Configuração Nginx de Produção
# nginx/nginx.conf
worker_processes auto;
events {
worker_connections 1024;
}
http {
# Rate limiting zone
limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;
upstream supportbot {
server supportbot:8000;
}
# Redirect HTTP to HTTPS
server {
listen 80;
return 301 https://$host$request_uri;
}
# HTTPS server
server {
listen 443 ssl;
server_name ai.globalshop.example.com;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/certs/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
# SupportBot API
location /v1/ {
limit_req zone=api burst=20 nodelay;
# API Key authentication
if ($http_x_api_key = "") {
return 401 '{"error": "API key required"}';
}
proxy_pass http://supportbot;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
}
# Health check (no auth)
location /health {
proxy_pass http://supportbot/health;
}
}
}
Saída:
// Execution successful
4. Benchmarking e Otimização de Desempenho
OLLAMA_KEEP_ALIVE controla o tempo de residência do modelo.
(1) Alvos-Chave de Desempenho
| Métrica | Alvo | Método de Teste |
|---|---|---|
| TTFT | < 500ms | Medição de latência do primeiro token |
| Latência P95 | < 3s | 100 requisições, pegar P95 |
| Throughput | > 20 QPS | Teste concorrente |
| Taxa de resolução automática | > 80% | Avaliação com 100 perguntas reais |
▶ Exemplo 3: Script de Benchmark
# benchmark.py
import asyncio
import aiohttp
import time
import json
import statistics
import os
API_URL = "http://localhost:8000/v1/chat"
API_KEY = os.environ.get("SUPPORTBOT_API_KEY", "your-api-key-here")
TEST_QUESTIONS = [
"What is the return policy?",
"How long does shipping take?",
"Combien coûte la livraison?",
"I received a damaged item, what do I do?",
"Compare the wireless headphones models",
"Where is order #88765?",
"What is the return policy?",
"My product broke after 2 weeks, I want a refund!",
"Do you ship to Germany?",
"What is the warranty for electronics?",
]
async def single_request(session: aiohttp.ClientSession, question: str) -> dict:
start = time.time()
try:
async with session.post(
API_URL,
json={"message": question},
headers={"X-Api-Key": API_KEY},
timeout=aiohttp.ClientTimeout(total=30)
) as response:
data = await response.json()
elapsed = time.time() - start
return {
"question": question[:40],
"latency_s": round(elapsed, 2),
"intent": data.get("intent", "?"),
"handler": data.get("handler", "?"),
"language": data.get("language", "?"),
"status": "success" if response.status == 200 else "error"
}
except Exception as e:
return {"question": question[:40], "latency_s": round(time.time() - start, 2),
"status": "error", "error": str(e)}
async def run_benchmark(concurrency: int = 1, total_requests: int = 50) -> dict:
async with aiohttp.ClientSession() as session:
# Warm up
await single_request(session, "Hello")
# Run benchmark
start = time.time()
tasks = []
for i in range(total_requests):
q = TEST_QUESTIONS[i % len(TEST_QUESTIONS)]
tasks.append(single_request(session, q))
if len(tasks) >= concurrency:
results = await asyncio.gather(*tasks)
tasks = []
if tasks:
results += await asyncio.gather(*tasks)
total_time = time.time() - start
latencies = [r["latency_s"] for r in results if r["status"] == "success"]
errors = sum(1 for r in results if r["status"] == "error")
return {
"concurrency": concurrency,
"total_requests": total_requests,
"total_time_s": round(total_time, 1),
"errors": errors,
"error_rate": round(errors / total_requests * 100, 1),
"avg_latency_s": round(statistics.mean(latencies), 2) if latencies else 0,
"p50_latency_s": round(statistics.median(latencies), 2) if latencies else 0,
"p95_latency_s": round(sorted(latencies)[int(len(latencies) * 0.95)], 2) if latencies else 0,
"max_latency_s": round(max(latencies), 2) if latencies else 0,
"throughput_rps": round(total_requests / total_time, 1)
}
if __name__ == "__main__":
print("=== SupportBot Benchmark ===")
for c in [1, 4, 8]:
result = asyncio.run(run_benchmark(concurrency=c, total_requests=20))
print(f"\nConcurrency {c}:")
for k, v in result.items():
print(f" {k}: {v}")
Saída:
=== SupportBot Benchmark ===
5. Fortalecimento de Segurança
curl.
(1) Checklist de Segurança
| Item de Verificação | Configuração | Status |
|---|---|---|
| Autenticação por API Key | Validação Nginx X-Api-Key | ✅ |
| Criptografia HTTPS | Configuração Nginx SSL | ✅ |
| Rate limiting | 60 req/min/IP | ✅ |
| Filtragem de entrada | Detecção de injeção de Prompt | ✅ |
| Filtragem de saída | Filtragem de conteúdo sensível | ✅ |
| Sanitização de dados | Remoção automática de PII | ✅ |
| Rede interna do Ollama | Binding 127.0.0.1 | ✅ |
▶ Exemplo 4: Middleware de Segurança Integrado
# security.py - FastAPI security middleware
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
import re
import time
from collections import defaultdict
class SecurityMiddleware:
def __init__(self, api_key: str, rate_limit: int = 60):
self.api_key = api_key
self.rate_limit = rate_limit
self.request_counts: dict = defaultdict(list)
self.injection_patterns = [
r"ignore\s+(previous|all)\s+instructions",
r"you\s+are\s+now\s+DAN",
r"show\s+(me\s+)?(your\s+)?system\s+prompt",
r"reveal\s+(your|the)\s+(initial|system)",
]
self.output_patterns = [
r"system\s*prompt[:\s]",
r"RETURN_POLICY_INTERNAL",
r"INTERNAL_PRICING",
]
def check_api_key(self, request: Request) -> bool:
key = request.headers.get("X-Api-Key", "")
return key == self.api_key
def check_rate_limit(self, client_ip: str) -> bool:
now = time.time()
self.request_counts[client_ip] = [
t for t in self.request_counts[client_ip]
if now - t < 60
]
if len(self.request_counts[client_ip]) >= self.rate_limit:
return False
self.request_counts[client_ip].append(now)
return True
def check_input(self, text: str) -> tuple[bool, str]:
for pattern in self.injection_patterns:
if re.search(pattern, text, re.IGNORECASE):
return False, "Potential prompt injection detected"
return True, ""
def check_output(self, text: str) -> tuple[bool, str]:
for pattern in self.output_patterns:
if re.search(pattern, text, re.IGNORECASE):
return False, "Sensitive content in response filtered"
return True, ""
def sanitize_pii(self, text: str) -> str:
text = re.sub(r"[\w.-]+@[\w.-]+\.\w+", "[EMAIL_REDACTED]", text)
text = re.sub(r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b", "[PHONE_REDACTED]", text)
text = re.sub(r"\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b", "[CC_REDACTED]", text)
return text
Saída:
# Function defined successfully
6. Integração de Monitoramento
(1) Métricas do Dashboard Grafana
| Painel | Métrica | Unidade | Alerta |
|---|---|---|---|
| Latência de requisição | supportbot_request_duration_seconds | s | P95 > 3s |
| Taxa de requisições | supportbot_requests_total | req/min | < 5 req/min |
| Taxa de erro | supportbot_errors_total | % | > 5% |
| Utilização de GPU | DCGM_FI_DEV_GPU_UTIL | % | > 95% |
| Uso de VRAM | DCGM_FI_DEV_FB_USED | MB | > 95% |
| Distribuição de intenção | supportbot_intent_count | count | — |
▶ Exemplo 5: Configuração Prometheus
# monitoring/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "supportbot"
static_configs:
- targets: ["supportbot:8000"]
metrics_path: /metrics
- job_name: "ollama"
static_configs:
- targets: ["ollama:11434"]
metrics_path: /metrics
scheme: http
- job_name: "node"
static_configs:
- targets: ["node-exporter:9100"]
rule_files:
- "alert_rules.yml"
# alert_rules.yml
groups:
- name: supportbot_alerts
rules:
- alert: HighLatency
expr: histogram_quantile(0.95, rate(supportbot_request_duration_seconds_bucket[5m])) > 3
for: 5m
labels:
severity: warning
annotations:
summary: "SupportBot P95 latency above 3s"
- alert: ServiceDown
expr: up{job="supportbot"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "SupportBot service is down"
- alert: HighErrorRate
expr: rate(supportbot_errors_total[5m]) / rate(supportbot_requests_total[5m]) > 0.05
for: 3m
labels:
severity: warning
annotations:
summary: "SupportBot error rate above 5%"
Saída:
# Prometheus configuration loaded successfully
# Target status: 3 scrape targets all UP
# supportbot (UP) | ollama (UP) | node-exporter (UP)
7. Exemplo Abrangente: Checklist de Entrada em Produção e Runbook de Operações
# ============================================
# Comprehensive: Production deployment checklist
# Final verification before going live
# ============================================
PRODUCTION_CHECKLIST = {
"infrastructure": {
"items": [
"Docker Compose services all running (6/6)",
"Ollama health check passing",
"Chroma health check passing",
"FastAPI health check passing",
"Nginx SSL certificate valid (not expired)",
"GPU acceleration verified (nvidia-smi inside container)",
],
"commands": [
"docker compose ps",
"curl -f http://localhost:11434/api/tags",
"curl -f http://localhost:8001/api/v1/heartbeat",
"curl -f http://localhost:8000/health",
"curl -f https://ai.globalshop.example.com/health",
]
},
"models": {
"items": [
"qwen2.5:7b pulled and verified",
"llama3.2:3b pulled and verified",
"nomic-embed-text pulled and verified",
"supportbot Modelfile created and tested",
],
"commands": [
"docker exec supportbot-ollama ollama list",
"docker exec supportbot-ollama ollama run supportbot 'Hello'",
]
},
"security": {
"items": [
"API Key authentication working (unauthorized request returns 401)",
"Rate limiting working (burst above limit returns 429)",
"Input injection filter tested",
"Output content filter tested",
"PII sanitization verified",
"Ollama not accessible from internet (port 11434 blocked)",
]
},
"performance": {
"items": [
"Single request latency < 3s (P95)",
"Concurrent 4 requests latency < 5s (P95)",
"Throughput > 10 QPS",
"RAG retrieval relevant (top-3 precision > 80%)",
"Intent classification accuracy > 85%",
]
},
"monitoring": {
"items": [
"Prometheus scraping all services",
"Grafana dashboards created and visible",
"Alert rules configured and tested",
"Log aggregation working",
]
},
"disaster_recovery": {
"items": [
"All configs in Git repository",
"Chroma data backup scheduled (daily)",
"Model files backed up on NFS/S3",
"Recovery procedure documented and tested",
]
}
}
def generate_runbook() -> str:
"""Generate operations runbook."""
runbook = [
"# SupportBot Operations Runbook\n",
"## Service Overview",
"- SupportBot API: https://ai.globalshop.example.com/v1/chat",
"- Health Check: https://ai.globalshop.example.com/health",
"- Grafana: http://localhost:3001 ⚠️ Change default admin password immediately!",
"- Prometheus: http://localhost:9090\n",
"## Common Operations\n",
"### Restart a Service",
"```bash",
"docker compose restart supportbot # Restart API",
"docker compose restart ollama # Restart Ollama",
"```\n",
"### Pull New Model",
"```bash",
"docker exec supportbot-ollama ollama pull model_name",
"```\n",
"### Rebuild Knowledge Base",
"```bash",
"docker exec supportbot-api python indexer.py /app/product_docs",
"```\n",
"### Check Logs",
"```bash",
"docker compose logs -f supportbot # API logs",
"docker compose logs -f ollama # Ollama logs",
"```\n",
"### Emergency: Service Down",
"1. Check: `docker compose ps`",
"2. Restart: `docker compose restart <service>`",
"3. Verify: `curl https://ai.globalshop.example.com/health`",
"4. If Ollama OOM: restart with `OLLAMA_NUM_PARALLEL=2`",
"5. Escalate: notify ops team\n",
"## Performance Baseline",
"| Metric | Target | Current |",
"|:-------|:-------|:--------|",
"| P95 Latency | < 3s | _fill after benchmark_ |",
"| Throughput | > 10 QPS | _fill after benchmark_ |",
"| Error Rate | < 1% | _fill after benchmark_ |",
"| GPU Util | < 85% | _fill after benchmark_ |",
]
return "\n".join(runbook)
# Generate and save
if __name__ == "__main__":
print(generate_runbook())
with open("RUNBOOK.md", "w") as f:
f.write(generate_runbook())
print("\nRunbook saved: RUNBOOK.md")
❓ Perguntas Frequentes
P: Quanto tempo leva a primeira inicialização após a implantação? R: O download de modelos leva cerca de 10-30 minutos (dependendo da velocidade da rede e tamanho do modelo). Reinicializações subsequentes levam cerca de 30 segundos (modelos em cache no Volume). Baixe modelos com antecedência.
P: Como obtenho certificados SSL para produção? R: Use certbot para certificados Let's Encrypt gratuitos, ou Caddy para HTTPS automático. Certificados autoassinados funcionam para redes internas.
P: Como verifico que a configuração de segurança está realmente funcionando? R: 1) Requisições sem API Key devem retornar 401; 2) Requisições com rate limit devem retornar 429; 3) Porta 11434 não deve ser externamente acessível; 4) Ataques de injeção devem ser bloqueados. Teste cada um.
P: Quanto tempo leva para configurar dashboards de monitoramento? R: Dashboards básicos cerca de 1 hora (importar template Node Exporter + painel Ollama personalizado). Regras de alerta cerca de 30 minutos. Total cerca de 2 horas.
P: Como otimizar continuamente o SupportBot após entrar em produção? R: 1) Analise logs para respostas de baixa qualidade; 2) Adicione documentos à base de conhecimento; 3) Ajuste a classificação de intenção; 4) Expanda exemplos MESSAGE do Modelfile; 5) Re-execute benchmarks regularmente.
P: Como faço rollback para uma versão anterior? R: Git checkout dos arquivos de configuração → docker compose down → docker compose up -d. Dados de modelos em Volumes não são afetados. Dados Chroma podem ser restaurados de backup.
📖 Resumo
- Configuração Docker Compose de produção inclui 6 serviços: Nginx + FastAPI + Ollama + Chroma + Prometheus + Grafana
- Alvos de desempenho: P95 < 3s, throughput > 10 QPS, taxa de resolução automática > 80%
- Fortalecimento de segurança: API Key + HTTPS + rate limiting + filtragem de entrada/saída + sanitização de dados
- Integração de monitoramento: Coleta Prometheus + visualização Grafana + alertas Alertmanager
- Checklist de Entrada em Produção cobre 6 áreas: infraestrutura, modelos, segurança, desempenho, monitoramento, recuperação de desastres
- Runbook de operações inclui operações diárias, troubleshooting, baselines de desempenho e procedimentos de emergência
📝 Exercícios
- Básico (⭐): Implante uma versão mínima do SupportBot usando Docker Compose (Ollama + FastAPI), e verifique que o endpoint /health retorna OK.
- Intermediário (⭐⭐): Execute o script de benchmark, registre latência P95, throughput e taxa de erro, e compare com os alvos. Se não atingir os alvos, ajuste OLLAMA_NUM_PARALLEL ou num_ctx e reteste.
- Avançado (⭐⭐⭐): Complete a implantação de produção completa do SupportBot — incluindo configuração de segurança Nginx + monitoramento Prometheus/Grafana + regras de alerta + runbook de operações — e execute a verificação completa do Checklist de entrada em produção.