WIKI
SUPPORT-IA Support N2/N3 · plateformes IA Azure

10 · Lab de bout en bout : monter, observer et dépanner un chatbot RAG sur Azure

27 min de lecture Mis à jour le 23 octobre 2026
Sur cette page

Version : 1.0 · Date : 2026-10-23

En une phrase : on monte en 12 heures maximum une copie réduite de la plateforme du client (chatbot RAG avec OCR sur AKS et Web App for Containers privée, déployé par Azure DevOps et Argo CD, observé par App Insights, Log Analytics, Managed Prometheus, Managed Grafana et Langfuse), puis on la casse volontairement pour s'entraîner au support N2/N3.

Ce document est le plan du projet : ce qu'on construit, pourquoi chaque brique existe, dans quel ordre on la monte, comment on vérifie qu'elle marche, puis la liste des pannes à diagnostiquer. Les concepts sont expliqués dans les chapitres 01 à 09 ; ici on pratique.


0. Règles du lab#

Règle Pourquoi
12 heures maximum entre le premier apply et le destroy Budget (§ 2) et aucune ressource oubliée
Un resource group dédié rg-lab-supportia, rien d'autre dedans Un seul terraform destroy nettoie tout
Alerte de budget à 40 € sur le resource group, créée en premier Être prévenu avant toute dérive
Tout en Terraform, plan relu puis apply validé par Hamid Règles de sécurité : aucune écriture sans accord explicite
Données fictives uniquement (PDF publics ou rédigés pour le lab) Aucune donnée client ni personnelle dans le lab
Aucune clé dans le code : identités managées, Workload Identity, Key Vault C'est le modèle du client, et ce qu'on va dépanner

1. Ce qu'on construit#

Le schéma de référence complet est au chapitre 01. Le lab en est une version réduite à un seul projet, avec les mêmes briques et les mêmes chemins.

flowchart LR
  U[Utilisateur] -->|1 question| ING[AKS · ingress<br/>application routing]
  U -.->|depuis le réseau privé| WA[Web App for Containers<br/>private endpoint]
  ING --> API[Pod chatbot-api<br/>FastAPI · Workload Identity]
  WA --> API2[Conteneur chatbot-api<br/>même image]
  API -->|2 recherche hybride| SRCH[(Azure AI Search<br/>index vectoriel)]
  API -->|3 génération| AOAI[Foundry · Azure OpenAI<br/>gpt-4.1-mini]
  API -->|secret Langfuse| KV[Key Vault<br/>External Secrets Operator]
  JOB[Job ingestion] -->|OCR| DI[Document Intelligence<br/>prebuilt-layout]
  JOB -->|embeddings| AOAI
  JOB -->|chunks + vecteurs| SRCH
  BLOB[(Storage<br/>PDF sources)] --> JOB
  API -. traces OTel .-> AI[App Insights<br/>Log Analytics]
  API -. traces LLM .-> LF[Langfuse]
  AKS[(Métriques AKS)] -. scrape .-> PROM[Managed Prometheus] --> GRAF[Managed Grafana]
  ADO[Azure DevOps<br/>pipeline] -->|build + push| ACR[ACR Premium] 
  ADO -->|tag dans Git| GIT[(Repo GitOps)] --> ARGO[Argo CD] --> ING

Trois chemins à savoir suivre de bout en bout (c'est ce qu'on dépannera) :

Chemin Étapes Où regarder
Question → réponse Ingress ou Web App → API → AI Search → Azure OpenAI → réponse App Insights (requests, dependencies), Langfuse (trace LLM), logs du pod / Log stream
Document → index Blob → Job → Document Intelligence → chunking → embeddings → AI Search Logs du Job, état de l'index (nombre de documents), erreurs Document Intelligence
Commit → pod Azure DevOps → ACR → tag dans le repo GitOps → Argo CD → Deployment → pods Historique du pipeline, tags ACR, statut Argo CD, kubectl rollout

2. Inventaire et coût pour 12 heures#

Prix publics Azure en euros, région Sweden Central (disponibilité des modèles), relevés le 23/10/2026 avec l'API Azure Retail Prices. Hors taxes, à revérifier le jour du lab.

Ressource SKU du lab Pourquoi ce choix Prix unitaire 12 h
AKS Niveau Free, 2 nœuds Standard_D4s_v5 (3 si Langfuse auto-hébergé) Comme le client, assez pour Argo CD, l'API, le job et le monitoring 0,18 €/h par nœud 4,3 à 6,5 €
ACR Premium Private endpoint (seul le Premium le permet), comme le client 1,47 €/jour 1,5 €
AI Search Basic Vectoriel, hybride, semantic ranker ; le Free n'a pas de private endpoint 0,089 €/h 1,1 €
Web App for Containers Plan Linux P0v3 VNet integration + private endpoint 0,078 €/h 0,9 €
Azure OpenAI (Foundry) gpt-4.1-mini (Global Standard) + text-embedding-3-small Bon rapport coût / qualité pour un chatbot 0,40 € / 1,40 € par million de tokens (entrée / sortie) 1 à 2 €
Document Intelligence S0, modèle prebuilt-layout OCR + structure en Markdown pour le chunking 8,80 € / 1 000 pages < 1 €
Log Analytics + App Insights Workspace-based, Analytics Logs Un seul workspace pour tout 2,63 €/Go (premiers Go gratuits) 0 à 5 €
Azure Monitor workspace Managed Prometheus Métriques AKS pour Grafana 0,097 € / 10 M d'échantillons < 1 €
Managed Grafana Standard Connexion à l'Azure Monitor workspace (Standard requis) 0,038 €/h + 5,28 € par utilisateur et par mois < 1 €
Key Vault, Storage, VNet, DNS privé, private endpoints Standard Socle privé — 1 à 2 €
VM de rebond + agent Azure DevOps Standard_B2s Tester le privé et builder vers l'ACR privé — < 1 €
Langfuse Cloud Hobby (gratuit : 50 000 unités/mois, 30 jours, 2 utilisateurs) ou auto-hébergé Traces LLM comme chez le client 0 € 0 €
Total estimé ≈ 12 à 22 €

Astuce

Langfuse auto-hébergé fait plus « vrai projet » mais demande Web, Worker, PostgreSQL, Redis/Valkey, ClickHouse et un stockage objet (chart Helm officiel) : compter 1 h de plus et un 3ᵉ nœud. Pour 12 h, Langfuse Cloud Hobby suffit ; l'interface et les traces sont les mêmes.


3. Prérequis#

3.1 Outils sur le poste#

Outil Rôle Vérification
Azure CLI + extensions k8s-extension, aks-preview si besoin Tout piloter az version
Terraform ≥ 1.9, provider azurerm ~> 4.0 Créer l'infra terraform version
kubectl + kubelogin Cluster avec Entra ID az aks install-cli
Helm 3 Langfuse auto-hébergé (option) helm version
CLI argocd Diagnostic Argo CD argocd version --client
Python 3.12 Chatbot et ingestion en local python --version

3.2 Côté Azure et Azure DevOps (à préparer par Hamid)#

  • Abonnement de test et compte avec Owner sur le resource group du lab (pour créer les rôles).
  • az login sur ce PC, puis vérification en lecture seule : az account show.
  • Quota des modèles dans la région (lecture seule, avant tout) :
    az cognitiveservices usage list --location swedencentral -o table
    
  • Organisation + projet Azure DevOps, deux repos : supportia-app (code + pipeline) et supportia-gitops (manifestes).
  • Compte Langfuse Cloud (région EU) : clés publique et secrète d'un projet supportia-lab.

4. Valeurs à valider avant le premier plan#

Ce sont des propositions : rien n'est créé tant qu'elles ne sont pas validées.

Paramètre Proposition Remarque
Abonnement à fournir Jamais un abonnement de production
Région swedencentral Modèles Azure OpenAI disponibles ; vérifier le quota
Préfixe des noms supportia Ex. aks-supportia-lab, acrsupportialab (ACR : lettres et chiffres seulement)
Plage du VNet 10.60.0.0/16 Ne doit chevaucher aucun réseau existant
Déploiement chat gpt-4.1-mini, Global Standard, petite capacité (ex. 30 K TPM) Assez pour le lab, permet de provoquer un 429
Déploiement embeddings text-embedding-3-small (1 536 dimensions) Doit être le même à l'ingestion et à la recherche
Accès AKS API publique limitée à ton IP (--api-server-authorized-ip-ranges) + Entra ID Simple pour le lab ; le client peut avoir un cluster privé
Langfuse Cloud Hobby, région EU Ou auto-hébergé (§ 2)

5. Réseau#

flowchart TB
  subgraph VNET[VNet 10.60.0.0/16]
    S1[snet-aks · 10.60.0.0/22<br/>nœuds et pods Azure CNI Overlay]
    S2[snet-webapp-int · 10.60.4.0/26<br/>VNet integration, délégué Microsoft.Web]
    S3[snet-pe · 10.60.5.0/26<br/>tous les private endpoints]
    S4[snet-jump · 10.60.6.0/27<br/>VM de rebond + agent Azure DevOps]
  end
  S3 --- PE1[PE ACR] & PE2[PE Key Vault] & PE3[PE AI Search] & PE4[PE Azure OpenAI] & PE5[PE Document Intelligence] & PE6[PE Web App] & PE7[PE Blob]
Service Private DNS zone (liée au VNet)
ACR privatelink.azurecr.io
Key Vault privatelink.vaultcore.azure.net
AI Search privatelink.search.windows.net
Azure OpenAI privatelink.openai.azure.com
Document Intelligence (Foundry Tools) privatelink.cognitiveservices.azure.com
Web App (et Kudu scm) privatelink.azurewebsites.net
Blob privatelink.blob.core.windows.net

Règle d'or du support : un service avec Public network access = Disabled n'est joignable que si le nom se résout en IP privée depuis l'appelant. Premier test d'un « ça ne répond plus » vers un service privé : nslookup <service>.<suffixe> depuis le pod ou la VM, réponse attendue en 10.60.5.x.

Note

Le chemin de supervision (Azure Monitor Private Link Scope) n'est pas privatisé dans le lab : trop long pour 12 h, et ce n'est pas le sujet. Les données de télémétrie passent par les endpoints publics d'Azure Monitor.


6. Identités et rôles#

Aucune clé : chaque appelant a une identité et le plus petit rôle utile. C'est la première chose à vérifier quand un appel répond 401 ou 403.

Identité Utilisée par Rôle Portée
id-chatbot (user-assigned) Pods chatbot-api via Workload Identity Cognitive Services OpenAI User Ressource Azure OpenAI
Search Index Data Reader AI Search
Key Vault Secrets User Key Vault
id-ingestion (user-assigned) Job d'ingestion via Workload Identity Storage Blob Data Reader Compte de stockage
Cognitive Services User Document Intelligence
Cognitive Services OpenAI User Azure OpenAI (embeddings)
Search Index Data Contributor AI Search
Identité système de la Web App Pull d'image + appels AcrPull ACR
mêmes rôles que id-chatbot
Identité kubelet d'AKS Pull d'image AcrPull ACR
Identité système de Grafana Data sources Monitoring Reader Resource group du lab
Monitoring Data Reader Azure Monitor workspace (Prometheus)
Hamid Portail, Grafana Grafana Admin Grafana
(test) un 2ᵉ utilisateur Grafana en lecture Grafana Viewer Grafana

Workload Identity en 4 pièces (à savoir réciter, voir le chapitre 04) :

  1. le cluster a l'OIDC issuer et le webhook Workload Identity activés ;
  2. une federated identity credential sur id-chatbot avec le sujet system:serviceaccount:chatbot:chatbot-sa ;
  3. le ServiceAccount porte l'annotation azure.workload.identity/client-id: <client id de id-chatbot> ;
  4. le pod porte le label azure.workload.identity/use: "true" et utilise ce ServiceAccount.

S'il manque une seule pièce, l'appli tombe en erreur d'authentification : c'est la panne n° 8 du § 13.


7. L'application#

7.1 Ce que fait le chatbot#

Composant Rôle Techno
chatbot-api POST /chat : reçoit une question, cherche dans l'index, appelle le modèle, renvoie la réponse avec ses sources Python 3.12, FastAPI, azure-identity, azure-search-documents, openai
UI minimale Page HTML servie par l'API (GET /) pour tester à la main HTML + fetch
ingest Job Kubernetes : PDF du blob → Document Intelligence → chunks → embeddings → index Python, azure-ai-documentintelligence
Télémétrie Traces, dépendances, exceptions vers App Insights ; traces LLM (prompt, tokens, coût, latence) vers Langfuse azure-monitor-opentelemetry, SDK langfuse

7.2 Arborescence du repo supportia-app#

supportia-app/
├── app/
│   ├── main.py            API FastAPI : /chat, /healthz, /readyz, /
│   ├── rag.py             recherche hybride + semantic ranker, assemblage du prompt
│   ├── llm.py             appel Azure OpenAI, usage des tokens, retries sur 429
│   ├── telemetry.py       configure_azure_monitor() + client Langfuse
│   └── static/index.html  UI minimale
├── ingest/
│   ├── ingest.py          blob → Document Intelligence → chunks → embeddings → index
│   └── index-schema.json  schéma de l'index (champs, vecteur, HNSW, semantic)
├── Dockerfile             image unique (API), port 8000
├── requirements.txt
└── azure-pipelines.yml    build + push ACR + mise à jour du tag dans le repo GitOps

7.3 Le cœur du RAG (extraits commentés)#

Recherche hybride (mots-clés + vecteur) puis reclassement sémantique, avec authentification sans clé :

from azure.identity import DefaultAzureCredential
from azure.search.documents import SearchClient
from azure.search.documents.models import VectorizedQuery
from openai import AzureOpenAI
from azure.identity import get_bearer_token_provider

cred = DefaultAzureCredential()  # Workload Identity dans le pod, identité système dans la Web App
search = SearchClient(SEARCH_ENDPOINT, "docs-index", cred)
aoai = AzureOpenAI(
    azure_endpoint=AOAI_ENDPOINT,
    azure_ad_token_provider=get_bearer_token_provider(cred, "https://cognitiveservices.azure.com/.default"),
    api_version=AOAI_API_VERSION,
)

def retrieve(question: str, k: int = 5):
    vector = aoai.embeddings.create(model="text-embedding-3-small", input=question).data[0].embedding
    results = search.search(
        search_text=question,                       # partie mots-clés (BM25)
        vector_queries=[VectorizedQuery(vector=vector, k_nearest_neighbors=50, fields="content_vector")],
        query_type="semantic", semantic_configuration_name="default",
        top=k, select=["id", "title", "content", "page"],
    )
    # @search.score = score hybride (RRF) ; @search.reranker_score = 0 à 4 (semantic ranker)
    return [r for r in results if r["@search.reranker_score"] >= 1.5]

Génération avec suivi des tokens (indispensable pour le coût et la latence) :

resp = aoai.chat.completions.create(
    model="gpt-4.1-mini",
    messages=[{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": prompt_avec_contexte}],
    temperature=0.2, max_tokens=600,
)
usage = resp.usage  # prompt_tokens, completion_tokens, prompt_tokens_details.cached_tokens

Le seuil 1.5 sur le reranker et k=5 sont des réglages : les changer est une des pannes du § 13 (réponses hors sujet ou « je ne sais pas » trop fréquents).

7.4 Ingestion avec OCR#

from azure.ai.documentintelligence import DocumentIntelligenceClient
from azure.ai.documentintelligence.models import AnalyzeDocumentRequest, DocumentContentFormat

di = DocumentIntelligenceClient(DI_ENDPOINT, cred)
poller = di.begin_analyze_document(
    "prebuilt-layout", AnalyzeDocumentRequest(url_source=blob_url_avec_sas_ou_bytes),
    output_content_format=DocumentContentFormat.MARKDOWN,   # titres et tableaux conservés
)
markdown = poller.result().content
chunks = split_by_headings(markdown, max_tokens=800, overlap_tokens=100)   # chunking par sections

Chaque chunk devient un document de l'index : id, title, page, content, content_vector (1 536 dimensions), source_url, ingested_at (pour vérifier la fraîcheur).

7.5 Dockerfile#

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ ./app/
# Un seul port, le même sur AKS (containerPort) et sur la Web App (WEBSITES_PORT=8000)
EXPOSE 8000
USER 1000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

8. Repo GitOps, Helm et Argo CD#

Comme dans beaucoup d'équipes, l'appli est packagée en chart Helm : les templates sont communs, chaque environnement a son fichier de values. Argo CD rend la chart (équivalent de helm template) et gère le cycle de vie : helm list reste vide, c'est normal.

supportia-gitops/
└── chatbot/                        chart Helm de l'appli
    ├── Chart.yaml
    ├── values.yaml                 valeurs par défaut
    ├── values-lab.yaml             valeurs de l'environnement : image.tag, réplicas, seuils RAG, ressources
    └── templates/
        ├── serviceaccount.yaml     annotations azure.workload.identity/client-id et tenant-id
        ├── configmap.yaml          endpoints, noms d'index et de déploiements, seuils RAG
        ├── secretstore.yaml        External Secrets Operator : comment joindre le Key Vault (Workload Identity)
        ├── externalsecret.yaml     quels secrets copier : clés Langfuse, connection string App Insights
        ├── deployment.yaml         image acrsupportialab.azurecr.io/chatbot-api:{{ .Values.image.tag }}, probes, requests/limits
        ├── service.yaml            ClusterIP 80 → 8000
        ├── ingress.yaml            classe webapprouting.kubernetes.azure.com
        ├── resourcequota.yaml      plafond du namespace (CPU, mémoire, nombre de pods)
        └── job-ingest.yaml         ingestion (relancée à la demande)

Secrets : le lab utilise External Secrets Operator (objets SecretStore et ExternalSecret, installé par sa chart Helm officielle), le mécanisme le plus courant en entreprise ; le Secrets Store CSI Driver est la variante vue au chapitre 04. Comparaison et diagnostic : chapitre 11 § 3.

Quota : le namespace chatbot reçoit un ResourceQuota (par exemple requests.memory: 2Gi, pods: 6) pour s'entraîner au « déploiement sans nouveau pod » et à l'escalade chiffrée vers l'équipe plateforme.

Application Argo CD (extension AKS microsoft.argocd, GA pour AKS), en synchronisation manuelle comme dans de nombreux environnements de production :

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: chatbot
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://dev.azure.com/<org>/<projet>/_git/supportia-gitops   # accès par PAT ou Workload Identity
    targetRevision: main          # branche suivie : une erreur ici = « Git à jour mais rien ne change »
    path: chatbot
    helm:
      valueFiles: [values-lab.yaml]   # oublier ce fichier = les valeurs par défaut s'appliquent
  destination:
    server: https://kubernetes.default.svc
    namespace: chatbot
  # Pas de syncPolicy.automated : un commit rend l'appli OutOfSync, et attend une sync manuelle.
  # Variante à tester en fin de lab : automated { prune: true, selfHeal: true }.

À savoir lire : Sync status (Synced / OutOfSync : le cluster est-il conforme à Git ?) et Health status (Healthy / Progressing / Degraded / Missing : les ressources fonctionnent-elles ?). Une appli peut être Synced et Degraded : Git est bien appliqué, mais les pods plantent. Refresh relit Git et compare, Sync applique : ce ne sont pas les mêmes actions.


9. Pipeline Azure DevOps#

L'ACR est privé : le build tourne sur un agent auto-hébergé dans le VNet (la VM de rebond), comme dans beaucoup d'entreprises. Les paramètres communs (nom de l'ACR, de la Web App, du repo GitOps) sont dans un variable group vg-supportia-lab.

trigger: { branches: { include: [main] } }
pool: { name: lab-selfhosted }          # agent dans snet-jump, résout privatelink.azurecr.io

variables:
- group: vg-supportia-lab               # acr, webapp, gitopsRepo
- name: tag
  value: $(Build.BuildId)               # jamais « latest » : sinon Argo CD ne voit aucun changement

steps:
- task: Docker@2
  displayName: Build et push vers ACR
  inputs:
    containerRegistry: sc-acr-supportia  # service connection : identité de la pipeline (droit de push)
    repository: chatbot-api
    command: buildAndPush
    tags: $(tag)

- script: |
    git clone https://$(System.AccessToken)@dev.azure.com/<org>/<projet>/_git/supportia-gitops gitops
    cd gitops
    sed -i "s#^\(  tag:\).*#\1 \"$(tag)\"#" chatbot/values-lab.yaml
    git -c user.name=pipeline -c user.email=pipeline@lab commit -am "chatbot-api $(tag)"
    git push origin HEAD:main
  displayName: AKS - mettre à jour le tag dans le repo GitOps (Argo CD synchronisera)

- task: AzureCLI@2
  displayName: Web App - pointer le conteneur sur la nouvelle image
  inputs:
    azureSubscription: sc-azure-supportia
    scriptType: bash
    scriptLocation: inlineScript
    inlineScript: |
      az webapp config container set -g rg-lab-supportia -n $(webapp) \
        --container-image-name $(acr).azurecr.io/chatbot-api:$(tag)
      # retour arrière : relancer ce pipeline sur le dernier tag connu comme sain

Deux chemins de livraison, comme chez beaucoup de clients : AKS en GitOps (la pipeline écrit dans Git, Argo CD applique : modèle pull) et Web App en push (la pipeline modifie directement la Web App). Deux identités, deux droits : la pipeline pousse dans l'ACR et modifie la Web App ; la Web App et le kubelet tirent l'image (AcrPull).


10. Observabilité#

Brique Mise en place dans le lab Ce qu'on y lit
Log Analytics workspace law-supportia-lab Créé par Terraform Tout ce qui est « log » : tables ContainerLogV2, KubeEvents, KubePodInventory, AppServiceConsoleLogs, AppServiceHTTPLogs
Application Insights (workspace-based) Connection string dans Key Vault, injectée dans le pod requests, dependencies (AI Search, OpenAI, Key Vault), exceptions, traces ; corrélées par operation_Id
Container insights Add-on monitoring d'AKS vers le workspace Logs des conteneurs, événements, inventaire des pods
Managed Prometheus Azure Monitor workspace + métriques AKS activées CPU, mémoire, redémarrages, requêtes ingress
Managed Grafana Azure Monitor workspace lié, rôles du § 6 Tableaux de bord Kubernetes fournis + un tableau « chatbot »
Log stream (Web App) Logs applicatifs activés sur la Web App Sortie console du conteneur en direct (non conservée)
Diagnostic settings (Web App) Vers le workspace Même console, mais stockée et requêtable en KQL
Langfuse Clés dans Key Vault Chaque appel LLM : prompt, contexte, tokens, coût, latence, scores

Requête de départ à garder sous la main : une question lente ou en erreur, découpée par étape (détails au chapitre 07).

requests
| where timestamp > ago(1h) and name has "/chat"
| join kind=inner (dependencies | where timestamp > ago(1h)) on operation_Id
| project timestamp, operation_Id, request_ms = duration, resultCode,
          dep = target, dep_type = type, dep_ms = duration1, dep_ok = success1
| order by timestamp desc

11. Ordre de montage (≈ 4 h, le reste pour les pannes)#

# Étape Durée Vérification avant de passer à la suite
1 Alerte de budget, resource group, VNet, sous-réseaux, zones DNS privées 20 min az network private-dns zone list -g rg-lab-supportia -o table
2 Log Analytics, App Insights, Azure Monitor workspace, Key Vault, Storage 20 min Workspace visible, Key Vault en accès privé
3 ACR Premium + private endpoint, VM de rebond + agent Azure DevOps 30 min nslookup acrsupportialab.azurecr.io → 10.60.5.x depuis la VM
4 Foundry (Azure OpenAI + déploiements), Document Intelligence, AI Search + private endpoints 30 min Quota OK, déploiements Succeeded
5 AKS : OIDC issuer, Workload Identity, monitoring, Managed Prometheus, application routing ; External Secrets Operator (chart Helm) 35 min kubectl get nodes, pods kube-system et external-secrets sains
6 Identités, federated credentials, rôles du § 6 15 min az role assignment list --assignee <id> -o table
7 Extension Argo CD, Application chatbot (chart Helm, sync manuelle), première sync 20 min argocd app get chatbot : Synced / Healthy
8 Pipeline Azure DevOps : variable group, premier build, push, mise à jour du tag 30 min Tag dans l'ACR, Argo CD OutOfSync → sync manuelle → Synced / Healthy
9 Ingestion : 5 PDF dans le blob, Job d'ingestion 15 min Nombre de documents de l'index > 0
10 Web App for Containers (même image), private endpoint, VNet integration 20 min curl depuis la VM de rebond → 200
11 Managed Grafana, data sources, tableau de bord 15 min Graphiques remplis
12 Langfuse : clés dans Key Vault (synchronisées par ESO), bouton pouce de l'UI envoyé comme score 10 min Une trace par question, un score par feedback

12. Validation de bout en bout (avant de casser quoi que ce soit)#

  • Une question dans l'UI (via l'ingress) obtient une réponse avec ses sources (titre et page du PDF).
  • La même question sur la Web App privée, depuis la VM de rebond, répond pareil.
  • Dans App Insights, Transaction search → la requête /chat → End-to-end transaction : on voit l'appel à AI Search puis à Azure OpenAI, avec leur durée.
  • Dans Langfuse, la trace montre le prompt, le contexte injecté, les tokens d'entrée et de sortie, le coût et la latence.
  • Dans Grafana, le tableau Kubernetes montre les pods chatbot ; la data source Azure Monitor liste les ressources du lab.
  • Le Log stream de la Web App affiche les logs en direct pendant une question.
  • kubectl describe externalsecret -n chatbot : condition Ready=True (raison SecretSynced) ; le Secret Kubernetes existe.
  • Un commit dans supportia-app produit une nouvelle image : la Web App la prend directement ; côté AKS, l'appli passe OutOfSync, et la sync manuelle crée un nouveau pod.
  • Un pouce baissé dans l'UI apparaît comme score user-feedback = 0 sur la bonne trace dans Langfuse.

13. Programme des pannes#

Le principe : l'énoncé est ce que dirait un utilisateur ou le N1. Tu ne regardes pas comment la panne a été créée : tu diagnostiques avec la méthode du chapitre 08, tu corriges, puis tu expliques la cause racine à voix haute (c'est l'exercice de l'entretien). Le corrigé est replié sous chaque panne.

Disponibilité (« le chatbot ne répond plus »)#

  1. « Plus rien ne répond, page d'erreur 502. »
    CorrigéLe `containerPort` du Deployment ne correspond plus au port de l'appli (8000 → 8080). Pods `Running` mais readiness KO, Service sans endpoint. Voir `kubectl get endpoints`, `describe pod` (probes).
  2. « Depuis la mise en prod de ce matin, erreur 500 sur chaque question. »
    CorrigéNouvelle image avec une variable d'environnement renommée dans le code mais pas dans la ConfigMap. Exception visible dans App Insights (`exceptions`) et dans `kubectl logs`. Retour arrière : revert Git, Argo CD resynchronise.
  3. « Les pods redémarrent en boucle. »
    Corrigé`limits.memory` abaissée à 128 Mi : `OOMKilled`, `CrashLoopBackOff`. Voir `lastState.terminated.reason` dans `describe pod` et la métrique de mémoire dans Grafana.
  4. « Le déploiement est parti, mais il n'y a pas de nouveau pod. »
    Corrigé`ImagePullBackOff` : tag inexistant dans l'ACR (faute de frappe dans le repo GitOps), ou kubelet sans `AcrPull`. Lire les events du pod, comparer avec `az acr repository show-tags`.
  5. « La Web App affiche "Application Error". »
    Corrigé`WEBSITES_PORT` supprimé : *container didn't respond to HTTP pings on port 80*. Visible dans le Log stream et `AppServicePlatformLogs`.
  6. « La Web App n'arrive plus à joindre le modèle (timeout). »
    CorrigéLien de la zone `privatelink.openai.azure.com` vers le VNet supprimé : le nom se résout en IP publique, refusée car l'accès public est désactivé. `nslookup` depuis la console Kudu / la VM.

Identité et secrets#

  1. « Le nouveau pod ne démarre pas : il manque une variable de configuration sensible. »
    Corrigé`remoteRef.key` erroné dans l'`ExternalSecret` (ou rôle `Key Vault Secrets User` retiré) : `Ready=False` sur l'ExternalSecret, le Secret Kubernetes n'est pas créé, le pod reste en `CreateContainerConfigError`. Lire `kubectl describe externalsecret` puis `describe pod`.
  2. « Erreur 401 sur chaque appel au modèle. »
    CorrigéLabel `azure.workload.identity/use` retiré du pod (ou sujet de la federated credential qui ne correspond plus au ServiceAccount). Pas de variables `AZURE_*` injectées ; `AADSTS70021` dans les logs.
  3. « 403 sur la recherche, mais le modèle répond. »
    CorrigéRôle `Search Index Data Reader` retiré de `id-chatbot`. Dependency AI Search en échec 403 dans App Insights, appel OpenAI non atteint ou réponse sans contexte.

Qualité des réponses RAG#

  1. « Le bot répond "je ne sais pas" à tout. »
    CorrigéSeuil du reranker monté à 3,5 dans la ConfigMap : tous les chunks sont filtrés. Le contexte est vide dans Langfuse.
  2. « Les réponses sont fausses ou hors sujet depuis hier. »
    CorrigéIngestion relancée avec un autre modèle d'embeddings (dimensions ou espace vectoriel différents de celui de la requête). La recherche vectorielle renvoie du bruit ; la recherche par mots-clés seule marche.
  3. « Le document ajouté ce matin n'est jamais cité. »
    CorrigéJob d'ingestion en échec (rôle `Cognitive Services User` retiré sur Document Intelligence) : nombre de documents de l'index inchangé, `ingested_at` ancien.
  4. « Les réponses citent des morceaux de phrases sans contexte. »
    CorrigéChunks réglés à 100 tokens sans chevauchement : le contexte est haché. À voir dans les chunks récupérés (Langfuse) et dans l'index.
  5. « Le bot invente des informations absentes des documents. »
    CorrigéPrompt système modifié (consigne « réponds uniquement à partir du contexte » retirée) et température à 1. Comparer le prompt entre deux traces Langfuse.

Performance et coût#

  1. « Le bot est devenu lent, sans erreur. »
    Corrigé`top` passé de 5 à 50 chunks : contexte énorme, plus de tokens d'entrée, TTFT en hausse. Décomposer la durée par dependency (KQL du § 10), tokens dans Langfuse.
  2. « Erreurs intermittentes aux heures de pointe. »
    CorrigéCapacité du déploiement réduite : `429 Too Many Requests` avec `retry-after`. Visible dans les dependencies et dans les métriques Azure OpenAI.
  3. « La facture des tokens a triplé. »
    Corrigé`max_tokens` passé de 600 à 4 000 et historique de conversation renvoyé en entier à chaque tour. Tokens de sortie et d'entrée par trace dans Langfuse ; le cache de prompt ne joue plus car le préfixe change.

Déploiement et GitOps#

  1. « Le code est à jour dans Git, mais rien ne change en prod. »
    CorrigéDeux variantes : `targetRevision` pointe vers une autre branche (Argo CD reste `Synced` sur une révision ancienne), ou simplement l'appli est `OutOfSync` et **attend la sync manuelle** (comportement normal en sync manuelle). Lire `argocd app get` : révision suivie, statut, dernière opération.
  2. « Quelqu'un a corrigé à la main, et la correction a disparu à la sync suivante. »
    CorrigéLa sync remet l'état de Git (immédiatement avec `selfHeal: true`, à la prochaine sync en mode manuel ; l'appli était d'ailleurs `OutOfSync` entre-temps). La bonne correction passe par Git.
  3. « Le pipeline est vert, mais aucune nouvelle image n'arrive. »
    CorrigéTag fixe `latest` dans le pipeline : le manifeste ne change pas, Argo CD ne voit rien, le pod n'est pas recréé.
  4. « Le pipeline échoue au push vers l'ACR. »
    CorrigéPipeline basculé sur un agent hébergé par Microsoft : il ne résout pas le nom privé de l'ACR (accès public désactivé). Message `denied` ou timeout dans le log du pipeline.

Observabilité#

  1. « Grafana n'affiche plus aucune donnée. »
    CorrigéRôle `Monitoring Data Reader` retiré à l'identité de Grafana sur l'Azure Monitor workspace. Test de la data source en erreur 403.
  2. « Un collègue ne voit pas les tableaux de bord. »
    CorrigéAucun rôle Grafana (Viewer) attribué à son compte : il ne peut pas se connecter ou ne voit rien. Les droits Azure (Reader) ne suffisent pas pour Grafana.
  3. « App Insights ne montre plus les requêtes du chatbot. »
    CorrigéConnection string erronée dans le secret (ou `configure_azure_monitor` désactivé par une variable). L'appli marche, mais `requests` est vide ; les logs du pod montrent l'erreur d'export.
  4. « Dans Langfuse, les traces n'ont plus de coût. »
    CorrigéNom du modèle envoyé à Langfuse différent de celui de sa table de prix (déploiement renommé). Tokens présents, coût vide.

Compléments (Run en namespace, Helm, Web App, reporting)#

  1. « Le déploiement est validé, mais le nombre de pods ne bouge pas. »
    Corrigé`ResourceQuota` du namespace atteint après une hausse des requests mémoire dans les values : `FailedCreate ... exceeded quota` sur le **ReplicaSet** (`kubectl describe rs`), aucun pod créé. Rédiger l'escalade chiffrée (modèle au chapitre 11 § 5.2).
  2. « On a changé la clé dans le Key Vault il y a une heure, l'appli utilise toujours l'ancienne. »
    Corrigé`refreshInterval` de l'ExternalSecret à 24 h, et le secret est injecté en variable d'environnement (lue au démarrage seulement). Forcer la relecture (annotation `force-sync`) puis redémarrer le Deployment (`kubectl rollout restart`), selon la procédure.
  3. « Après la sync, l'appli tourne avec un seul réplica et sans les bons seuils. »
    Corrigé`helm.valueFiles` retiré de l'Application Argo CD : la chart s'applique avec ses valeurs par défaut. Le diff Argo CD et `argocd app get -o yaml` le montrent.
  4. « Argo CD affiche une erreur et ne compare plus rien. »
    Corrigé`ComparisonError` : PAT du dépôt expiré ou révoqué. Mettre à jour les identifiants du repo dans Argo CD.
  5. « La nouvelle version de la Web App plante, il faut revenir en arrière tout de suite. »
    CorrigéRelancer la pipeline (ou `az webapp config container set`) avec le **dernier tag sain**, vérifier le démarrage dans le Log stream, puis analyser la version fautive au calme.
  6. « Les métriques de la Web App sont là, mais aucun log dans Log Analytics. »
    CorrigéDiagnostic setting supprimé : les métriques de plateforme sont collectées sans lui, pas les logs (`AppServiceConsoleLogs` vide).
  7. « Le rapport des feedbacks du mois montre deux fois moins de votes que prévu. »
    CorrigéExport filtré sur un seul environnement et limité à la première page ; ou feedback envoyé avec un identifiant de trace non propagé (scores orphelins). Voir chapitre 11 § 7.4.
  8. « Sur le dashboard, un seul panneau est vide. »
    CorrigéLa variable `$namespace` du panneau pointe vers un namespace renommé ; les autres panneaux n'utilisent pas cette variable. Requête et variable du panneau d'abord, droits ensuite.

Astuce

Pour chaque panne, note dans un fichier : symptôme → premières vérifications → cause → correctif → prévention. Ces 33 fiches sont ta meilleure préparation : en entretien, on te demandera « raconte-moi comment tu diagnostiques X ».


14. Exercices guidés (sans panne)#

Exercice Déroulé Preuve à garder
Document Intelligence Studio Modèle Layout sur un PDF avec un tableau ; comparer le rendu et le JSON (pages, paragraphs, tables → cells) ; garder un exemple de mauvaise extraction Capture + explication de l'impact sur les chunks
Index AI Search Lire le schéma, 3 documents (contenu, vecteur, métadonnées) ; comparer recherche textuelle, vectorielle, hybride ; tester un filtre Requêtes et résultats commentés
Dashboard Grafana 3 panneaux (volume, taux d'erreur, durée), une variable $namespace, export JSON puis réimport Fichier JSON + capture
Langfuse Trace à plusieurs étapes, feedback rattaché, filtre sur une interaction, export Export + explication d'une investigation en 2 minutes
Helm helm template de la chart avec values-lab.yaml ; repérer d'où vient chaque valeur du Deployment Extrait du rendu commenté
Tokens et cache Relever les compteurs d'un appel (prompt_tokens, completion_tokens, cached_tokens) ; refaire un appel au long préfixe identique et vérifier cached_tokens Les deux réponses JSON
KQL Erreurs sur une période, erreurs par type, chronologie d'une requête (operation_Id) Les 3 requêtes

15. Nettoyage (obligatoire, au plus tard à H+12)#

  1. terraform destroy sur le resource group du lab, plan relu puis validé.
  2. Vérifier qu'il ne reste rien : az resource list -g rg-lab-supportia -o table (doit être vide), puis supprimer le resource group.
  3. Purger les ressources en soft delete, sinon leurs noms restent pris :
    az keyvault list-deleted -o table            # puis az keyvault purge --name <nom>
    az cognitiveservices account list-deleted -o table
    
  4. Révoquer les clés Langfuse du projet supportia-lab, désactiver l'agent auto-hébergé dans Azure DevOps.
  5. Contrôler le coût réel le lendemain dans Cost Management (filtre sur le resource group).

Références#

Relevé du 23/10/2026.

Sur cette page
Échap

Tape au moins 2 lettres. Plusieurs mots : tous doivent apparaître.