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 loginsur 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) etsupportia-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) :
- le cluster a l'OIDC issuer et le webhook Workload Identity activés ;
- une federated identity credential sur
id-chatbotavec le sujetsystem:serviceaccount:chatbot:chatbot-sa; - le ServiceAccount porte l'annotation
azure.workload.identity/client-id: <client id de id-chatbot>; - 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: conditionReady=True(raisonSecretSynced) ; le Secret Kubernetes existe. - Un commit dans
supportia-appproduit une nouvelle image : la Web App la prend directement ; côté AKS, l'appli passeOutOfSync, et la sync manuelle crée un nouveau pod. - Un pouce baissé dans l'UI apparaît comme score
user-feedback = 0sur 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 »)#
- « 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). - « 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. - « 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. - « 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`. - « 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`. - « 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#
- « 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`. - « 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. - « 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#
- « 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. - « 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. - « 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. - « 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. - « 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#
- « 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. - « 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. - « 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#
- « 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. - « 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. - « 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éé. - « 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é#
- « 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. - « 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. - « 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. - « 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)#
- « 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). - « 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. - « 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. - « 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. - « 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. - « 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). - « 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. - « 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)#
terraform destroysur le resource group du lab, plan relu puis validé.- Vérifier qu'il ne reste rien :
az resource list -g rg-lab-supportia -o table(doit être vide), puis supprimer le resource group. - 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 - Révoquer les clés Langfuse du projet
supportia-lab, désactiver l'agent auto-hébergé dans Azure DevOps. - 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.
- Argo CD sur AKS (extension
microsoft.argocd, GA pour AKS) : https://learn.microsoft.com/azure/azure-arc/kubernetes/conceptual-gitops-argocd · https://learn.microsoft.com/azure/azure-arc/kubernetes/tutorial-use-gitops-argocd - Zones DNS privées des private endpoints : https://learn.microsoft.com/azure/private-link/private-endpoint-dns
- Managed Grafana et Prometheus (Monitoring Data Reader, Monitoring Reader) : https://learn.microsoft.com/azure/managed-grafana/how-to-connect-azure-monitor-workspace · https://learn.microsoft.com/azure/managed-grafana/how-to-permissions · https://learn.microsoft.com/azure/azure-monitor/metrics/prometheus-grafana
- Rôles Azure OpenAI et AI Search sans clé : https://learn.microsoft.com/azure/developer/ai/keyless-connections · https://learn.microsoft.com/azure/ai-foundry/openai/how-to/on-your-data-configuration#role-assignments
- Prix : API Azure Retail Prices https://prices.azure.com/api/retail/prices (région
swedencentral, EUR) - Langfuse : offres https://langfuse.com/pricing · auto-hébergement https://langfuse.com/self-hosting