DevOps Elastic Hayway
Document

SUBSCRIBE TO GET FULL ACCESS TO THE E-BOOKS FOR FREE 🎁SUBSCRIBE NOW

Professional Dropdown with Icon

SUBSCRIBE NOW TO GET FREE ACCESS TO EBOOKS

RAG pour docs infra : interroger vos runbooks avec retrieval

À la fin de ce tutoriel RAG docs infra, vous saurez monter un mini pipeline Retrieval-Augmented Generation sur des runbooks Markdown : ingestion, chunking, index vectoriel léger (TF‑IDF / cosine), requête top‑k avec citations de sources, et garde-fous pour ne jamais indexer de secrets — lab factice sous /tmp/lab-rag-docs-infra.

Niveau : Intermédiaire · Temps estimé : 50–60 min · Versions testées : Ubuntu 22.04 / 24.04, Python 3.10+ · Dernière vérification : 2026-09-11

Prérequis

Coût : 0 €. En prod : vrais embeddings + store Chroma / LanceDB / pgvector.

Ce que nous allons construire

/tmp/lab-rag-docs-infra
  ├── docs/
  │     ├── runbook-nginx-restart.md
  │     ├── runbook-disk-full.md
  │     ├── runbook-k8s-scale.md
  │     └── adr-observability.md
  ├── rag_lab.py          (ingest + query + cite)
  ├── index.npz           (vecteurs + métadonnées)
  └── .gitignore-like     (exclure secrets)
       → chunking Markdown par titres
       → TF-IDF + similarité cosine (proxy embeddings)
       → top-k + citations chemin/extrait
       → filtre anti-secrets · quiz · FAQ

(Schéma à remplacer par une image locale Excalidraw / draw.io, alt : « RAG docs infra : runbooks Markdown, chunking, index vectoriel, requête citée ».)

Objectif : question ops → bonne page + extrait (pas d’hallucination). Le RAG apporte le contexte hors fenêtre de prompt.

Pourquoi RAG pour la doc infra ?

Coller tout le wiki dans un prompt échoue : limite de contexte, coût, pas de provenance. Pattern retrieve puis generate : indexer des chunks → top‑k → LLM avec consigne de citer les fichiers. Ici on valide surtout l’index + retrieval ; le LLM peut venir ensuite.

Étape 1 — Lab : runbooks Markdown factices

mkdir -p /tmp/lab-rag-docs-infra/docs && cd /tmp/lab-rag-docs-infra

cat > docs/runbook-nginx-restart.md << 'EOF'
# Runbook — Redémarrer nginx (lab)

### Symptômes
Site 502 / connexion refusée sur le port 80.

### Diagnostic
```bash
systemctl is-active nginx
journalctl -u nginx -n 50 --no-pager

Remédiation

  1. Vérifier la config : nginx -t
  2. Recharger : systemctl reload nginx
  3. Si échec : systemctl restart nginx
  4. Smoke : curl -I http://127.0.0.1/

Escalade

Si nginx -t échoue, corriger le vhost avant tout restart.
EOF

cat > docs/runbook-disk-full.md << ‘EOF’

Runbook — Disque plein (lab)

Symptômes

Écritures en échec, alerts disk > 90 %.

Diagnostic

df -h /
du -xh /var --max-depth=2 | sort -h | tail -n 20
journalctl --disk-usage

Remédiation

  1. Purger logs rotatés sous /var/log (audit avant)
  2. docker system df puis nettoyage images inutiles
  3. Étendre volume LVM / cloud si structurel

Ne pas faire

Supprimer des données applicatives sans ticket ni backup.
EOF

cat > docs/runbook-k8s-scale.md << ‘EOF’

Runbook — Scale Deployment Kubernetes (lab)

Symptômes

Latence haute, HPA saturé, files d’attente.

Diagnostic

kubectl -n demo get deploy api
kubectl -n demo get hpa
kubectl -n demo top pods

Remédiation

kubectl -n demo scale deploy/api --replicas=5
kubectl -n demo rollout status deploy/api

Vérification

Requêtes p95 en baisse ; pods Ready = replicas.
EOF

cat > docs/adr-observability.md << ‘EOF’

ADR — Observabilité minimale (lab)

Contexte

Besoin de métriques, logs et traces pour les services demo.

Décision

Prometheus + Grafana ; logs journald / Loki ; OTel optionnel.

Conséquences

Services avec /metrics ; runbooks citent les dashboards avant escalade.
EOF

find docs -type f -name ‘*.md’ | wc -l


Vous devez voir `4`.

## Étape 2 — Script `rag_lab.py` : chunk, index, query

Le lab utilise **TF‑IDF** (scikit-learn) comme **proxy d’embeddings** (même schéma retrieve + cite, sans modèle multi‑Go). En prod : embeddings réels + Chroma / LanceDB / pgvector.

```bash
cd /tmp/lab-rag-docs-infra
pip install --user -q scikit-learn

cat > rag_lab.py << 'PY'
#!/usr/bin/env python3
"""Mini RAG lab — chunk Markdown, TF-IDF index, query + citations."""
from __future__ import annotations

import argparse
import json
import re
from pathlib import Path

import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

ROOT = Path(__file__).resolve().parent
DOCS = ROOT / "docs"
INDEX = ROOT / "index.npz"
META = ROOT / "index_meta.json"
SECRET_PAT = re.compile(
    r"(.env|kubeconfig|.pem|.key|id_rsa|secret|password|token)",
    re.I,
)

def should_skip(path: Path) -> bool:
    return bool(SECRET_PAT.search(str(path)))

def chunk_markdown(text: str, source: str, max_chars: int = 700) -> list[dict]:
    """Découpe par titres #/## puis fusionne si trop long."""
    parts = re.split(r"(?=^#{1,3} )", text, flags=re.M)
    chunks: list[dict] = []
    buf = ""
    for part in parts:
        part = part.strip()
        if not part:
            continue
        if len(buf) + len(part) + 1 <= max_chars:
            buf = f"{buf}nn{part}".strip() if buf else part
        else:
            if buf:
                chunks.append({"source": source, "text": buf})
            buf = part if len(part) <= max_chars else part[:max_chars]
    if buf:
        chunks.append({"source": source, "text": buf})
    return chunks

def ingest() -> None:
    all_chunks: list[dict] = []
    for path in sorted(DOCS.rglob("*.md")):
        if should_skip(path):
            print(f"skip secret-like: {path}")
            continue
        rel = str(path.relative_to(ROOT))
        all_chunks.extend(chunk_markdown(path.read_text(encoding="utf-8"), rel))
    if not all_chunks:
        raise SystemExit("aucun chunk — vérifiez docs/")
    texts = [c["text"] for c in all_chunks]
    vec = TfidfVectorizer(ngram_range=(1, 2), min_df=1)
    X = vec.fit_transform(texts)
    np.savez_compressed(
        INDEX,
        data=X.data,
        indices=X.indices,
        indptr=X.indptr,
        shape=X.shape,
        vocabulary=np.array(list(vec.vocabulary_.items()), dtype=object),
        idf=vec.idf_,
    )
    META.write_text(json.dumps(all_chunks, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"index OK — {len(all_chunks)} chunks → {INDEX.name}")

def load_matrix():
    z = np.load(INDEX, allow_pickle=True)
    from scipy.sparse import csr_matrix

    X = csr_matrix(
        (z["data"], z["indices"], z["indptr"]), shape=tuple(z["shape"])
    )
    vocab = {str(k): int(v) for k, v in z["vocabulary"]}
    idf = z["idf"]
    chunks = json.loads(META.read_text(encoding="utf-8"))
    return X, vocab, idf, chunks

def query(q: str, k: int = 3) -> None:
    from scipy.sparse import csr_matrix

    X, vocab, idf, chunks = load_matrix()
    # vectorize query with same vocab/idf
    analyzer = TfidfVectorizer(ngram_range=(1, 2)).build_analyzer()
    toks = analyzer(q)
    row = np.zeros(X.shape[1], dtype=float)
    tf: dict[int, int] = {}
    for t in toks:
        if t in vocab:
            j = vocab[t]
            tf[j] = tf.get(j, 0) + 1
    for j, c in tf.items():
        row[j] = c * idf[j]
    if row.sum() == 0:
        print("aucun terme indexé dans la requête — reformulez")
        return
    qv = csr_matrix(row)
    sims = cosine_similarity(qv, X).ravel()
    order = np.argsort(-sims)[:k]
    print(f"Q: {q}n")
    for rank, i in enumerate(order, 1):
        c = chunks[int(i)]
        excerpt = c["text"].replace("n", " ")[:220]
        print(f"{rank}. score={sims[i]:.3f}  source={c['source']}")
        print(f"   {excerpt}…n")

def main() -> None:
    p = argparse.ArgumentParser(description="RAG lab docs infra")
    sub = p.add_subparsers(dest="cmd", required=True)
    sub.add_parser("ingest")
    pq = sub.add_parser("query")
    pq.add_argument("text")
    pq.add_argument("-k", type=int, default=3)
    args = p.parse_args()
    if args.cmd == "ingest":
        ingest()
    else:
        query(args.text, k=args.k)

if __name__ == "__main__":
    main()
PY

Étape 3 — Ingest puis requêtes avec citations

cd /tmp/lab-rag-docs-infra
python3 rag_lab.py ingest
python3 rag_lab.py query "disque plein logs docker"
python3 rag_lab.py query "scale deployment kubernetes latence" -k 2
python3 rag_lab.py query "redémarrer nginx 502"

Validé si : top‑k pointe le bon fichier (disk-full, k8s-scale, nginx-restart) avec un extrait utilisable. Ensuite, prompt LLM du type : « réponds uniquement avec ces extraits et cite le chemin ; sinon dis que l’info manque ».

Étape 4 — Garde-fous secrets et qualité

# Simuler un fichier dangereux — doit être ignoré à l'ingest
echo 'API_TOKEN=lab-fake-not-real' > /tmp/lab-rag-docs-infra/docs/secrets.env.md
# Renommer pour matcher le filtre (extension .env dans le nom)
mv /tmp/lab-rag-docs-infra/docs/secrets.env.md 
   /tmp/lab-rag-docs-infra/docs/fake.env.notes.md
python3 rag_lab.py ingest   # doit afficher skip secret-like
rm -f /tmp/lab-rag-docs-infra/docs/fake.env.notes.md
Contrôle Attendu
Question connue Top‑1 = bon runbook
Hors corpus Scores bas ; LLM dit « pas dans l’index »
Chunk trop gros Réduire max_chars ; respecter ##
Secrets Filtre path + deny-list CI

Prod : embeddings réels, store Chroma / LanceDB / pgvector, corpus Git, expo MCP (MCP DevOps).

Nettoyage

rm -rf /tmp/lab-rag-docs-infra
echo "lab RAG docs infra nettoyé — OK"

Erreurs fréquentes

Symptôme Cause Correction
Mauvais runbook en top‑1 Chunks trop larges ou titres absents Chunker sur ## ; baisser max_chars
Scores tous ~0 Vocabulaire requête hors index Reformuler avec termes du doc ; n‑grams
ModuleNotFoundError: sklearn Paquet manquant pip install --user scikit-learn
Hallucination LLM malgré RAG Prompt sans contrainte de citation Exiger « uniquement les extraits » + sources
Secret indexé Fichier mal nommé / filtre faible Patterns + deny-list CI ; jamais .env dans docs/
Index obsolète Docs modifiés sans re-ingest rag_lab.py ingest dans le pipeline Git

Quiz (4 questions)

1. À quoi sert le retrieval dans un RAG docs infra ?
– A. Compiler le noyau Linux
– B. Sélectionner les chunks de runbooks les plus pertinents avant génération
– C. Remplacer Git

2. Pourquoi citer le chemin source dans la réponse ?
– A. Pour décorer le terminal
– B. Pour permettre vérification humaine et réduire les hallucinations
– C. Pour compresser l’index

3. Que faire d’un fichier kubeconfig dans le dossier docs ?
– A. L’indexer pour « plus de contexte »
– B. L’exclure (filtre secrets) et le stocker hors corpus
– C. Le coller dans le prompt système

4. TF‑IDF dans ce lab remplace quoi pédagogiquement ?
– A. Kubernetes
– B. Les embeddings d’un vrai pipeline RAG (même schéma retrieve)
– C. systemd

Réponses : 1‑B · 2‑B · 3‑B · 4‑B

Compléments opérationnels

Mesure et amélioration continue

Définissez trois indicateurs simples : taux de DoD vert du premier coup, nombre de refus utiles par session, coût tokens par livrable Git accepté. Revoyez-les toutes les deux semaines. Si le taux DoD stagne sous 50 %, le problème est le brief ou le corpus de rules, pas « le besoin d’un modèle plus cher ». Si le coût explose, imposez un modèle fast par défaut et réservez strong aux plans. Publiez un mini tableau dans le dépôt docs pour rendre le coût visible à l’équipe, comme n’importe quelle ligne cloud.

Standard de brief réutilisable

Pour rag-docs-infra, adoptez un brief unique partagé par l’équipe. Il contient toujours cinq champs : objectif mesurable, contexte borné (lab/staging), liste blanche de fichiers ou ressources, interdits explicites (secrets, réseau, prod), et definition of done exécutable (commande de test ou checklist). Sans ces champs, l’outil improvisera — parfois avec élégance, souvent hors périmètre.

Stockez les briefs gagnants sous briefs/rag-docs-infra/ dans un dépôt de documentation interne. Chaque brief cite le commit de référence et le modèle utilisé. Au troisième succès comparable, ouvrez une PR qui transforme le brief en rule, Skill ou slash command. Voir ai-skills-concept, claude-skills-instructions et evaluer-qualite-skill.

Revue de diff comme compétence P0

La vitesse d’un agent n’a aucune valeur si personne ne sait lire un diff. Instituez une règle simple : aucun apply massif sans parcours hunk par hunk. Posez-vous trois questions : ce changement est-il demandé ? est-il testé ? est-il réversible ? Si une réponse est non, refusez ou exigez un plan plus petit.

En atelier, faites commenter un diff à voix haute pendant cinq minutes. Les juniors apprennent ainsi le vocabulaire du refus. Reliez cette pratique à ai-code-review-pr et aux workflows ai-agent-workflows.

Sécurité continue

Les surfaces rag-docs-infra multiplient les occasions d’injection et de fuite. Traitez tickets, README upstream, logs et ressources MCP comme des données non fiables. Séparez instructions et données. Allowlist tools. Confirmation humaine avant mutation. Guide : ai-prompt-injection-securite, mcp-servers-devops, claude-code-mcp.

Secrets : variables d’environnement de session, gestionnaire d’équipe, rotation, jamais de commit. Si un secret fuit dans un chat vendor, supposez-le compromis et rotatez.

Coûts, modèles et mode dégradé

Documentez un mapping fast/strong et un soft-cap mensuel dans la stack perso. OpenRouter peut aider à basculer — openrouter-multi-modeles. Le local (ollama-llm-locaux + continue-dev) sert de mode dégradé ou de bac à sable confidentialité.

Surveillez les changements silencieux de modèle par défaut : une régression de qualité est souvent un routing vendor, pas « l’équipe qui a oublié comment prompter ».

Ancrage parcours

rag-docs-infra n’est qu’une étape du parcours IA DevOps 2026. Tronc commun d’abord (prompts, injection, skills), puis surface spécialisée, puis industrialisation (routines, MCP, RAG). Hub de navigation : /ia/. Comparatifs utiles : claude-vs-gpt-code, codex-vs-claude-code, claude-vs-cursor-vs-copilot.

Rituals hebdomadaires

Hebdo : promouvoir un brief en Skill/rule ; archiver un échec instructif ; vérifier le budget tokens. Mensuel : revoir la stack (retirer l’inutile) ; rejouer un lab gold ; mettre à jour les versions d’outils. Ces rituals valent mieux que d’ajouter un cinquième abonnement.

Exemple de NOTES.md

# NOTES rag-docs-infra
Date:
Modèle:
Brief (résumé):
Fichiers touchés:
Commande DoD:
Refus utiles:
Commit:
Leçon:

Sans ce journal, vous réapprendrez les mêmes leçons chaque sprint. Avec ce journal, vous construisez un avantage composé d’équipe.

Quand arrêter l’outil

Arrêtez la session si : le DoD échoue deux fois de suite sans diagnostic clair ; l’outil insiste pour sortir du scope ; une commande dangereuse revient après refus ; vous ne comprenez plus le diff. Dans ces cas, revenir à l’éditeur manuel + checklist est un succès de gouvernance, pas un échec personnel.

Ponts concrets vers d’autres tutoriels

Après ce lab, enchaînez selon besoin : agent CLI (claude-code-demarrer, openai-codex-intro-2026, aider-pair-programming), IDE (windsurf-cascade, continue-dev, cursor-agent-composer-2026), qualité (evaluer-qualite-skill, ai-routines-agents), ou recherche (gemini-cli-deep-research, chatgpt-search-browsing).

FAQ

Faut-il un LLM pour commencer ?
Non. Validez le top‑k d’abord, puis branchez un modèle avec « cite les sources ».

Chunking tokens ou titres ?
Pour des runbooks Markdown : ## + plafond de caractères. Le découpage aveugle à N tokens casse les procédures.

Chroma, pgvector ou TF‑IDF lab ?
Le lab prouve le flux. Équipe : pgvector si Postgres ; Chroma/LanceDB en prototype ; embeddings dès que les synonymes comptent.

Comment re-indexer après une MAJ de runbook ?
Relancez python3 rag_lab.py ingest (CI Git idéalement). Un index obsolète renvoie d’anciennes procédures.

Le RAG remplace-t-il la recherche plein texte ?
Non : rg reste excellent pour une chaîne exacte. Le RAG aide surtout les questions en langage naturel et le passage au LLM avec citations.

Pour aller plus loin

Maillage série IA Outils 2026

← Précédent MCP servers : panorama DevOps
→ Suite Routines et agents planifiés
Hub IA pour DevOps : parcours 2026

Mesure et amélioration continue

Définissez trois indicateurs simples : taux de DoD vert du premier coup, nombre de refus utiles par session, coût tokens par livrable Git accepté. Revoyez-les toutes les deux semaines. Si le taux DoD stagne sous 50 %, le problème est le brief ou le corpus de rules, pas « le besoin d’un modèle plus cher ». Si le coût explose, imposez un modèle fast par défaut et réservez strong aux plans. Publiez un mini tableau dans le dépôt docs pour rendre le coût visible à l’équipe, comme n’importe quelle ligne cloud.

Retour parcours IA — hub Claude, Codex, ChatGPT et Skills.