Runbooks on-call
Pour qui : SRE / astreinte / support N2 VitaKYC. Doctrine : chaque runbook doit permettre à un ingénieur non spécialiste de résoudre l’incident en suivant les étapes.
1. Modèle d’incident
Section intitulée « 1. Modèle d’incident »1.1 Severities
Section intitulée « 1.1 Severities »| Sev | Définition | SLA réponse | Escalade immédiate |
|---|---|---|---|
| SEV-1 | Service inaccessible pour ≥ 10 % des tenants OU perte de données confirmée OU violation de sécurité active | 5 min | CTO + CEO + Compliance officer |
| SEV-2 | Fonctionnalité critique dégradée (KYC / AML / TCR impossible) OU > 2× SLO breach | 15 min | Tech Lead + CTO |
| SEV-3 | Dégradation partielle, contournement possible | 1 h | Tech Lead |
| SEV-4 | Impact mineur, remédiation planifiable | 1 j ouvré | — |
1.2 Cycle de vie
Section intitulée « 1.2 Cycle de vie »1.3 Checklist d’intervention
Section intitulée « 1.3 Checklist d’intervention »Pour chaque incident : ouvrir un channel dédié #inc-YYYYMMDD-short-name, désigner un Incident Commander, un Communications Lead, et un Scribe. Horodater chaque action dans le channel.
1.4 Escalade
Section intitulée « 1.4 Escalade »| Niveau | Rôle | Délai max avant escalade |
|---|---|---|
| L1 | On-call engineer | 15 min de diagnostic sans progrès → L2 |
| L2 | Tech Lead / Architecte | 30 min sans remédiation → L3 |
| L3 | CTO | SEV-1 dès confirmation |
| L4 | CEO + Compliance officer | SEV-1 impactant clients ou régulateurs |
2. Catalogue des alertes (Prometheus + PagerDuty)
Section intitulée « 2. Catalogue des alertes (Prometheus + PagerDuty) »| Code alerte | Condition Prometheus | Sev |
|---|---|---|
ApiHighLatencyP95 | histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 0.6 pendant 5 min | SEV-3 |
ApiErrorRateHigh | rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.02 | SEV-2 |
OcrServiceDown | up{job="ocr-svc"} == 0 pendant 2 min | SEV-2 |
KafkaConsumerLagHigh | kafka_consumer_lag > 10000 pendant 10 min | SEV-3 |
PostgresConnectionsExhausted | pg_stat_activity_count > 0.85 × max_connections | SEV-2 |
PostgresReplicationLag | pg_replication_lag_bytes > 100 MB | SEV-2 |
RneBalanceLow | rne_subscription_balance < 1000 pour un tenant | SEV-4 |
IdesSubmissionFailure | tcr_ides_submission_failed_total incrémenté | SEV-3 |
AmlListIngestStale | aml_list_last_ingested_seconds_ago > 86400 | SEV-3 |
CertificateExpiringSoon | cert_expiry_days < 14 | SEV-4 |
AuditHashChainBroken | audit_chain_integrity_failed_total > 0 | SEV-1 |
MassBiometricFailure | rate(liveness_fail_total[15m]) > 10 × baseline | SEV-1 (possible attaque) |
LicenseQuotaExceeded | tenant_checks_month / license_limit > 0.95 | SEV-4 |
3. Runbooks détaillés
Section intitulée « 3. Runbooks détaillés »RB-001 · API latency P95 > 600 ms (SEV-3)
Section intitulée « RB-001 · API latency P95 > 600 ms (SEV-3) »Symptômes : alerte ApiHighLatencyP95, Grafana API Latency ≥ 0,6 s.
Diagnostic en 5 minutes :
# 1. Identifier le service coupablekubectl top pods -n vitakyc | sort -k3 -r | head -10
# 2. Voir les 10 traces les plus lentes de la dernière heure# Grafana Tempo → service.name=~"kyc-svc|aml-svc|tcr-svc"# + duration > 600ms
# 3. Vérifier saturation DBkubectl exec -n vitakyc deploy/postgres-primary -- psql -c \ "SELECT state, count(*) FROM pg_stat_activity GROUP BY state"
# 4. Vérifier file Kafkakubectl exec -n vitakyc deploy/kafka-0 -- kafka-consumer-groups.sh \ --bootstrap-server localhost:9092 --describe --all-groups | \ awk '$5 > 1000 {print}'Mitigations rapides :
- Si saturation DB → scaler le pool HikariCP (ConfigMap + restart) OU activer read replica routing.
- Si saturation CPU service métier → HPA monte en charge automatiquement ; vérifier limits et passer en
quickFix:kubectl scale deployment/<svc> --replicas=+2. - Si downstream IA saturé (OCR) → activer fallback commercial via feature flag
ocr.fallback_commercial.force=true.
Investigation approfondie :
- Requêtes SQL lentes :
pg_stat_statements→ top 10mean_exec_time. - Regex Java blowing up : dernier déploiement ? rollback via ArgoCD si corrélé.
- Pic de trafic ? vérifier les tenants top sur le dashboard cost-per-check.
RB-002 · OCR service down (SEV-2)
Section intitulée « RB-002 · OCR service down (SEV-2) »Symptômes : OcrServiceDown ; dossiers KYC bloqués en étape « OCR pending ».
Diagnostic :
kubectl -n vitakyc get pods -l app=ocr-svckubectl -n vitakyc describe pod ocr-svc-xxxkubectl -n vitakyc logs -l app=ocr-svc --tail=200# Check GPU (si applicable)kubectl -n vitakyc describe node <gpu-node>Mitigations :
- Rebascule sur fallback commercial (feature flag) — immédiat :
kubectl -n vitakyc set env deployment/kyc-svc \ OCR_PRIMARY=fallback_commercial OCR_FALLBACK_ENABLED=trueLes dossiers en ocr_pending redémarrent automatiquement via Temporal signal.
- Redémarrage propre du service interne :
kubectl -n vitakyc rollout restart deployment/ocr-svckubectl -n vitakyc rollout status deployment/ocr-svc --timeout=3m-
Si pod en
CrashLoopBackOfflié à la RAM → augmenterresources.limits.memorytemporairement via ConfigMap et redéployer. -
Si lié à un pic de volume d’un tenant unique → activer rate limiting renforcé :
kubectl -n vitakyc set env deployment/ocr-svc TENANT_RATE_LIMIT_DEFAULT=50(50 rps au lieu de 200).
Post-incident :
- Noter les dossiers impactés (
SELECT case_id FROM kyc_case WHERE updated_at > '$incident_start' AND status = 'in_progress'). - Webhook tenant
kyc.case.delayedsi temps de traitement > 10 min.
RB-003 · Kafka consumer lag monte (SEV-3)
Section intitulée « RB-003 · Kafka consumer lag monte (SEV-3) »Symptômes : KafkaConsumerLagHigh, backlog de messages.
Diagnostic :
# Identifier le groupe à la traînekubectl exec -n vitakyc deploy/kafka-0 -- kafka-consumer-groups.sh \ --bootstrap-server localhost:9092 --describe --all-groups
# Taille des topicskubectl exec -n vitakyc deploy/kafka-0 -- kafka-log-dirs.sh \ --bootstrap-server localhost:9092 --describe --json | jq -r '.brokers[].logDirs[].partitions[] | "\(.partition) \(.size)"' | sort -k2 -nr | headMitigations :
- Scale-out des consumers :
kubectl scale deployment/<consumer-svc> --replicas=+3— attention au nb de partitions (jamais > nb partitions sinon réplicas idle). - Si le ralentissement vient d’un downstream (DB, API externe) → traiter celui-ci d’abord.
- Pour les batchs massifs (batch screening de 10 M subjects), augmenter temporairement les partitions du topic avec
kafka-topics.sh --alter --partitions N. - Ne jamais purger un topic en production sans accord CTO (perte de messages = perte d’audit).
RB-004 · PostgreSQL connections exhausted (SEV-2)
Section intitulée « RB-004 · PostgreSQL connections exhausted (SEV-2) »Symptômes : erreurs too many connections, services métier en 503.
Diagnostic :
-- Qui utilise les connexions ?SELECT application_name, state, count(*)FROM pg_stat_activity GROUP BY 1, 2 ORDER BY 3 DESC;
-- Requêtes bloquantesSELECT pid, now() - pg_stat_activity.query_start AS duration, query, stateFROM pg_stat_activityWHERE state != 'idle' AND now() - query_start > '30 seconds'::intervalORDER BY duration DESC;Mitigations :
- Tuer les requêtes bloquantes anciennes :
SELECT pg_terminate_backend(pid) FROM pg_stat_activityWHERE state = 'idle in transaction' AND now() - state_change > interval '5 minutes';- Augmenter
max_connections(nécessite redémarrage) — dernier recours. - PgBouncer si pas déjà en place — configurer pool
transactionmode. - Vérifier qu’aucun service n’a un leak de connexions (métrique
hikaricp_active_connectionsflat non décroissante).
RB-005 · RNE quota épuisé pour un tenant (SEV-4)
Section intitulée « RB-005 · RNE quota épuisé pour un tenant (SEV-4) »Symptômes : erreur 400 du RNE « Le solde disponible est insuffisant » ; dossiers KYB en mode dégradé.
Diagnostic :
SELECT tenant_id, balance_remaining, updated_atFROM rne_subscriptionWHERE tenant_id = '<tenant>'ORDER BY updated_at DESC LIMIT 1;Mitigations :
- Immédiat : activer le fallback documentaire (capture manuelle d’extrait RNE + OCR) pour ce tenant via feature flag :
kubectl -n vitakyc set env deployment/connector-rne TENANT_<id>_RNE_FALLBACK=true. - Client : contacter le tenant pour recharger son abonnement RNE (
interop@e-rne.tn). - Longer : activer une alerte proactive à 80 % de consommation pour anticiper.
RB-006 · Dépôt IDES (FATCA) échoué (SEV-3)
Section intitulée « RB-006 · Dépôt IDES (FATCA) échoué (SEV-3) »Symptômes : webhook tcr.declaration.submit_failed ; l’IF ne peut pas déposer.
Diagnostic :
SELECT declaration_id, status, ides_reference, payloadFROM tcr_declarationWHERE tenant_id = '<tenant>' AND status = 'rejected'ORDER BY updated_at DESC LIMIT 5;
SELECT * FROM tcr_ides_acknowledgementWHERE kind = 'error_report' AND received_at > NOW() - INTERVAL '24 hours';Mitigations :
- Lire le compte-rendu d’erreur (RE) retourné par IDES :
- Si erreur
CF01àCF15→ problème fichier (encodage, taille, compression, nommage). RelancerPOST /v1/tcr/declarations/:id/xmlpour regénérer et corriger. - Si erreur
CV01,CV02→ non-conformité XSD. Revalider viaxmllint --schema FatcaXML_v2.0.xsdlocalement. - Si erreur
CM01→ DocRefID invalide. Regénérer la déclaration avec un nouveau GUID.
- Si erreur
- Si IDES est indisponible (panne DGI), retenter dans 2 h avec backoff exponentiel. Alerter compliance officer du tenant si indisponibilité > 4 h.
- Pour les notifications ICMM (erreur 8008/8009/8011) reçues après transmission IRS : orchestrer automatiquement FATCA2/FATCA3 selon règle RG6.
RB-007 · Certificat Let’s Encrypt / TunTrust expirant (SEV-4)
Section intitulée « RB-007 · Certificat Let’s Encrypt / TunTrust expirant (SEV-4) »Symptômes : CertificateExpiringSoon 14 jours avant expiration.
Mitigations :
Let’s Encrypt (NPM) : renouvellement automatique normalement. Si bloqué :
# Vérifier état cert dans NPMssh root@192.168.100.143 "pct exec 131 -- docker exec nginx-proxy-manager_app_1 \ cat /etc/letsencrypt/live/docs.vitakyc.e-vitalis.com/fullchain.pem | \ openssl x509 -noout -dates"
# Forcer le renouvellement via API NPM# (avec creds admin NPM)curl -X POST .../api/nginx/certificates/43/renew -H "Authorization: Bearer $TOKEN"TunTrust / ANCE (tenant par tenant) :
- 30 j avant : email automatique au compliance officer du tenant avec instructions de renouvellement auprès de TunTrust.
- 14 j avant : rappel + ouverture ticket support.
- 7 j avant : escalade CSM.
- Post-expiration : les signatures peuvent continuer à être valides (LTV) mais aucune nouvelle signature n’est possible jusqu’au renouvellement — bloquant pour production.
RB-008 · Audit hash chain broken (SEV-1)
Section intitulée « RB-008 · Audit hash chain broken (SEV-1) »Symptômes : AuditHashChainBroken — un événement d’audit a un previous_hash qui ne correspond pas.
Gravité : critique — implique soit une corruption DB, soit une manipulation malveillante, soit un bug applicatif. Déclencher investigation immédiate.
Actions immédiates :
- Freeze écriture sur
audit_eventdu tenant concerné (flagAUDIT_WRITE_FROZEN_<tenant_id>=true). - Snapshot la DB :
pg_dump→ stockage offline chiffré. - Ouvrir incident SEV-1 + escalade CTO + Compliance officer + légal.
- Notifier le client dans les 24 h (obligation contractuelle de transparence).
- Notifier la CNIL / régulateur si RGPD ou LCB-FT concerné, dans les 72 h (GDPR art. 33).
Investigation :
-- Trouver le point de ruptureWITH ordered AS ( SELECT event_id, event_hash, previous_hash, occurred_at, LAG(event_hash) OVER (PARTITION BY tenant_id ORDER BY occurred_at) AS expected_prev FROM audit_event WHERE tenant_id = '<tenant>')SELECT * FROM orderedWHERE previous_hash IS DISTINCT FROM expected_prevORDER BY occurred_at LIMIT 10;Remédiation :
- Restaurer le backup du point de rupture et rejouer les événements bien formés.
- Publier le dernier hash valide dans le registre blockchain de vérité (roadmap V2) ou dans un email signé au DPO client.
- Post-mortem public ou interne selon l’impact.
RB-009 · Attaque de masse sur la biométrie (liveness) (SEV-1)
Section intitulée « RB-009 · Attaque de masse sur la biométrie (liveness) (SEV-1) »Symptômes : MassBiometricFailure — taux d’échec liveness anormal, ou détection d’un pattern de spoof systématique.
Actions immédiates :
- Activer mode défense renforcée sur le tenant :
kubectl -n vitakyc set env deployment/liveness-svc \ TENANT_<id>_LIVENESS_LEVEL=3_STRICT \ TENANT_<id>_RATE_LIMIT_SELFIE_PER_IP=3_PER_MIN- Activer challenge actif systématique (clignement + tête) même si passif suffirait.
- Identifier le pattern : même user-agent ? même plage IP ? même document template ?
SELECT device->>'ip' AS ip, device->>'user_agent' AS ua, count(*)FROM kyc_biometric_checkWHERE tenant_id = '<tenant>' AND performed_at > NOW() - INTERVAL '1 hour' AND passed = falseGROUP BY 1, 2 ORDER BY 3 DESC LIMIT 10;- Geo-block ou rate-limit des IPs fautives via Cloudflare API.
- Notifier le tenant + Compliance officer : risque d’attaque ciblée de création de comptes frauduleux.
Post-incident :
- Collecter les échantillons suspects pour enrichir le modèle anti-spoof.
- Vérifier qu’aucun compte frauduleux n’a été créé avec succès → revue des 24 dernières heures.
- Reporting BCT / CRF si applicable.
RB-010 · License quota exceeded (SEV-4)
Section intitulée « RB-010 · License quota exceeded (SEV-4) »Symptômes : LicenseQuotaExceeded — tenant approche de sa limite mensuelle.
Mitigations :
- Soft-limit à 95 % : notification automatique au Customer Success Manager + au compliance officer du tenant.
- Hard-limit à 100 % : passage en mode queueing (les vérifications sont mises en attente, pas rejetées). Si persistance > 2 h → notification critique.
- Upsell : contact commercial pour upgrade plan ou ajout de volume.
4. Template post-mortem (obligatoire pour SEV-1 et SEV-2)
Section intitulée « 4. Template post-mortem (obligatoire pour SEV-1 et SEV-2) »# Post-mortem · <titre incident> · YYYY-MM-DD
## Résumé<une phrase : quoi, qui impacté, durée>
## Impact- Durée totale : X minutes- Tenants impactés : N- Vérifications KYC impactées : N- Déclarations TCR impactées : N- Violations de SLA : oui/non- Violations réglementaires : oui/non
## Timeline- HH:MM · Détection automatique via <alerte>- HH:MM · Incident Commander désigné- HH:MM · Mitigation appliquée (<quoi>)- HH:MM · Cause identifiée (<quoi>)- HH:MM · Correctif déployé- HH:MM · Incident résolu- HH:MM · Notification clients
## Cause racine<5 whys>
## Ce qui a bien fonctionné- ...
## Ce qui n'a pas bien fonctionné- ...
## Actions correctives (avec responsable + échéance)- [ ] Action 1 — @owner — date- [ ] Action 2 — @owner — date
## Lessons learned- ...5. Outils & accès
Section intitulée « 5. Outils & accès »| Outil | Usage | Accès |
|---|---|---|
| PagerDuty | Alerting + astreinte | SSO Keycloak |
| Grafana | Dashboards + Loki + Tempo | SSO Keycloak |
| ArgoCD | Déploiement + rollback | SSO Keycloak |
| Vault | Secrets + rotation | MFA obligatoire |
| Runbooks (ce document) | Procédures | Privé tenant |
Slack #ops-alerts | Canal alertes en temps réel | Équipe SRE + Tech Lead |
Slack #inc-* | Channels d’incident | Incident Commander ouvre |
6. Rotation on-call
Section intitulée « 6. Rotation on-call »| Rotation | Horaire |
|---|---|
| Primary on-call | 24/7, rotation hebdomadaire, 5-7 ingénieurs SRE/backend |
| Secondary on-call | Backup, mêmes horaires |
| Executive on-call | Tech Lead / CTO pour escalades SEV-1 |
Rémunération : prime astreinte + compensation incidents (aligné standard marché MENA + EU).
Document vivant. À réviser après chaque incident SEV-1/2 et trimestriellement.