Version : 1.0 · Date : 2026-10-23 · Parcours SUPPORT-IA (support N2/N3 de chatbots sur Azure)
En une phrase : une Web App for Containers privée reçoit le trafic par un Private Endpoint, sort vers ACR, Azure OpenAI, AI Search et Key Vault par la VNet integration, tire son image avec sa managed identity, démarre seulement si le conteneur répond sur le bon port à temps ; presque toutes les pannes sont un port, un DNS, une route ou un rôle.

Le choix entre AKS, Container Apps et App Service est traité dans AKS/07-conteneurs-azure-et-entretiens. Ce chapitre suit une Web App for Containers Linux de chatbot (Python ou Node), du build de l'image à la lecture des journaux. La chaîne de build et ACR sont dans 05-gitops-argocd-azure-devops-acr ; le pendant AKS dans 04-aks-pour-le-support.
Ce que tu dois savoir expliquer en entretien#
| Question type | Section |
|---|---|
| « Que fait App Service quand il démarre ton conteneur ? Que veut dire didn't respond to HTTP pings ? » | 2, 3 |
| « Comment la Web App tire-t-elle une image d'un ACR privé ? » | 4 |
| « Private Endpoint ou VNet integration ? Pourquoi un 403 ? » | 5 |
| « Comment passez-vous les secrets ? Que se passe-t-il à la rotation ? » | 6 |
| « Slots, swap, Health check ? » | 7, 8 |
| « Où lis-tu les journaux du conteneur ? » | 9 |
1. Les pièces du puzzle#
| Élément | Rôle | À savoir pour le support |
|---|---|---|
| App Service plan | Les VM (instances) partagées par les apps du plan | Une app qui sature la mémoire gêne ses voisines ; deux VNet integrations au plus par plan |
| Web App for Containers | L'app qui exécute une image (plus d'éventuels sidecars) | Un seul port HTTP exposé par le conteneur |
| Site SCM (Kudu) | Outils : journaux, API de déploiement, terminal | Sur Linux, il tourne dans un conteneur séparé : il ne voit ni les fichiers ni les processus du conteneur applicatif |
| Managed identity (system ou user-assigned) | Pull de l'image, Key Vault references, appels SDK | Un rôle par usage : AcrPull, Key Vault Secrets User, Cognitive Services OpenAI User… |
| Deployment slots | Copies vivantes de l'app (staging) | Standard, Premium ou Isolated ; chaque slot a ses Private Endpoints |
/home |
Stockage persistant partagé entre instances | Désactivé par défaut pour un conteneur Linux personnalisé (WEBSITES_ENABLE_APP_SERVICE_STORAGE) |
Docker Compose (multi-container) sera retiré le 31/03/2027 : les sidecars le remplacent. Une app encore en Compose est un sujet de migration à signaler.
2. Le Dockerfile d'une application de chat#
Règles qui évitent la majorité des pannes de démarrage :
| Règle | Pourquoi |
|---|---|
Écouter sur 0.0.0.0, pas 127.0.0.1 |
App Service envoie ses pings de l'extérieur du conteneur |
Port 80 ou 8080, sinon WEBSITES_PORT |
Ces deux ports sont détectés automatiquement ; tout autre port exige l'app setting (qui n'est pas visible dans le conteneur) |
| Répondre à n'importe quel chemin, même par une 404 | Le warm-up appelle /robots933456.txt ; une application qui ne répond rien aux chemins inconnus est redémarrée en boucle |
Démarrer vite, ou régler WEBSITES_CONTAINER_START_TIME_LIMIT |
230 s par défaut avant échec du démarrage |
Image petite (multi-stage), pas d'écriture massive hors de /home |
Espace disque hôte limité (15 Go par instance), image retirée à chaque changement d'instance |
| Ne pas gérer TLS dans le conteneur | App Service termine TLS aux front-ends ; lire X-Forwarded-Proto |
| Journaux sur stdout / stderr | C'est ce que montrent le Log stream et AppServiceConsoleLogs |
| Requêtes LLM longues : streaming | Une requête HTTP sans réponse est coupée vers 230 s (Windows) ou 240 s (Linux), limite non réglable |
# Python (FastAPI) : image de build puis image d'exécution minimale
FROM python:3.12-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=build /install /usr/local
COPY src/ ./src/
EXPOSE 8000
# 0.0.0.0 : joignable par les pings d'App Service ; WEBSITES_PORT=8000 côté app settings
CMD ["gunicorn", "src.main:app", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000", "--timeout", "600"]
# Node.js : port 8080 détecté sans réglage
FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV PORT=8080
EXPOSE 8080
CMD ["node", "server.js"] # server.js doit écouter sur 0.0.0.0:${PORT}
Piège :
WEBSITES_PORTn'est pasPORT.PORTsert aux images intégrées (runtimes Python, Node d'App Service) ; pour un conteneur personnalisé, c'estWEBSITES_PORTqui dit à la plateforme où envoyer le trafic.
3. Le démarrage d'un conteneur#

| Réglage (app setting ou propriété) | Effet | Défaut |
|---|---|---|
WEBSITES_PORT |
Port vers lequel la plateforme envoie le trafic | 80 (80 et 8080 détectés) |
WEBSITES_CONTAINER_START_TIME_LIMIT |
Délai pour que le conteneur réponde aux pings de démarrage | 230 s (Linux, de 10 à 1 800 s) ; une FAQ Microsoft parle de 240 s |
WEBSITE_WARMUP_PATH / WEBSITE_WARMUP_STATUSES |
Chemin et codes HTTP qui valident le warm-up | Tout code HTTP vaut « prêt » |
| Always On (General settings) | Évite le déchargement de l'app après 20 min sans requête (ping toutes les 5 min) | Désactivé ; disponible à partir de Basic |
WEBSITES_ENABLE_APP_SERVICE_STORAGE |
Monte /home persistant et partagé |
false pour un conteneur Linux personnalisé |
Ce qu'il faut savoir sur un redémarrage :
- Changer l'image : l'ancien conteneur continue de servir tant que le nouveau n'est pas prêt ; le basculement peut prendre plusieurs minutes.
- Redémarrage simple : seules les couches modifiées sont retirées. Nouvelle instance (scale-out, changement de tier, maintenance) : toutes les couches sont retirées, d'où des démarrages plus longs et parfois un dépassement du délai.
- Si le conteneur n'est pas prêt dans le délai, la plateforme fait échouer la tentative et réessaie : l'utilisateur voit des 503.
4. Tirer l'image d'un ACR privé avec une managed identity#
# Lecture : comment l'app tire-t-elle son image ?
az webapp config container show -g rg-chat-prd -n app-chat-prd -o table
az webapp config show -g rg-chat-prd -n app-chat-prd \
--query "{acrMi:acrUseManagedIdentityCreds, acrUami:acrUserManagedIdentityID, image:linuxFxVersion}" -o json
az webapp identity show -g rg-chat-prd -n app-chat-prd
az acr config authentication-as-arm show -r acrchatprd # doit être enabled pour le pull par managed identity
Mise en place (pour comprendre ce qu'on lit) :
PRINCIPAL=$(az webapp identity assign -g rg-chat-prd -n app-chat-prd --query principalId -o tsv)
az role assignment create --assignee "$PRINCIPAL" --role AcrPull --scope "$(az acr show -n acrchatprd --query id -o tsv)"
az webapp config set -g rg-chat-prd -n app-chat-prd --generic-configurations '{"acrUseManagedIdentityCreds": true}'
# Avec une user-assigned identity : préciser son client ID
az webapp config set -g rg-chat-prd -n app-chat-prd --generic-configurations '{"acrUserManagedIdentityID": "<client-id>"}'
# Registre derrière un Private Endpoint : faire passer le pull par la VNet integration
az resource update -g rg-chat-prd -n app-chat-prd --resource-type "Microsoft.Web/sites" \
--set properties.outboundVnetRouting.imagePullTraffic=true
Symptôme (Log stream, AppServicePlatformLogs) |
Cause | Correction |
|---|---|---|
UNAUTHORIZED / token validation failed |
Le registre n'accepte pas les jetons d'audience ARM | az acr config authentication-as-arm update --status enabled |
UNAUTHORIZED |
Pas d'AcrPull, mauvaise identité (user-assigned sans acrUserManagedIdentityID), acrUseManagedIdentityCreds à false |
Rôle et propriétés ci-dessus |
Timeout ou refus sur *.azurecr.io |
Pull par Internet alors que le registre est privé | outboundVnetRouting.imagePullTraffic=true (ancien nom vnetImagePullEnabled ou WEBSITE_PULL_IMAGE_OVER_VNET) |
| Échec sur le data endpoint seulement | DNS du data endpoint (<acr>.<région>.data.azurecr.io) absent |
Enregistrements dans privatelink.azurecr.io |
manifest unknown / tag introuvable |
Tag non poussé (pipeline en échec) | az acr repository show-tags |
Identifiants admin dans DOCKER_REGISTRY_SERVER_* |
Ancienne méthode (admin user ou service principal) | Passer à la managed identity ; ces variables ne sont jamais exposées à l'application |
5. Réseau : entrée par Private Endpoint, sortie par VNet integration#
5.1 Deux mécanismes indépendants#
| Private Endpoint (entrant) | VNet integration (sortant) | |
|---|---|---|
| Sens | Clients → app | App → VNet et au-delà |
| Subnet | N'importe quel subnet (pas forcément dédié) | Subnet dédié et délégué à Microsoft.Web/serverFarms ; jamais le même que celui du Private Endpoint |
| Plans | Basic, Standard, PremiumV2/V3/V4, IsolatedV2, Functions Premium | Selon le plan |
| Par défaut | L'accès public coexiste : il faut désactiver le public network access | Seul le trafic privé (RFC 1918) entre dans le VNet |
| DNS | privatelink.azurewebsites.net : un enregistrement pour l'app et un pour scm |
Les zones privatelink.* des services appelés doivent être liées au VNet d'intégration |
| Slots | Un Private Endpoint par slot (sites-<slot>) |
Configuré par app |
5.2 Routage sortant : application et configuration#
La VNet integration distingue :
- Application routing : le trafic émis par le code.
outboundVnetRouting.applicationTraffic=true(case Outbound internet traffic du portail ; anciens nomsvnetRouteAllEnabled,WEBSITE_VNET_ROUTE_ALL) envoie tout le trafic applicatif dans le VNet, sinon seulement le trafic RFC 1918. - Configuration routing : ce que la plateforme fait au démarrage, par la route publique par défaut : pull de l'image
(
imagePullTraffic), partage de contenu, sauvegarde, et Key Vault references. outboundVnetRouting.allTraffic=trueroute les deux familles dans le VNet (CLI uniquement, auditable par Azure Policy).
Règle à retenir : une app Linux qui appelle des Private Endpoints doit router tout son trafic dans le VNet ; sinon, selon la résolution DNS, une partie part par la route publique et revient en 403.
# Lecture : intégration, accès public, routage
az webapp vnet-integration list -g rg-chat-prd -n app-chat-prd -o table
az webapp show -g rg-chat-prd -n app-chat-prd \
--query "{subnet:virtualNetworkSubnetId, public:publicNetworkAccess, routing:outboundVnetRouting}" -o json
az webapp config show -g rg-chat-prd -n app-chat-prd --query "{routeAllLegacy:vnetRouteAllEnabled, alwaysOn:alwaysOn, health:healthCheckPath}"
5.3 Les zones DNS privées d'un chatbot#
| Service | Zone privée | Remarque |
|---|---|---|
| Web App (entrant) | privatelink.azurewebsites.net |
Enregistrements app et app.scm |
| Azure OpenAI / Foundry | privatelink.openai.azure.com, privatelink.cognitiveservices.azure.com, privatelink.services.ai.azure.com |
Une zone par suffixe réellement utilisé |
| AI Search | privatelink.search.windows.net |
|
| Key Vault | privatelink.vaultcore.azure.net |
|
| ACR | privatelink.azurecr.io |
Registre et data endpoint régional |
Le nom appelé ne change jamais (monapp.azurewebsites.net, moncoffre.vault.azure.net) : c'est la chaîne CNAME vers privatelink.*
qui doit finir sur une IP privée depuis le VNet. Le certificat par défaut couvre *.azurewebsites.net : il faut appeler la Web
App par ce nom, pas par l'IP du Private Endpoint.
5.4 Diagnostic réseau#
# Depuis le conteneur de l'app (terminal SSH du portail ou az webapp exec en préversion, si l'image a un shell)
nslookup oai-chat-prd.openai.azure.com # IP privée attendue (10.x, 172.16-31.x, 192.168.x)
nslookup kv-chat-prd.vault.azure.net
curl -sv https://srch-chat-prd.search.windows.net -o /dev/null # 403 de la ressource ≠ timeout réseau
# Depuis une VM du réseau client : la Web App elle-même
nslookup app-chat-prd.azurewebsites.net # doit passer par app-chat-prd.privatelink.azurewebsites.net
| Symptôme | Cause | Vérifier | Corriger |
|---|---|---|---|
| L'utilisateur reçoit 403 en appelant l'app | Son DNS résout vers l'IP publique alors que l'accès public est coupé | nslookup depuis le poste ou le VNet client |
Zone privatelink.azurewebsites.net liée, ou redirection DNS depuis le DNS de l'entreprise |
| Kudu / déploiement injoignable en privé | Enregistrement scm manquant |
nslookup app.scm.azurewebsites.net |
Ajouter l'enregistrement (automatique avec un DNS zone group) |
| L'app reçoit 403 d'OpenAI, Search ou Key Vault, avec un message « public network access is disabled » | Résolution vers l'IP publique, ou trafic non routé dans le VNet | nslookup depuis le conteneur, outboundVnetRouting |
Lier la zone au VNet d'intégration, router tout le trafic |
| 403 « permission » avec résolution privée correcte | Rôle data plane manquant sur la managed identity | az role assignment list --assignee <principalId> --all |
Attribuer le rôle (propagation jusqu'à quelques minutes) |
| Timeouts vers un Private Endpoint malgré une IP privée | NSG ou UDR sur le subnet d'intégration, firewall, peering absent | Network Watcher (next hop), règles NSG | Ouvrir le flux 443 |
| L'app ne peut plus sortir vers Internet après « route all » | Le VNet n'a pas de sortie (NAT Gateway, firewall) | Table de routage du subnet | Prévoir la sortie, ou n'envoyer que le trafic privé |
6. App settings et Key Vault references#
Les app settings arrivent dans le conteneur comme variables d'environnement ; les modifier redémarre l'app. Une valeur peut être une Key Vault reference :
@Microsoft.KeyVault(SecretUri=https://kv-chat-prd.vault.azure.net/secrets/search-api-key)
@Microsoft.KeyVault(VaultName=kv-chat-prd;SecretName=search-api-key)
| Point | Comportement |
|---|---|
| Identité utilisée | La system-assigned par défaut ; une user-assigned via keyVaultReferenceIdentity (utile quand le secret est lu dès la création) |
| Droits | Key Vault Secrets User (modèle Azure RBAC) ou permission Get (access policies) |
| Coffre privé | Accès par le VNet : configurer la VNet integration et router le trafic ; le coffre ne doit pas dépendre des IP publiques sortantes de l'app |
| Rotation | Sans version dans la référence : nouvelle version prise sous 24 h ; tout changement de configuration force la relecture ; API …/config/configreferences/appsettings/refresh pour forcer |
| Échec de résolution | L'app reçoit la chaîne littérale @Microsoft.KeyVault(...) à la place du secret |
| Diagnostic | Portail : Environment variables → éditer le setting → statut de résolution ; Diagnose and solve problems → Key Vault Application Settings Diagnostics |
| Slots | Un coffre par environnement : marquer ces settings deployment slot setting (sticky) |
| Journal du coffre | Un SecretGet 403 depuis l'IP publique suivi d'un succès depuis l'IP privée est normal |
Encore mieux que la référence : pour Azure OpenAI ou AI Search, appeler le service avec la managed identity de l'app (
DefaultAzureCredential) plutôt qu'avec une clé : plus de secret, plus de rotation.
7. Deployment slots et swap#
| Étape du swap (cible = production) | Ce qui se passe |
|---|---|
| 1 | Les settings sticky de la cible sont appliqués au slot source, qui redémarre (fin de la phase 1 en swap with preview) |
| 2 | Attente du redémarrage de toutes les instances ; un échec annule le swap |
| 3 | Warm-up : requête sur / (ou WEBSITE_SWAP_WARMUP_PING_PATH, codes WEBSITE_SWAP_WARMUP_PING_STATUSES) |
| 4 | Échange des règles de routage : la production sert l'app préchauffée |
| 5 | L'ancien code repart dans le slot source avec ses settings |
- Slots disponibles à partir de Standard ; nombre limité par tier.
- Retour arrière : refaire le même swap immédiatement.
- Swap with preview :
az webapp deployment slot swap … --action preview, puis--action swapou--action reset. - Pour un conteneur : déployer le nouveau tag dans le slot
staging(tâcheAzureWebAppContainer@1ouaz webapp config container set --slot staging), tester, puis swapper ; tag unique, paslatest. - La configuration du Health check n'est pas propre au slot : elle suit le swap.
az webapp deployment slot list -g rg-chat-prd -n app-chat-prd -o table
az webapp config container show -g rg-chat-prd -n app-chat-prd --slot staging -o table
8. Health check#
| Point | Comportement |
|---|---|
| Fonctionnement | Ping du chemin choisi sur chaque instance, toutes les minutes ; sain = code 200 à 299 en moins d'une minute |
| Retrait | Après 10 échecs par défaut (WEBSITE_HEALTHCHECK_MAXPINGFAILURES, de 2 à 10), l'instance sort du load balancer |
| Limite de retrait | Pas plus de 50 % des instances par défaut (WEBSITE_HEALTHCHECK_MAXUNHEALTHYWORKERPERCENT) ; si tout est malade, rien n'est retiré |
| Remplacement | Instance malade depuis 1 h remplacée ; au plus une par heure et trois par jour par plan |
| Prérequis utile | Au moins deux instances ; avec une seule, pas de reroutage (seulement le remplacement) |
| Pièges | Redirections non suivies (301/302 = malade) ; chemin derrière l'authentification ; changer la config redémarre l'app |
Pour un chatbot : un chemin /ready qui vérifie les dépendances critiques (avec un délai court), distinct d'un /healthz de
liveness sans dépendance, comme sur AKS (chapitre 04).
9. Les journaux : Log stream, Diagnostic settings, Application Insights#

| Outil | Contenu | Activation | Quand |
|---|---|---|---|
| Log stream | stdout/stderr du conteneur et messages de la plateforme, en direct | az webapp log config --docker-container-logging filesystem, puis az webapp log tail ou portail Monitoring > Log stream |
« Ça démarre ? Ça plante maintenant ? » |
| Fichiers Docker | Journaux bruts de l'hôte et du conteneur | Kudu : https://<app>.scm.azurewebsites.net/api/logs/docker (zip : /api/logs/docker/zip) ; en privé, passe par l'enregistrement scm |
Récupérer l'historique récent |
| Diagnostic settings → Log Analytics | AppServiceConsoleLogs (stdout/stderr), AppServicePlatformLogs (opérations du conteneur), AppServiceHTTPLogs (requêtes) ; AppServiceAppLogs seulement pour Java SE et Tomcat sur Linux |
Diagnostic setting sur l'app | Chercher hier, corréler, alerter |
| Application Insights | Requêtes, dépendances (appels OpenAI, Search), exceptions, traces | Instrumentation OpenTelemetry ou SDK dans le code | Suivre une requête, trouver l'appel lent |
| Diagnose and solve problems | Détecteurs : démarrage du conteneur, Key Vault references, disponibilité | Rien | Premier réflexe dans le portail |
// Démarrages et arrêts du conteneur sur les deux dernières heures
AppServicePlatformLogs
| where TimeGenerated > ago(2h)
| order by TimeGenerated desc
| take 100
// Codes HTTP renvoyés par l'app (500, 502, 503 ?)
AppServiceHTTPLogs
| where TimeGenerated > ago(1h)
| summarize requetes = count(), duree_moyenne_ms = avg(TimeTaken) by ScStatus
| order by requetes desc
// Recherche du message de démarrage raté
AppServicePlatformLogs
| where TimeGenerated > ago(1d)
| search "didn't respond"
Piège : les requêtes du Health check ne figurent pas dans les journaux HTTP (elles sont internes). Et
AppServiceAppLogsreste vide pour un conteneur Python ou Node : leurs journaux sont dansAppServiceConsoleLogs.
10. Pannes fréquentes : symptôme → cause → vérification → correction#
| Symptôme | Cause probable | Vérifier | Corriger |
|---|---|---|---|
| Container … didn't respond to HTTP pings on port: 80, failing site start | L'app écoute sur un autre port | Log stream ; port dans le Dockerfile | WEBSITES_PORT = port réel |
| Même message, bon port | Écoute sur 127.0.0.1 ; démarrage plus long que la limite ; ne répond pas aux chemins inconnus |
Log stream (l'app a-t-elle fini de démarrer ?) | 0.0.0.0, WEBSITES_CONTAINER_START_TIME_LIMIT, WEBSITE_WARMUP_PATH |
| Le conteneur s'arrête en boucle | CMD faux, variable manquante, dépendance absente, mémoire |
Log stream, AppServiceConsoleLogs |
Corriger l'image ou les settings |
Échec du pull (UNAUTHORIZED) |
Identité, AcrPull, authentication-as-arm |
Section 4 | Section 4 |
| Échec du pull (timeout) | Registre privé, pull hors VNet | outboundVnetRouting |
imagePullTraffic=true, DNS |
L'app voit @Microsoft.KeyVault(...) au lieu du secret |
Référence non résolue : rôle, réseau, nom du secret, syntaxe | Statut dans le portail, détecteur | Rôle Key Vault Secrets User, route all, nom exact |
| 403 pour l'utilisateur | DNS public alors que l'accès public est coupé | nslookup côté client |
Zone privée liée, DNS d'entreprise |
| 403 de l'app vers OpenAI / Search / Key Vault | DNS ou routage public, ou rôle manquant | Message (réseau ou permission), nslookup dans le conteneur |
Section 5.4 |
| 502 / 503 intermittents | Conteneur qui redémarre, instance retirée par Health check, warm-up raté | AppServicePlatformLogs, métrique Health check |
Selon la cause |
| 504 ou coupure après ~4 min | Réponse LLM non streamée au-delà de 230/240 s | Durée des requêtes (TimeTaken) |
Streaming, traitement asynchrone |
| Lenteur de la première requête | App déchargée (Always On désactivé), nouvelle instance | Always On, événements de scale | Activer Always On, préchauffage |
| Nouvelle version déployée, ancienne servie | Même tag (latest), basculement pas fini, mauvais slot |
az webapp config container show |
Tag unique, attendre, vérifier le slot |
| no space left on device | Image ou écritures trop grosses sur le disque hôte (15 Go) | Taille de l'image | Image plus petite, écrire dans /home |
| Kudu ne montre pas les fichiers de l'app | Le site SCM est un autre conteneur (Linux) | — | Utiliser le terminal SSH du conteneur |
flowchart TD
A["Web App KO"] --> B{"Le conteneur démarre-t-il ?<br/>Log stream / AppServicePlatformLogs"}
B -- "Échec du pull" --> B1["Identité, AcrPull, authentication-as-arm,<br/>imagePullTraffic, DNS privatelink.azurecr.io"]
B -- "didn't respond to HTTP pings" --> B2["WEBSITES_PORT, 0.0.0.0,<br/>START_TIME_LIMIT, warm-up"]
B -- "S'arrête en boucle" --> B3["stdout : exception, variable,<br/>@Microsoft.KeyVault non résolue"]
B -- "Démarré" --> C{"L'utilisateur atteint-il l'app ?"}
C -- "403 / timeout" --> C1["Private Endpoint, DNS privatelink.azurewebsites.net,<br/>public network access"]
C -- "Oui, erreurs 5xx" --> D{"Les dépendances répondent-elles ?"}
D -- "403 / timeout" --> D1["DNS des services, route all,<br/>rôles de la managed identity"]
D -- "Lentes" --> D2["Application Insights : dépendances,<br/>quotas OpenAI, streaming"]
11. Lab : une Web App for Containers privée qui tire depuis ACR (60 minutes)#
Dans une subscription de test uniquement ; supprimer le resource group à la fin.
RG=rg-lab-webapp; LOC=francecentral; ACR=acrlab$RANDOM; APP=app-lab-$RANDOM
az group create -n $RG -l $LOC
az acr create -g $RG -n $ACR --sku Premium
az acr import -n $ACR --source mcr.microsoft.com/azuredocs/aci-helloworld:latest --image hello:1 # tag unique
az appservice plan create -g $RG -n plan-lab --is-linux --sku P0v3
az webapp create -g $RG -p plan-lab -n $APP --container-image-name $ACR.azurecr.io/hello:1
PRINCIPAL=$(az webapp identity assign -g $RG -n $APP --query principalId -o tsv)
az role assignment create --assignee "$PRINCIPAL" --role AcrPull --scope "$(az acr show -n $ACR --query id -o tsv)"
az webapp config set -g $RG -n $APP --generic-configurations '{"acrUseManagedIdentityCreds": true}'
az webapp log config -g $RG -n $APP --docker-container-logging filesystem
az webapp restart -g $RG -n $APP # nouveau pull, cette fois avec la managed identity
az webapp log tail -g $RG -n $APP # observer le pull et le démarrage
Variantes à tester, une à la fois, en lisant le Log stream :
WEBSITES_PORT=8000alors que l'image écoute sur 80 : message didn't respond to HTTP pings, puis retirer le setting.- Retirer le rôle
AcrPullet redémarrer : échec du pullUNAUTHORIZED. - Ajouter un app setting
@Microsoft.KeyVault(VaultName=inexistant;SecretName=x): statut d'erreur dans le portail, chaîne littérale dans la variable. - Aller plus loin : VNet, Private Endpoint ACR,
az acr update --public-network-enabled false, puis observer l'échec du pull tant queimagePullTrafficet la zone DNS ne sont pas en place.
Nettoyage : az group delete -n $RG --yes --no-wait.
12. Questions d'entretien#
1. Que fait App Service quand il démarre ton conteneur ?
Il tire l'image (avec la managed identity si configurée), résout les app settings dont les Key Vault references, démarre le conteneur, puis envoie des pings HTTP sur le port déclaré jusqu'à obtenir une réponse, dans la limite de
WEBSITES_CONTAINER_START_TIME_LIMIT(230 s par défaut). Ensuite seulement il route le trafic.
2. didn't respond to HTTP pings on port 80 : ta démarche ?
C'est un symptôme. Je vérifie le port réel de l'application et
WEBSITES_PORT, qu'elle écoute sur0.0.0.0, qu'elle répond même par une 404 aux chemins inconnus, et la durée de démarrage dans le Log stream ; si elle charge un index ou un modèle, j'augmente la limite de démarrage.
3. Private Endpoint et VNet integration, quelle différence ?
Le Private Endpoint ne sert qu'au trafic entrant : il donne une IP privée à l'app, et il faut couper l'accès public. La VNet integration ne sert qu'au trafic sortant : elle place l'app dans un subnet délégué pour joindre les Private Endpoints d'OpenAI, Search, Key Vault et ACR. Les deux s'utilisent ensemble pour une app 100 % privée.
4. Comment la Web App tire-t-elle une image d'un ACR privé ?
Managed identity avec
AcrPull,acrUseManagedIdentityCreds(etacrUserManagedIdentityIDsi user-assigned), le registre doit accepter les jetons d'audience ARM, et le pull doit passer par le VNet (outboundVnetRouting.imagePullTraffic) avec la zoneprivatelink.azurecr.iopour le registre et son data endpoint.
5. L'app reçoit un 403 d'Azure OpenAI. Comment sais-tu si c'est le réseau ou les droits ?
Le message le dit souvent (« public network access is disabled » contre une erreur de permission). Je vérifie depuis le conteneur que le FQDN résout vers une IP privée et que le trafic est routé dans le VNet ; si oui, c'est le rôle
Cognitive Services OpenAI Userde la managed identity, avec quelques minutes de propagation.
6. Comment gérez-vous les secrets ?
Le mieux est de ne pas en avoir : managed identity vers OpenAI et Search. Sinon, des Key Vault references dans les app settings, lues avec la managed identity (
Key Vault Secrets User), marquées sticky par slot. Sans version, la rotation est prise sous 24 h ; une référence non résolue arrive en chaîne littérale dans l'application.
7. Log stream, Diagnostic settings, Application Insights : lequel et quand ?
Le Log stream pour voir en direct un démarrage ou un plantage ; les Diagnostic settings vers Log Analytics pour chercher après coup (
AppServiceConsoleLogs,AppServicePlatformLogs,AppServiceHTTPLogs) et alerter ; Application Insights pour suivre une requête et ses dépendances, par exemple un appel OpenAI lent.
8. Comment déployer sans coupure et revenir en arrière ?
Déployer le nouveau tag dans un slot staging, le tester (swap with preview si besoin), puis swapper : la production reçoit une app déjà démarrée et préchauffée. Retour arrière : refaire le swap. Attention aux settings sticky et au Health check qui suit le swap.
9. Les utilisateurs ont des coupures au bout de quatre minutes sur les longues réponses.
App Service coupe une requête sans réponse vers 230 s (Windows) ou 240 s (Linux), ce n'est pas réglable. Pour un chatbot, il faut streamer la réponse du LLM ou passer en traitement asynchrone.
10. Le Health check retire une instance : comment ça marche ?
Ping toutes les minutes ; après 10 échecs par défaut (réglable de 2 à 10), l'instance sort du load balancer, sans dépasser 50 % des instances ; si elle reste malade une heure, elle est remplacée. Il faut au moins deux instances pour en profiter.
13. Fiche à retenir#
- Port : 80 ou 8080 détectés, sinon
WEBSITES_PORT; écouter sur0.0.0.0; un seul port ; répondre à/robots933456.txt. -
WEBSITES_CONTAINER_START_TIME_LIMIT: 230 s par défaut (10 à 1 800) ; échec = 503 et nouvel essai. - Always On : déchargement après 20 min sinon ; requêtes coupées vers 230/240 s → streaming.
- Pull ACR : managed identity +
AcrPull+acrUseManagedIdentityCreds(+acrUserManagedIdentityID) +authentication-as-arm+imagePullTraffic. - Private Endpoint = entrant (couper l'accès public,
privatelink.azurewebsites.net+scm) ; VNet integration = sortant (subnet déléguéMicrosoft.Web/serverFarms). -
outboundVnetRouting:applicationTraffic(exvnetRouteAllEnabled),imagePullTraffic(exvnetImagePullEnabled),allTraffic. - Zones :
privatelink.openai.azure.com,privatelink.search.windows.net,privatelink.vaultcore.azure.net,privatelink.azurecr.io. - Key Vault reference :
@Microsoft.KeyVault(SecretUri=…), system identity par défaut, rotation sous 24 h, échec = chaîne littérale, settings sticky. - Slots dès Standard ; swap = settings appliqués, redémarrage, warm-up, échange ; retour arrière = re-swap.
- Health check : 1 ping/min, 10 échecs, 50 % max retirés, remplacement après 1 h, deux instances minimum.
- Journaux : Log stream (
--docker-container-logging filesystem),AppServiceConsoleLogs,AppServicePlatformLogs,AppServiceHTTPLogs, Application Insights. - Docker Compose retiré le 31/03/2027 → sidecars.
Références#
Relevé du 23/10/2026 sur Microsoft Learn :
- Configure a custom container for Azure App Service
- Environment variables and app settings reference
- FAQ App Service on Linux (conteneurs, warm-up, ports)
- Configure an App Service app (Always On, general settings)
- Web request times out after 230 seconds
- Use private endpoints for App Service apps
- Integrate your app with an Azure virtual network et gérer le routage de la VNet integration
- Azure Private Endpoint private DNS zone values
- Troubleshoot 403 errors through a private endpoint et private endpoint DNS resolution
- Use Key Vault references as app settings
- Set up staging environments (deployment slots) et deployment best practices
- Monitor App Service instances by using Health check
- Enable diagnostic logs et App Service monitoring data reference
- Troubleshoot slow web app performance (cold start)
- Configure registry acceptance of Microsoft Entra authentication scopes (authentication-as-arm)
- Role-based access control for Azure OpenAI
- AzureWebAppContainer@1