On-Premises
Objectif & portée¶
Horodatage¶
-
Standard : ISO 8601 UTC (
Z). L'UTC+1 figure uniquement dans l'UI si nécessaire. -
Déployer SalamBot on-prem (datacenter privé ou edge) avec persistance et observabilité
- Couvrir Docker Compose et Kubernetes, incluant réseau, stockage, sécurité et sauvegardes
- Assurer compatibilité avec les runbooks et exigences SLO/SLA
- Fuseau : UTC+1 (Maroc)
Architecture on-prem (aperçu)¶
| Service | Port | Rôle |
|---|---|---|
| gateway | 80/443 (8080 interne) | Ingress/API v1 |
| orchestrateur | 8081 | Coordination |
| nlu | 8082 | LLM wrapper |
| rag | 8083 | Retrieval |
| postgres | 5432 | Métadonnées |
| redis | 6379 | Cache/queues |
| qdrant | 6333 | Vector DB |
| minio | 9000/9001 | Objets (KB) / Console |
| prometheus | 9090 | Métriques |
| grafana | 3001 | Dashboards |
| otel-collector | 4317/4318 | OTLP gRPC/HTTP |
Architecture réseau on-premise¶
Le diagramme suivant illustre la topologie réseau complète pour un déploiement on-premise :
graph TB
subgraph "Zone DMZ (Front)"
LB[Load Balancer<br/>80/443]
FW[Firewall<br/>WAF]
end
subgraph "Zone Application"
GW[Gateway<br/>:8080]
ORC[Orchestrateur<br/>:8081]
NLU[NLU<br/>:8082]
RAG[RAG<br/>:8083]
end
subgraph "Zone Données"
PG[(PostgreSQL<br/>:5432)]
RD[(Redis<br/>:6379)]
QD[(Qdrant<br/>:6333)]
MN[(MinIO<br/>:9000/:9001)]
end
subgraph "Zone Observabilité"
PR[Prometheus<br/>:9090]
GR[Grafana<br/>:3001]
OT[OTel Collector<br/>:4317/:4318]
end
subgraph "Réseau Externe"
USR[Utilisateurs]
WH[Webhooks<br/>WhatsApp/FB]
API[APIs Externes<br/>OpenAI/Ollama]
end
%% Flux utilisateurs
USR -->|HTTPS| LB
WH -->|HTTPS| LB
LB --> FW
FW -->|Port 8080| GW
%% Flux internes app
GW --> ORC
ORC --> NLU
ORC --> RAG
%% Accès données
ORC --> PG
ORC --> RD
RAG --> QD
RAG --> MN
%% Observabilité
GW --> OT
ORC --> OT
NLU --> OT
RAG --> OT
OT --> PR
PR --> GR
%% Accès externes
NLU -.->|HTTPS| API
%% Accès admin
USR -.->|Port 3001<br/>Restreint| GR
USR -.->|Port 9090<br/>Restreint| PR
USR -.->|Port 9001<br/>Restreint| MN
style LB fill:#e3f2fd
style FW fill:#fff3e0
style GW fill:#e8f5e8
style PG fill:#fce4ec
style RD fill:#fce4ec
style QD fill:#fce4ec
style MN fill:#fce4ec
style PR fill:#f3e5f5
style GR fill:#f3e5f5
Prérequis & sizing¶
⚠️ Sécurité prod on-prem
Interdire les credentials par défaut, imposer TLS 1.2+ (idéalement 1.3), restreindre Grafana/Prometheus/MinIO (réseau/SSO), stocker les secrets dans Vault/KMS, et auditer tout accès d'admin.
- Hôte (min) : 4 vCPU, 16 Go RAM, SSD 100 Go ; prod : 8+ vCPU, 32+ Go, SSD NVMe
- Réseau : 1 Gbps interne, sorties Internet optionnelles (MAJ/registries)
- DNS interne + certificats TLS (AC interne ou ACME)
- Comptes de service/accès root restreints ; NTP synchronisé
Réseau & sécurité¶
- Ingress : Traefik ou Nginx ; TLS 1.2+ (préférer 1.3), HSTS, mTLS interne optionnel
- Pare-feu : ouvrir 80/443 (ingress), 3001 (Grafana), 9090 (Prometheus, restreint), 9001 (MinIO console, restreint)
- Zonage réseau : front (DMZ) ↔ app ↔ data ; pas d'accès direct aux DB depuis DMZ
- Headers requis : Authorization, X-SalamBot-Tenant, X-Request-Id ; signatures webhooks (HMAC)
Stockage & persistance¶
- PostgreSQL : volume dédié, WAL archiving optionnel ; sauvegardes quotidiennes
- MinIO : buckets par tenant ; politiques IAM minimales ; versioning conseillé
- Qdrant : snapshots activés ; plan de restauration testé
- Backups offsite chiffrés (AES-256), clés KMS/Vault
Secrets & IAM¶
- Stockage dans Vault/KMS on-prem ; pas de secrets en clair dans git/logs
- Rotation ≤90j (JWT/webhooks/API keys) ; accès par rôles (RBAC+ABAC)
- Comptes break-glass en coffre avec journalisation et expiration
Déploiement — Docker Compose¶
services:
gateway:
ports: ['8080:8080']
env_file: [.env]
depends_on: [orchestrateur]
orchestrateur:
depends_on: [postgres, redis]
nlu:
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
rag:
depends_on: [qdrant, minio]
postgres:
volumes: ['pgdata:/var/lib/postgresql/data']
redis:
command: ['redis-server', '--save', '', '--appendonly', 'no']
qdrant:
volumes: ['qdrant:/qdrant/storage']
minio:
environment:
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
command: ['server', '/data', '--console-address', ':9001']
prometheus:
ports: ['9090:9090']
grafana:
ports: ['3001:3000']
volumes:
pgdata: {}
qdrant: {}
Ajout du service OpenTelemetry Collector¶
services:
otel-collector:
image: otel/opentelemetry-collector:0.95.0
command: ['--config=/etc/otel/config.yaml']
volumes:
- ./ops/otel/config.yaml:/etc/otel/config.yaml:ro
ports:
- '4317:4317'
- '4318:4318'
Les variables
OTEL_EXPORTER_OTLP_*des services pointent surotel-collector:4318.
Déploiement — Kubernetes (on-prem)¶
- CNI : Calico/Cilium ; StorageClass : CSI (NFS/ceph/RBD) avec RWO/RWX
- IngressController : Traefik/Nginx ; cert-manager pour TLS
- Namespace : salambot ; ressources avec requests/limits ; PodSecurity standards
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway
namespace: salambot
spec:
replicas: 2
selector: { matchLabels: { app: gateway } }
template:
metadata: { labels: { app: gateway } }
spec:
containers:
- name: gateway
image: salambot/gateway:latest
ports: [{ containerPort: 8080 }]
envFrom: [{ secretRef: { name: salambot-secrets } }]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gateway
namespace: salambot
spec:
tls: [{ hosts: [salambot.local], secretName: tls-salambot }]
rules:
- host: salambot.local
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: gateway, port: { number: 8080 } } }
Service manquant pour l'Ingress¶
apiVersion: v1
kind: Service
metadata:
name: gateway
namespace: salambot
spec:
selector:
app: gateway
ports:
- name: http
port: 8080
targetPort: 8080
Observabilité on-prem¶
- OTel Collector → Prometheus/Grafana/Loki/Tempo ; exporter OTLP (4317/4318)
- Dashboards : latence E2E/TTFB, NLU, RAG, erreurs ; alertes burn-rate SLO
- Journalisation : pas de PII ; corrélation trace_id/span_id/correlation_id
Configuration .env.on-prem¶
TENANT=demo
ENV=on-prem
# DB/Cache (Local)
POSTGRES_URL=postgres://salambot:${POSTGRES_PASSWORD}@postgres:5432/salambot
REDIS_URL=redis://redis:6379
# Vector DB
QDRANT_URL=http://qdrant:6333
QDRANT_API_KEY=${QDRANT_API_KEY}
# Objets (MinIO)
MINIO_ENDPOINT=http://minio:9000
MINIO_ROOT_USER=${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
S3_BUCKET=salambot-kb
# OTel → Local stack
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_SERVICE_NAME=salambot-onprem
# Auth (RS256 recommandé)
JWT_ALG=RS256
JWT_PRIVATE_KEY_PATH=/secrets/jwt/private.pem
JWT_PUBLIC_KEY_PATH=/secrets/jwt/public.pem
JWT_AUDIENCE=gateway
JWT_ISSUER=onprem-idp
# LLM
OPENAI_API_KEY=${OPENAI_API_KEY}
# Webhooks
PUBLIC_BASE_URL=https://salambot.local
WEBHOOK_SECRET=${WEBHOOK_SECRET}
Mise en route¶
# Docker Compose
cp .env.on-prem.example .env
cp docker-compose.override.on-prem.yml docker-compose.override.yml
docker compose up -d
# Kubernetes
kubectl create namespace salambot
kubectl apply -f k8s/secrets.yaml -n salambot
kubectl apply -f k8s/ -n salambot
# Health check
curl -f http://localhost:8080/health
DNS local : ajoutez
127.0.0.1 salambot.localdans/etc/hostssi vous utilisez l'Ingress avecsalambot.local.
Seed & données initiales¶
# Créer buckets MinIO
mc alias set local http://localhost:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD}
mc mb local/salambot-kb
# Uploader documents KB
mc cp --recursive ./kb-docs/ local/salambot-kb/demo/
# Indexer dans Qdrant
docker compose exec orchestrateur python -m salambot.jobs.seed_kb \
--tenant demo --incremental --vector qdrant
Tests fumée on-prem¶
# Health check
curl -f http://localhost:8080/health
# Test génération (canonique)
curl -X POST http://salambot.local/v1/generate/reply \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer ${JWT_TOKEN}" \\
-H "X-SalamBot-Tenant: demo" \\
-d '{
"schema_version":"1.0",
"tenant":"demo",
"channel":"webchat",
"message_id":"msg_smoke",
"correlation_id":"corr_smoke",
"timestamp":"2025-08-14T10:00:00Z",
"locale":"fr-MA",
"data":{"prompt":"Bonjour SalamBot","context":[]}
}'
# Test RAG (canonique)
curl -X POST http://localhost:8080/v1/search/query \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer ${JWT_TOKEN}" \\
-H "X-SalamBot-Tenant: demo" \\
-d '{"query":"politique remboursement","tenant":"demo","top_k":3}'
# Health check
curl -f http://localhost:8080/health
# Test génération
curl -X POST http://salambot.local/v1/generate/reply \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${JWT_TOKEN}" \
-H "X-SalamBot-Tenant: demo" \
-d '{
"schema_version": "1.0",
"tenant": "demo",
"channel": "webchat",
"message_id": "msg_smoke",
"correlation_id": "corr_smoke",
"timestamp": "2025-01-16T10:00:00Z",
"locale": "fr-MA",
"data": {"prompt": "Bonjour SalamBot", "context": []}
}'
# Test RAG
curl -X POST http://localhost:8080/v1/search/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${JWT_TOKEN}" \
-H "X-SalamBot-Tenant: demo" \
-d '{"query": "politique remboursement", "tenant": "demo", "top_k": 3}'
Troubleshooting on-prem¶
Connectivité interne¶
- PostgreSQL : vérifier
docker compose logs postgresoukubectl logs -l app=postgres - Redis : tester avec
redis-cli -h localhost -p 6379 ping - Qdrant :
curl http://localhost:6333/collections - MinIO : console web
http://localhost:9001
Performance¶
- Latence DB : optimiser index PostgreSQL, pool connections
- Vector search : ajuster
ef_constructetmdans Qdrant - Cache Redis : TTL et éviction policy selon usage
- Stockage : IOPS SSD, monitoring I/O wait
Sécurité¶
- Certificats : renouvellement automatique via cert-manager
- Secrets : rotation via Vault/External Secrets Operator
- Réseau : NetworkPolicies Kubernetes, iptables Docker
- Audit : logs d'accès Traefik/Nginx, events Kubernetes
Sauvegardes & restauration¶
PostgreSQL¶
# Sauvegarde
docker compose exec postgres pg_dump -U salambot salambot > backup.sql
# Restauration
docker compose exec -T postgres psql -U salambot salambot < backup.sql
Qdrant¶
# Snapshot
curl -X POST http://localhost:6333/collections/salambot-kb/snapshots
# Télécharger
curl http://localhost:6333/collections/salambot-kb/snapshots/\
snapshot-2025-01-16.snapshot \
-o qdrant-backup.snapshot
MinIO¶
# Sync vers backup externe
mc mirror local/salambot-kb/ backup-site/salambot-kb/
Monitoring & alertes¶
Métriques clés¶
- Latence : p95 < 2s (génération), p95 < 500ms (recherche)
- Disponibilité : uptime > 99.5%
- Erreurs : taux < 1%
- Ressources : CPU < 80%, RAM < 85%, disque < 90%
Dashboards Grafana¶
- SalamBot Overview : santé globale, SLI/SLO
- Application : latence par endpoint, erreurs, throughput
- Infrastructure : CPU/RAM/disque/réseau par service
- Business : requêtes par tenant, top queries RAG
Mise à jour & maintenance¶
Rolling update (Kubernetes)¶
kubectl set image deployment/gateway \
gateway=salambot/gateway:v1.2.0 -n salambot
kubectl rollout status deployment/gateway -n salambot
Blue-green (Docker Compose)¶
# Déployer nouvelle version
docker compose -f docker-compose.blue.yml up -d
# Basculer le trafic (load balancer)
# Arrêter ancienne version
docker compose -f docker-compose.green.yml down
Sécurité & conformité¶
- Chiffrement : TLS 1.3 en transit, AES-256 au repos
- Authentification : JWT + mTLS interne, rotation clés
- Autorisation : RBAC Kubernetes, IAM MinIO
- Audit : logs centralisés, corrélation trace_id
- PII : anonymisation, pas de logs sensibles
Coûts & optimisation¶
- Ressources : requests/limits Kubernetes, autoscaling HPA
- Stockage : compression Qdrant, lifecycle MinIO
- Réseau : cache Redis, CDN statiques
- Observabilité : rétention logs/métriques, sampling traces
Seed & données¶
docker compose exec minio mc alias set local http://minio:9000 \
"$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
docker compose exec minio mc mb -p local/salambot-kb || true
docker compose exec orchestrateur python -m salambot.jobs.seed_kb --tenant demo --incremental
Tests fumée¶
TOKEN="dev-token" TENANT="demo"
# Health
curl -H "Authorization: Bearer $TOKEN" \
-H "X-SalamBot-Tenant: $TENANT" http://localhost:8080/health
# Génération
curl -X POST http://localhost:8080/v1/generate/reply \
-H 'Content-Type: application/json' -d '{
"schema_version":"1.0","tenant":"demo","channel":"webchat",
"message_id":"msg_smoke","correlation_id":"corr_smoke","timestamp":"2025-08-14T10:00:00Z","locale":"fr-MA",
"data":{"prompt":"Bonjour","context":[]}}
'
# Recherche RAG
curl -X POST http://localhost:8080/v1/search/query \
-H 'Content-Type: application/json' \
-d '{"query":"test","tenant":"demo","top_k":3}'
Sauvegarde & restauration¶
- PostgreSQL: pg_dump quotidien + rotation; restauration test mensuelle.
- MinIO: mirror vers stockage WORM; versions/snapshots activés.
- Qdrant: snapshots programmés; procédure de recover documentée.
Troubleshooting¶
- Ports occupés: ajuster mapping; vérifier SELinux/AppArmor.
- Lenteur RAG: vérifier Qdrant et index présents; réduire top_k.
- TLS: vérifier chaîne cert + horloge NTP; refuser ciphers faibles.
- Secrets: valider injection (Vault/Secrets K8s) et permissions.
Checklists go-live¶
- Dashboards/alertes actifs; budgets SLO (voir Ops/slo-sla.md) configurés.
- Backups vérifiés et restauration testée.
- Comptes admin et accès break-glass contrôlés.
- Runbooks d'incident imprimés/partagés (P0/P1/P2).