Version : 1.0 · Date : 2026-10-23
En une phrase : ce chapitre relie les attentes exprimées pour un poste de Run N2/N3 sur des applications IA (chatbots, agents, systèmes multi-agents) aux chapitres du cours, et comble les sujets qui n'y étaient pas encore : External Secrets Operator, Argo CD en synchronisation manuelle, Helm, le Run limité à un namespace, Langfuse côté reporting, Document Intelligence Studio et Grafana « as code ».
1. Le poste en clair#
| Élément | Ce que cela implique pour toi |
|---|---|
| Support N2/N3 de chatbots, agents et systèmes multi-agents | Les métiers signalent « ça ne marche pas » ou « la réponse est fausse » : il faut une investigation structurée jusqu'à la cause |
| Accès aux logs, aux traces Langfuse, aux dashboards Grafana, aux ressources Kubernetes et au code si besoin | Savoir lire chaque outil, et surtout relier les informations entre eux |
| Amélioration continue : scripts, dashboards, workbooks, exports | Chaque incident résolu peut devenir une vue, une requête ou un script ; expliquer le problème que ça résout |
| Une équipe plateforme gère les clusters AKS ; le Run intervient dans des namespaces alloués | Tu n'administres pas le cluster : tu diagnostiques dans ton périmètre et tu escalades avec des faits |
| Équipe Run en construction, travail avec les squads Build | Documenter, standardiser, poser des runbooks |
Priorités de préparation :
- P1 : diagnostic Web Apps et AKS, lecture des logs, traces Langfuse, accès aux secrets, principes du RAG (savoir distinguer panne technique et mauvaise réponse IA).
- P2 : Grafana et Prometheus, Document Intelligence, Azure AI Search, Argo CD.
- Moins prioritaire : les mathématiques des LLM, l'administration complète des clusters, les slots de Web App, le détail des PV/PVC (connaître leur définition suffit).
2. Où chaque attente est traitée#
| # | Prio | Travail attendu | Preuve à pouvoir montrer | Dans le cours |
|---|---|---|---|---|
| 01 | P1 | Web Apps et réseau | Schéma entrée privée (Private Endpoint) et sortie (VNet Integration), DNS, deux subnets distincts | 06, 01 § réseau |
| 02 | P1 | Docker, ACR et pipeline | Image versionnée, droits push et pull, test de démarrage, retour arrière | 05, § 6 ci-dessous |
| 03 | P1 | Logs et supervision Azure | Erreur reproduite, vue Log stream, vue persistée, 3 requêtes KQL | 07, § 6.3 |
| 04 | P1 | Langfuse | Trace à plusieurs étapes, feedback, filtre sur une interaction, export | 07, § 7 |
| 05 | P1 | Run Kubernetes | Panne analysée avec get, describe, logs, événements |
04, § 5 |
| 06 | P1 | Quotas et ressources | Requests, limits et quota expliqués, refus de création compris | § 5 |
| 07 | P1 | Identités et secrets | Schéma de fédération, lecture des manifests du mécanisme utilisé | 04, § 3 (External Secrets) |
| 08 | P1 | RAG de bout en bout | Deux pipelines dessinées, causes de mauvaise réponse | 03 |
| 09 | P2 | Document Intelligence | PDF avec tableau analysé dans le Studio, JSON expliqué | § 8 |
| 10 | P2 | Azure AI Search | Schéma d'index, métadonnées, recherche et filtre | 03, § 9 |
| 11 | P2 | Grafana et Prometheus | Dashboard 3 panneaux, une variable, export / import JSON | 07, § 10 |
| 12 | P2 | Helm et Argo CD | Values reconnues, diff observé, synchronisation manuelle, santé vérifiée | § 4 et § 4.3 |
| 13 | P2 | Modèles, tokens et cache | Fiche des familles, compteurs, cache expliqué | 02 |
| 14 | P1 | Simulation d'entretien | Présentation courte et deux incidents traités en 5 minutes chacun | 09, 08 |
3. Secrets : External Secrets Operator ou Secrets Store CSI Driver#
Deux mécanismes répandus font arriver un secret de Key Vault dans un pod. Ils n'ont pas les mêmes objets ni les mêmes pannes :
la première question à se poser est donc « lequel est utilisé ici ? » (kubectl get secretstore,externalsecret,secretproviderclass -n <namespace>).
| External Secrets Operator (ESO) | Secrets Store CSI Driver (+ provider Key Vault) | |
|---|---|---|
| Objets | SecretStore (ou ClusterSecretStore) : comment joindre le coffre ; ExternalSecret : quels secrets copier |
SecretProviderClass : quel coffre et quels secrets monter |
| Qui lit le coffre | Le contrôleur ESO, avec le ServiceAccount référencé par le SecretStore |
Le provider CSI au montage du volume du pod |
| Résultat | Un Secret Kubernetes créé et rafraîchi (refreshInterval) |
Des fichiers dans le pod, et éventuellement un Secret synchronisé |
| Où voir l'erreur | kubectl describe externalsecret / secretstore (conditions, événements) |
Événement FailedMount sur le pod (pod bloqué en ContainerCreating) |
| Le pod démarre si le coffre est injoignable ? | Oui, avec l'ancienne valeur du Secret s'il existe déjà | Non, le montage échoue |
3.1 Les manifests ESO à savoir lire#
apiVersion: v1
kind: ServiceAccount
metadata:
name: workload-identity-sa
annotations:
azure.workload.identity/client-id: <client id de l'identité managée>
azure.workload.identity/tenant-id: <tenant id>
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: azure-store
spec:
provider:
azurekv:
authType: WorkloadIdentity
vaultUrl: "https://<coffre>.vault.azure.net"
serviceAccountRef:
name: workload-identity-sa # le ServiceAccount fédéré (subject system:serviceaccount:<ns>:workload-identity-sa)
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: chatbot-secrets
spec:
refreshInterval: 1h0m0s # fréquence de relecture du coffre ; 0 = créé une fois, jamais mis à jour
secretStoreRef: { kind: SecretStore, name: azure-store }
target: { name: chatbot-secrets, creationPolicy: Owner }
data:
- secretKey: langfuse-secret-key # clé dans le Secret Kubernetes
remoteRef: { key: secret/langfuse-secret-key } # nom du secret dans Key Vault
La chaîne de confiance est la même que pour Workload Identity (chapitre 04) : identité managée avec le
rôle Key Vault Secrets User, federated credential (issuer OIDC du cluster, subject du ServiceAccount, audience
api://AzureADTokenExchange), ServiceAccount annoté. La différence : c'est le contrôleur ESO qui s'authentifie, pas le code de
l'appli.
3.2 Diagnostic ESO#
kubectl get externalsecret -n <ns> # colonne STATUS / READY
kubectl describe externalsecret chatbot-secrets -n <ns> # condition Ready, raison, message, événements
kubectl describe secretstore azure-store -n <ns> # le store est-il valide ?
kubectl get secret chatbot-secrets -n <ns> # existe-t-il ? (ne jamais afficher la valeur dans un ticket)
kubectl annotate es chatbot-secrets force-sync=$(date +%s) --overwrite # forcer une relecture (action : à faire selon la procédure)
| Symptôme | Cause probable | Vérification |
|---|---|---|
Ready=False sur l'ExternalSecret, message d'authentification |
Federated credential absente ou subject faux ; annotation client-id erronée |
Subject = system:serviceaccount:<ns>:<sa> ; client ID de l'identité |
| Message d'autorisation (403) | Rôle Key Vault Secrets User manquant sur le coffre |
az role assignment list --assignee <client-id> --scope <id du coffre> |
| Message « secret introuvable » | remoteRef.key faux ou secret supprimé / désactivé |
Nom exact dans le coffre |
| Timeout réseau | Coffre privé non résolu en IP privée depuis le cluster | nslookup <coffre>.vault.azure.net depuis un pod du namespace |
| Le secret a changé dans le coffre mais l'appli garde l'ancien | refreshInterval long, et l'appli lit la variable d'environnement au démarrage seulement |
Date de status.refreshTime ; redémarrage du pod nécessaire si le secret est injecté en variable |
Distinguer les trois refus : authentification (qui es-tu ? : identité, fédération), autorisation (as-tu le droit ? : rôle), réseau (peux-tu joindre ? : DNS, private endpoint). C'est la phrase à dire en entretien.
4. Argo CD en synchronisation manuelle, avec Helm#
4.1 Refresh, diff, sync : trois actions différentes#
| Action | Ce qu'elle fait | Ce qu'elle ne fait pas |
|---|---|---|
| Refresh | Relit Git et recalcule l'écart avec le cluster → Synced ou OutOfSync |
Ne déploie rien |
| Diff | Montre ligne par ligne ce qui changerait | Ne déploie rien |
| Sync | Applique l'état de Git dans le cluster | Ne garantit pas que l'appli fonctionne (voir Health) |
Dans un contexte où la sync est manuelle, un commit produit OutOfSync et attend une sync : « Git est à jour mais rien ne
change » est alors un comportement normal, pas une panne. Après la sync, vérifier Healthy puis faire un test applicatif.
argocd app get <app> # source (repo, révision, chemin ou chart), destination, Sync et Health status
argocd app diff <app> # l'écart entre Git et le cluster
argocd app history <app> # les révisions déjà déployées
argocd app sync <app> # action : appliquer Git (selon la procédure de l'équipe)
Le retour arrière durable passe par Git (revert du commit puis sync). Un rollback dans l'interface d'Argo CD revient à une révision précédente, mais si Git n'est pas corrigé, la prochaine sync remettra la version fautive.
L'accès au dépôt (Azure DevOps) peut se faire par PAT, par SSH ou par Workload Identity selon l'environnement : si l'application
affiche une erreur de comparaison (ComparisonError), regarder d'abord les identifiants du dépôt (PAT expiré, droits retirés).
4.2 Helm, ce qu'il faut savoir lire#
Une chart = des templates de ressources Kubernetes + un fichier de values (configuration par environnement). Exemple typique : la chart publique de Langfuse, avec des values propres à l'entreprise conservées dans Azure DevOps.
helm show values <repo>/<chart> # les valeurs par défaut de la chart
helm template <nom> <repo>/<chart> -f values-prd.yaml > rendu.yaml # voir les manifests produits, sans rien installer
Important
Quand Argo CD déploie une chart, il la rend lui-même (équivalent de helm template) et gère le cycle de vie : helm list dans
le namespace peut être vide. Les values effectivement appliquées se lisent dans l'Application Argo CD (onglet Parameters /
App details, ou argocd app get <app> -o yaml), pas avec helm get values.
4.3 Exercice#
Modifier une value dans Git (par exemple le nombre de réplicas), faire un refresh, constater OutOfSync, lire le diff, faire
la sync, vérifier Healthy, puis retrouver le commit et les événements Kubernetes du changement.
5. Le Run dans un namespace#
5.1 Ce qu'on peut faire, ce qu'on escalade#
kubectl config current-context # toujours vérifier le cluster avant tout
kubectl auth can-i get pods -n <ns> # mes droits réels (RBAC via groupes Entra ID)
kubectl auth can-i create pods/exec -n <ns>
kubectl get resourcequota,limitrange -n <ns>
kubectl describe resourcequota <quota> -n <ns> # utilisé / autorisé, par ressource
| Objet | Définition à savoir donner |
|---|---|
| requests | Ce que le conteneur réserve : sert au placement du pod sur un nœud |
| limits | Le plafond : au-delà de la mémoire limite → OOMKilled ; au-delà du CPU limite → ralentissement (throttling) |
| ResourceQuota | Plafond du namespace (CPU, mémoire, nombre d'objets). Une création qui le dépasse est refusée : le pod n'apparaît même pas |
| LimitRange | Valeurs par défaut et bornes par conteneur dans le namespace |
| StatefulSet | Pods avec identité et stockage stables (app-0, app-1) ; il ne gère pas à lui seul la réplication d'une base |
| PV / PVC | Volume persistant / demande de volume par un pod |
| HPA | Ajuste le nombre de réplicas selon une métrique (CPU, mémoire…) |
| KEDA | Scaling piloté par des événements ou des sources externes (file de messages, requêtes…) |
Quota dépassé : le Deployment existe mais aucun nouveau pod n'apparaît. Le message est sur le ReplicaSet :
kubectl describe rs -n <ns> -l app=<app> # événement FailedCreate : ... exceeded quota ...
kubectl get events -n <ns> --sort-by=.lastTimestamp
5.2 Escalader à l'équipe plateforme (modèle de ticket)#
Impact : chatbot X indisponible pour les utilisateurs de Y depuis 14 h 05. Périmètre : namespace
chatbot-prd, clusteraks-…. Constat : nouveau pod non créé,FailedCreate: exceeded quota: requests.memory=4Gi, used=3.8Gi, requested=512Mi. Déjà vérifié : pas de fuite (mémoire stable sur 7 jours dans Grafana), demande cohérente avec la montée en charge prévue. Besoin chiffré : passerrequests.memorydu quota de 4 Gi à 6 Gi, ou valider une réduction des requests à 384 Mi.
6. Web App : chaîne de déploiement et logs#
6.1 « La pipeline est verte mais l'appli ne démarre pas »#
La pipeline a seulement prouvé que l'image est construite et poussée. Vérifier dans l'ordre :
- L'image : le tag configuré sur la Web App existe-t-il dans l'ACR ? (
az acr repository show-tags) - Le pull : l'identité de la Web App a-t-elle
AcrPull, et l'ACR privé est-il joignable (VNet Integration, DNS) ? Logs de plateforme (AppServicePlatformLogs) : échec de pull. - Le port : l'appli écoute-t-elle sur le port attendu (
WEBSITES_PORT) ? Message container didn't respond to HTTP pings. - La configuration : variables et références Key Vault résolues ? Logs de console (
AppServiceConsoleLogs). - Les dépendances : au démarrage, l'appli joint-elle la base, le coffre, le modèle (DNS privé) ?
Retour arrière : redéployer le dernier tag connu comme sain (relancer la pipeline sur ce tag ou remettre ce tag dans la
configuration du conteneur), d'où l'importance de tags immuables (Build.BuildId, SHA du commit) et jamais latest.
6.2 Deux identités, deux droits#
| Qui | Pour quoi | Droit |
|---|---|---|
| La pipeline (service connection) | Pousser l'image, modifier la Web App | Push sur l'ACR (selon le mode RBAC du registre), contributeur sur la Web App |
| La Web App (identité managée) | Tirer l'image | AcrPull |
Les variable groups centralisent les paramètres (noms, environnements) ; la service connection porte l'identité et son périmètre. Un agent de pipeline (hébergé par Microsoft ou auto-hébergé, par exemple sur VMSS) doit pouvoir joindre les endpoints privés utilisés pendant le déploiement.
6.3 Métriques, logs, traces : trois mécanismes#
- Avoir des métriques d'une Web App ne prouve pas que ses logs arrivent dans Log Analytics : les métriques de plateforme sont collectées automatiquement, les logs demandent un diagnostic setting.
- La connection string Application Insights dit où envoyer la télémétrie ; elle n'instrumente pas l'appli à elle seule (il faut un SDK, OpenTelemetry ou l'auto-instrumentation compatible avec la pile).
- Log stream sert pendant une reproduction en direct ; ce n'est pas un stockage.
Trois requêtes à savoir écrire (adapter le nom des colonnes au schéma de la ressource) :
// 1. Erreurs de console sur une période
AppServiceConsoleLogs
| where TimeGenerated > ago(1h) and _ResourceId has "<nom-webapp>"
| where ResultDescription has_any ("error", "exception")
| project TimeGenerated, ResultDescription
| order by TimeGenerated desc
// 2. Erreurs regroupées par type (Application Insights)
exceptions
| where timestamp > ago(24h)
| summarize nb = count() by type, outerMessage
| order by nb desc
// 3. Chronologie complète d'une requête
union requests, dependencies, exceptions, traces
| where operation_Id == "<operation_Id>"
| project timestamp, itemType, name, target, duration, success, message
| order by timestamp asc
7. Langfuse côté exploitation et reporting#
7.1 Organisation et lecture#
Une instance Langfuse (souvent auto-hébergée dans Kubernetes et gérée centralement) contient des organisations (par exemple développement et production) et des projets (un par application). Avant toute analyse : bonne organisation, bon projet, bon environnement, bonne période.
Une trace = une interaction ; elle contient des observations : appels de modèle (générations), appels d'outils, recherche documentaire, chunks retournés. Pour un agent, plusieurs appels de modèle et d'outils se succèdent dans la même trace : une boucle d'outils se voit à la répétition des observations, et à la hausse des tokens et du coût.
Une vue vide ne prouve pas l'absence de trafic : l'information visible dépend de ce que l'application a instrumenté (si seul le message final est envoyé, on ne voit ni les chunks, ni les appels d'outils, ni les tokens par étape).
7.2 Feedbacks utilisateur#
Le pouce levé ou baissé (et un commentaire) est enregistré comme un score rattaché à la trace :
from langfuse import get_client
langfuse = get_client()
langfuse.create_score(
trace_id="<id de la trace>",
name="user-feedback",
value=1, # 1 = pouce levé, 0 = pouce baissé
comment="Réponse exacte et sourcée",
)
Un feedback négatif ne réentraîne pas le modèle : c'est une donnée d'observation et de reporting, utile pour retrouver les interactions à analyser (filtrer les traces avec un score à 0).
7.3 Export et reporting#
Langfuse propose un export depuis l'interface (extraction ponctuelle filtrée), un export planifié vers un stockage, une API publique et une Metrics API (coût, usage, latence, volume, scores agrégés). Un usage courant : export → stockage → Power BI pour suivre l'usage et les feedbacks, avec des identifiants d'utilisateurs plutôt que leurs noms.
Le coût affiché par Langfuse est un calcul à partir des tokens et d'une table de prix configurée dans l'outil : il peut différer du coût Azure réellement facturé (déploiement, mode de facturation, remises, PTU). Toujours confronter à Cost Management.
7.4 Cas : « les traces existent mais le reporting semble faux »#
| Vérification | Pourquoi |
|---|---|
| Projet, organisation, environnement | Mélange dev / prod ou mauvaise appli |
| Fuseau horaire et période | Un export en UTC comparé à un rapport en heure de Paris |
| Filtres et pagination de l'export / de l'API | Export tronqué |
| Rattachement des scores | Feedback attaché à la mauvaise trace (identifiant de trace non propagé) |
| Homogénéité de l'instrumentation | Une version de l'appli n'envoie pas les tokens ou les scores |
| Table de prix des modèles | Nom de modèle ou de déploiement non reconnu → coût vide ou faux |
8. Document Intelligence Studio : lire un résultat Layout#
Exercice : dans Document Intelligence Studio, choisir Layout, charger un PDF contenant du texte et un tableau, puis comparer le rendu visuel et le JSON :
Partie du JSON (analyzeResult) |
Contenu |
|---|---|
content |
Tout le texte extrait (en Markdown si demandé) |
pages |
Une entrée par page : mots, lignes, dimensions |
paragraphs |
Les paragraphes, avec leur rôle éventuel (titre, en-tête…) |
tables → cells |
Chaque cellule avec rowIndex, columnIndex et son contenu |
sections, figures |
Structure du document, images détectées |
À garder : un exemple de mauvaise extraction (tableau fusionné, colonnes mélangées, texte d'image non lu) et son effet en aval : chunk incohérent → mauvais passage récupéré → réponse fausse alors que tout répond en HTTP 200. La description d'une image par un modèle (« verbalisation ») est une étape supplémentaire possible, pas une fonction du modèle Layout.
9. Azure AI Search : trois précisions qui reviennent#
- Le champ clé identifie chaque document de recherche : sa valeur est fournie par l'ingestion (par exemple
<doc-id>_<n° de chunk>). Ne pas supposer que le service l'invente. - Des métadonnées (projet, catégorie, groupe autorisé) permettent de filtrer ; leur seule présence ne sécurise rien : l'application doit appliquer le filtre à chaque requête.
- Le semantic ranker ne s'applique pas automatiquement : il faut une configuration sémantique et
queryType=semantic. Son gain se mesure sur un jeu de questions représentatif, pas sur un chiffre annoncé.
Document manquant dans les réponses : vérifier l'ingestion (job réussi ?), le bon index, les champs chargés (contenu, vecteur, métadonnées) et le filtre de droits appliqué à la requête.
10. Grafana « as code »#
- Un dashboard contient des panneaux ; chaque panneau a sa requête et peut utiliser sa propre data source (Azure Monitor métriques, Log Analytics en KQL, Prometheus en PromQL).
- Une variable (
$namespace,$app) rend le dashboard réutilisable pour plusieurs applications. - L'export JSON (partage → export) conserve la structure et les requêtes ; on peut le réimporter ailleurs ou le déployer par une pipeline.
| Langage | Données | Exemple |
|---|---|---|
| KQL | Logs et tables Azure (Log Analytics, App Insights) | requests | summarize count() by bin(timestamp, 5m), resultCode |
| PromQL | Séries temporelles Prometheus (Azure Monitor workspace) | sum(rate(container_cpu_usage_seconds_total{namespace="$namespace"}[5m])) by (pod) |
Une seule visualisation vide alors que les autres marchent : regarder d'abord sa requête, sa data source, sa variable et la période ; ensuite seulement les droits de l'identité de Grafana et le réseau (voir chapitre 07). L'accès à l'interface Grafana (rôles Grafana Viewer / Editor / Admin) est distinct de l'accès de Grafana aux données (rôles Azure de son identité managée : Monitoring Reader, Monitoring Data Reader sur l'Azure Monitor workspace).
11. La démarche à raconter#
- Qualifier : application, environnement, utilisateurs touchés, début, fréquence, message exact, impact métier.
- Reproduire avec une requête et un identifiant de corrélation.
- Vérifier les changements récents et la santé de l'hébergement.
- Suivre le parcours dans les logs et les traces pour isoler l'étape fautive.
- Corriger dans son périmètre ou escalader au bon propriétaire, avec des faits.
- Valider par un test métier, puis documenter la cause et l'action préventive.
Ne pas redémarrer ni resynchroniser par réflexe : d'abord recueillir les éléments (logs --previous, événements, trace), sinon la
preuve disparaît.
12. Questions à poser en entretien#
- Quel mécanisme amène les secrets dans les pods : External Secrets Operator, CSI Driver, autre ? Quelles versions ?
- Comment sont corrélées une trace Langfuse et une requête Application Insights (identifiant commun) ?
- Quels namespaces, quotas, droits et outils d'accès pour le Run ? Quelle procédure d'escalade vers l'équipe plateforme ?
- Quels objectifs de disponibilité et de délai de prise en charge (SLO, SLA) ? Comment sont priorisés les incidents ?
- Quelles versions de Langfuse et d'Argo CD ? La sync est-elle manuelle partout ?
- Quels sont les incidents les plus fréquents aujourd'hui, et qu'est-ce qui manque le plus à l'équipe Run ?
Références#
Relevé du 23/10/2026.
- External Secrets Operator, provider Azure Key Vault : https://external-secrets.io/latest/provider/azure-key-vault/
- External Secrets Operator, API ExternalSecret (
refreshInterval, conditionReady,force-sync) : https://external-secrets.io/latest/api/externalsecret/ - Langfuse, feedback utilisateur : https://langfuse.com/docs/scores/user-feedback · export et API : https://langfuse.com/docs/api-and-data-platform/overview
- Kubernetes Resource Quotas : https://kubernetes.io/docs/concepts/policy/resource-quotas/ · StatefulSets : https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
- Argo CD, synchronisation : https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/ · Helm : https://argo-cd.readthedocs.io/en/stable/user-guide/helm/
- Document Intelligence Layout : https://learn.microsoft.com/azure/ai-services/document-intelligence/prebuilt/layout
- App Service, supervision et logs : https://learn.microsoft.com/azure/app-service/monitor-app-service
- Application Insights et OpenTelemetry : https://learn.microsoft.com/azure/azure-monitor/app/opentelemetry-enable
- Managed Grafana, autorisations : https://learn.microsoft.com/azure/managed-grafana/how-to-permissions