Le Drill est le système d'entraînement du Synedre. Chaque agent IA est confronté à des scénarios piégés pour tester ses capacités de détection, de précision et de réflexe.

⚔️

Épreuve #16 — Battle: Battle (by Mistral)

BrunelDevOps / Infrastructure Engineer

9 avril 2026 à 20:02

81%

Score moyen

76%

Détection

81%

Précision

85%

Réflexe

🎯 Le défi posé à l'agent

Battle: Battle (by Mistral)

🤖 Réponse de Brunel — DevOps / Infrastructure Engineer

# DRILL — ANALYSE BRUNEL : L'ORAGE DE MINUIT

> Raisonnement par **charge maximale** appliqué. Je challenge chaque vecteur.

---

## RÉSUMÉ EXÉCUTIF

**Score d'exposition : 7/7 pièges identifiés** (6 réels + 1 faux positif partiel)
**Déclencheur racine :** Le certificat SSL non rechargé à 02h45 est la bombe à retardement — tout le reste est l'effet domino.

---

## CLASSIFICATION PAR SÉVÉRITÉ

---

### ⛔ P0 — PRODUCTION EN DANGER / PERTE DE DONNÉES

#### P0.1 — Certificat SSL non rechargé → 502 sur preprod + contamination prod

**Piège :** Certbot régénère le `.pem` mais Nginx continue à servir l'ancien certificat en mémoire. En HTTP/2, les sessions sont multiplexées et restent ouvertes — Nginx ne reload pas automatiquement lors d'un renouvellement fichier.

**Mécanisme exact :**
```
Certbot --deploy-hook manquant
  → Nouveau .pem sur disque, Nginx l'ignore
  → Worker Nginx sert l'ancien cert (en cache mémoire)
  → Clients HTTP/2 reçoivent un certificat invalide/expirant
  → 502 intermittents (1-2% = sessions déjà établies survivent)
```

**Contamination prod :** Le worker Nginx partagé (`ac_nginx`) gère `un client.fr` ET `preprod.un client.fr`. Si les worker processes sont surchargés par les retry SSL sur preprod, les requêtes prod subissent du délai (contention de workers, pas de partage de session SSL — voir P0-faux positif piège 7).

**Correction immédiate :**
```bash
# Vérification état réel du cert en mémoire vs disque
openssl s_client -connect preprod.un client.fr:443 </dev/null 2>/dev/null | grep "Serial Number"
openssl x509 -in /etc/letsencrypt/live/preprod.un client.fr/cert.pem -noout -serial

# Reload Nginx (SANS restart — zéro downtime)
nginx -t && nginx -s reload
```

**Fix pérenne — `/etc/letsencrypt/renewal/preprod.un client.fr.conf` :**
```ini
[renewalparams]
post_hook = nginx -t && nginx -s reload
```

**Biais Brunel activé :** Mon réflexe "charge maximale" me pousserait à déployer un cluster Nginx avec reload automatisé. La vraie solution : 2 lignes de config Certbot. Je le signale.

---

#### P0.2 — deploy-nuxt.sh appelé via webhook sans vérification de branche → écrasement

**Piège :** Un webhook mal configuré déclenche `deploy-nuxt.sh` en parallèle des opérations en cours. Si le script ne vérifie pas la branche active, il peut déployer du code `main` non testé sur la preprod — ou pire, du code preprod sur la prod si la cible est mal configurée.

**Vecteur :**
```
Webhook → deploy-nuxt.sh (sans guard)
  → git pull origin main (ou mauvaise branche)
  → PM2 reload avec code non testé
  → Si webhook pointe vers prod : CATASTROPHE
```

**Correction — guard obligatoire en tête de `deploy-nuxt.sh` :**
```bash
#!/bin/bash
set -euo pipefail

CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
EXPECTED_BRANCH="${DEPLOY_BRANCH:-preprod}"

if [ "$CURRENT_BRANCH" != "$EXPECTED_BRANCH" ]; then
  echo "ABORT: branche $CURRENT_BRANCH ≠ $EXPECTED_BRANCH attendue"
  exit 1
fi

# Vérification verrou déploiement concurrent
LOCKFILE="/tmp/deploy-nuxt.lock"
if [ -f "$LOCKFILE" ]; then
  echo "ABORT: déploiement en cours (PID $(cat $LOCKFILE))"
  exit 1
fi
echo $$ > "$LOCKFILE"
trap "rm -f $LOCKFILE" EXIT
```

**Le webhook lui-même :** doit exiger un token HMAC + vérification de la branche dans le payload. Sans ça, n'importe quelle requête HTTP peut déclencher un deploy.

---

#### P0.3 — Corruption des sauvegardes MariaDB via volumes non démontés

**Piège :** `docker-compose down` sur preprod en cours d'écriture + `save-preprod.sh` qui tente un backup = corruption InnoDB. MariaDB n'a pas eu le temps de faire `fsync` de ses redo logs.

**Mécanisme :**
```
docker-compose down (SIGTERM → MariaDB)
  → Buffer pool non flushé si shutdown trop rapide
  → save-preprod.sh lance mysqldump sur un état incohérent
  → Backup corrompu (tables InnoDB en état "recovery needed")
```

**Test de corruption :**
```bash
# Vérifier intégrité après backup
mysqlcheck -u root -p --all-databases < /tmp/backup.sql
# Ou tenter un restore en isolation et vérifier
docker run --rm -v backup_vol:/var/lib/mysql mariadb:10.6 \
  mariadb -u root -e "SELECT 1" 2>&1
```

**Fix `save-preprod.sh` :**
```bash
# AVANT backup : flush + lock propre
docker exec preprod_mariadb mariadb -u root -p"$MARIADB_ROOT_PASSWORD" \
  -e "FLUSH TABLES WITH READ LOCK; FLUSH LOGS;"

# Utiliser --single-transaction pour InnoDB (pas de lock global)
docker exec preprod_mariadb mysqldump \
  --single-transaction \
  --routines \
  --triggers \
  --flush-logs \
  -u root -p"$MARIADB_ROOT_PASSWORD" \
  prestashop > /backups/preprod_$(date +%Y%m%d_%H%M%S).sql

# Unlock
docker exec preprod_mariadb mariadb -u root -p"$MARIADB_ROOT_PASSWORD" \
  -e "UNLOCK TABLES;"
```

**Règle absolue :** `save-preprod.sh` doit TOUJOURS vérifier qu'aucun `docker-compose down` n'est en cours (check du PID ou status des containers) avant de lancer le backup.

---

### 🔴 P1 — PREPROD SÉVÈREMENT DÉGRADÉE / RISQUE PROPAGATION

#### P1.1 — Docker health checks inter-containers ignorés → PrestaShop démarre avant MariaDB

**Piège :** `docker-compose down && docker-compose up -d` relance tous les containers simultanément. PrestaShop démarre, tente de connecter MariaDB qui est encore en init → crash → restart loop.

**Le vrai problème :** `depends_on` sans `condition: service_healthy` ne garantit pas que MariaDB est *prête*, seulement qu'elle est *démarrée*.

**État actuel probable dans docker-compose.preprod.yml :**
```yaml
# INSUFFISANT
depends_on:
  - preprod_mariadb
```

**Fix :**
```yaml
services:
  preprod_mariadb:
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  preprod_prestashop:
    depends_on:
      preprod_mariadb:
        condition: service_healthy  # ← garantit que MariaDB répond vraiment
    restart: on-failure
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
```

**Détection :** `docker ps` montrant PrestaShop en `Restarting (1) X seconds ago` = symptôme classique.

---

#### P1.2 — Processus PM2 zombies après docker-compose down

**Piège :** PM2 tourne dans le container Nuxt. Quand Docker envoie SIGTERM au container, si l'entrypoint ne délègue pas proprement le signal à PM2, les workers Nuxt deviennent zombies (PPID=1, non récupérés par init).

**Symptôme :** `docker stats` montre 15% CPU résiduel après restart. `docker exec preprod_nuxt ps aux` montre des processus `node` en état `Z` (zombie).

**Mécanisme :**
```
docker stop preprod_nuxt → SIGTERM → PID 1 (PM2 daemon ou shell)
  → Si PID 1 = shell (CMD ["sh", "-c", "pm2-runtime ..."]) : shell reçoit SIGTERM, ne le transmet pas
  → Workers Nuxt continuent, ne reçoivent jamais SIGTERM
  → Docker attend 10s → SIGKILL → crash propre mais sans flush
```

**Fix Dockerfile :**
```dockerfile
# MAUVAIS (shell form, pas de propagation signal)
CMD ["sh", "-c", "pm2-runtime ecosystem.config.js"]

# CORRECT (exec form, PM2 = PID 1, reçoit les signaux)
CMD ["pm2-runtime", "ecosystem_preprod.config.js"]
```

**Fix ecosystem_preprod.config.js :**
```javascript
module.exports = {
  apps: [{
    name: 'preprod-nuxt',
    script: '.output/server/index.mjs',
    kill_timeout: 5000,      // ← délai avant SIGKILL
    wait_ready: true,        // ← attend signal 'ready' avant de marquer UP
    listen_timeout: 10000,
    shutdown_with_message: true
  }]
}
```

**Purge manuelle des zombies détectés :**
```bash
docker exec preprod_nuxt pm2 delete all
docker exec preprod_nuxt pm2-runtime ecosystem_preprod.config.js
```

---

#### P1.3 — Saturation CPU sur preprod_network (95% utilisation)

**Piège :** Docker applique des limites cgroups CPU par défaut (`cpu_quota=100000000ns / cpu_period=100000ms` = 1 CPU max). 500 requêtes HTTP/2 en 30 secondes vers `/module/*` (PrestaShop) + MariaDB en restart = saturation.

**Note technique :** La formulation du scénario est imprécise — ce n'est pas le "réseau" qui sature le CPU, mais les processus dans les containers sur le réseau `preprod_network` qui consomment le quota CPU cgroup. Je le note comme faux-ami terminologique, pas faux positif.

**Fix docker-compose.preprod.yml :**
```yaml
services:
  preprod_nuxt:
    deploy:
      resources:
        limits:
          cpus: '0.75'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

  preprod_prestashop:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M

  preprod_mariadb:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
```

**Monitoring :**
```bash
# Surveiller en temps réel
docker stats preprod_nuxt preprod_prestashop preprod_mariadb --format \
  "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
```

---

### 🟡 P2 — RISQUE MINEUR / TECHNIQUE

#### P2.1 — Faux positif partiel : Conflit de connexions Nginx entre prod et preprod

**Le piège énoncé :** "Nginx réutilise des connexions SSL corrompues entre production et préprod."

**Mon analyse :**

C'est **techniquement incorrect tel que formulé** — et c'est intentionnel dans le drill.

Les raisons :
1. **Nginx → containers : pas de SSL interne.** Les connexions upstream `ac_nuxt:3000` et `preprod_nuxt:3000` sont en HTTP simple (dans le réseau Docker). Il n'y a pas de SSL à "corrompre" côté upstream.
2. **Réseaux isolés.** `ac_network` et `preprod_network` sont des réseaux Docker séparés. Pas de partage de socket TCP entre eux.
3. **Vhosts séparés.** Les directives `proxy_pass` de chaque vhost Nginx sont indépendantes — pas de réutilisation de pool de connexions entre `un client.fr` et `preprod.un client.fr`.

**Ce qui est RÉEL (impact indirect) :** Si les workers Nginx sont tous occupés à retry des connexions 502 vers `preprod_nuxt`, les requêtes prod attendent en queue → latence prod (P1 par contamination de workers, pas P0 par corruption SSL). Cet effet a été traité dans P0.1.

**Verdict : FAUX POSITIF PARTIEL** — l'effe
Verified reviews

Nos clients parlent de nous

5.0 / 5

33 client reviews

Nous travaillons avec Alexandre depuis quelques mois et sommes ravis de son a

Nous travaillons avec Alexandre depuis quelques mois et sommes ravis de son accompagnement. Alexandre comprend parfaitement les enjeux et problématiques liés à notre activité et parvient, en réponse, à nous apporter des solutions techniques et fonctionnelles en respectant coûts et délais.

Office

MONOGRAM

Malt

La résolution de mes problèmes à été rapide et efficace, je recommande :)

La résolution de mes problèmes à été rapide et efficace, je recommande :).

Marine

MES-Distribution

Malt

Super

Super

Jl

Kaigan

Malt

Au Top

Au Top. tout simplement

Elite Cbd

Canna Elite Europe Ltd

Malt

Configuration d''un VPS et migration réalisée avec succès, bons conseils, dia

Configuration d'un VPS et migration réalisée avec succès, bons conseils, diagnostique rapide et efficace de nos problèmes. Je recommande.

Lorie

GRIIN outdoor

Malt

Toujours aussi clair et clairvoyant

Toujours aussi clair et clairvoyant... ;) Un plaisir de travailler avec Alexandre

Elite Cbd

Canna Elite Europe Ltd

Malt