Cloud

Azure Container Apps + Dapr : diagnostiquer un appel service-to-service avant de redéployer

Un runbook de production pour séparer app-id, sidecar Dapr, port applicatif, mTLS, résilience et révision lorsqu'un appel interservice échoue dans Azure Container Apps.

17 août 2026 azurecontainer-appsdaprservice-invocationmicroservicesmtlsobservabilitykqlresiliencyrunbookrollbackproduction

Un service orders-api hébergé dans Azure Container Apps appelle payments-api par l’API d’invocation Dapr. Après une nouvelle révision, les requêtes retournent des 500, des 503 ou expirent. Les deux conteneurs semblent pourtant sains et un redéploiement rétablit parfois le flux pendant quelques minutes.

Redéployer immédiatement détruit une partie de la preuve et mélange plusieurs pannes possibles : mauvais app-id, sidecar absent, app-port erroné, protocole incompatible, cible non prête, politique de résilience trop agressive ou révision défectueuse. Le runbook doit d’abord localiser la rupture entre l’application appelante, son sidecar, le sidecar cible et l’application cible.

Figer un appel représentatif

Choisissez une opération qui reproduit le symptôme sans déclencher d’effet métier irréversible. Conservez l’heure UTC, la révision source, la révision cible, la méthode, le statut, la latence et un identifiant de corrélation. Ne partez pas d’un test exécuté depuis un poste d’administration : le chemin utile commence dans le conteneur appelant.

yaml dapr-invocation-incident.yml
incident: inc-20260817-001
environment: <container-apps-environment>
source_app: orders-api
source_revision: <revision>
source_dapr_app_id: orders-api
target_app: payments-api
target_revision: <revision>
target_dapr_app_id: payments-api
method: GET /health/dependencies
first_failure_utc: <timestamp>
last_known_healthy_utc: <timestamp>
correlation_id: <redacted-id>

preserve:
- caller application logs
- caller and target Dapr sidecar logs
- target application logs
- revision state and traffic weights
- Dapr configuration and resiliency changes
- deployment and secret or identity changes

Gelez les nouvelles révisions et les changements de composant Dapr pendant l’analyse. Un test isolé ne suffit pas : mesurez aussi le taux d’erreur par révision afin de savoir si la panne suit une version, une replica ou tout l’environnement.

Vérifier le contrat Dapr des deux applications

Dans Container Apps, appId est le nom logique utilisé par l’appelant. appPort est le port sur lequel le sidecar cible joint l’application, et appProtocol décrit ce dialogue local. Ces valeurs doivent être vérifiées sur la configuration effective, pas seulement dans le dépôt IaC.

bash inspect-dapr-contract.sh
az containerapp show \
--resource-group <resource-group> \
--name orders-api \
--query '{dapr:properties.configuration.dapr,revisionsMode:properties.configuration.activeRevisionsMode}'

az containerapp show \
--resource-group <resource-group> \
--name payments-api \
--query '{dapr:properties.configuration.dapr,ingress:properties.configuration.ingress}'

az containerapp revision list \
--resource-group <resource-group> \
--name payments-api \
--query '[].{name:name,active:properties.active,health:properties.healthState,created:properties.createdTime,traffic:properties.trafficWeight}' \
--output table

Comparez appId, appPort, appProtocol, niveau de log et journalisation de l’API. Vérifiez ensuite que le processus cible écoute réellement sur le port annoncé et sur une interface accessible au sidecar. Un conteneur peut passer sa probe tout en exposant Dapr sur un autre port que celui de l’application.

Le app-id n’est ni le FQDN d’ingress ni forcément le nom métier attendu par le code. Un renommage d’application, une valeur Helm ou Bicep héritée, ou deux applications configurées avec le même identifiant peuvent casser la résolution sans produire une panne réseau classique.

Tester les quatre segments du chemin

L’appel Dapr suit quatre segments : application source vers sidecar local, découverte de la cible, sidecar cible vers application cible, puis réponse. Testez-les dans cet ordre.

Depuis la même révision source, utilisez le point d’entrée local du sidecar si l’image ou une révision de diagnostic contrôlée fournit curl :

bash dapr-local-canary.sh
curl --fail-with-body \
--max-time 10 \
-H 'X-Correlation-ID: <incident-id>' \
'http://localhost:3500/v1.0/invoke/payments-api/method/health/dependencies'

Interprétez le résultat avec les logs, pas avec le seul code HTTP :

  • aucune réponse sur localhost:3500 pointe d’abord vers le sidecar source ou son démarrage ;
  • une erreur de résolution d’app-id pointe vers l’identité logique ou la disponibilité de la cible ;
  • une erreur de connexion au port applicatif pointe vers appPort, le listener, la readiness ou le protocole côté cible ;
  • un statut applicatif avec une trace cible prouve que Dapr a livré l’appel ;
  • un timeout après plusieurs tentatives impose de vérifier la politique de résilience avant de conclure à une panne réseau.

Ne contournez pas Dapr en remplaçant définitivement l’appel par un FQDN d’ingress. Un appel direct peut réussir tout en ignorant la découverte de service, mTLS, les retries et la télémétrie qui font partie du contrat de production.

Corréler sidecars, applications et révisions

Les logs console regroupent les sorties des conteneurs et des sidecars Dapr. Les logs système donnent le contexte de provisioning et de composants. Commencez par la fenêtre de l’appel figé, puis élargissez seulement si nécessaire.

kusto container-apps-dapr-invocation.kql
let start = datetime(<start-utc>);
let stop = datetime(<stop-utc>);
ContainerAppConsoleLogs_CL
| where TimeGenerated between (start .. stop)
| where ContainerAppName_s in ("orders-api", "payments-api")
| where Log_s has_any (
  "<incident-id>",
  "ERR_DIRECT_INVOKE",
  "ERR_HEALTH_NOT_READY",
  "failed to invoke",
  "connection refused",
  "deadline exceeded",
  "retry"
)
| project TimeGenerated, ContainerAppName_s, RevisionName_s,
        ReplicaName_s, ContainerName_s, Log_s
| order by TimeGenerated asc

Complétez avec ContainerAppSystemLogs_CL pour les erreurs de création de composant Dapr, le provisioning de révision et les changements de poids de trafic. Si les tables sont routées vers Azure Monitor plutôt que Log Analytics, adaptez les noms de tables et de colonnes au mode de destination configuré.

Construisez une seule timeline. Une trace source sans trace cible suggère une rupture avant l’application cible. Une trace cible sans log applicatif suggère un problème sidecar-vers-application. Une exécution applicative complète suivie d’un timeout côté source impose de vérifier la réponse, le deadline et les effets déjà produits avant toute nouvelle tentative.

Séparer résilience et amplification

Les retries peuvent masquer un défaut bref, mais ils peuvent aussi multiplier une écriture ou maintenir une dépendance saturée sous charge. Relevez la politique de résilience effective, le timeout, le nombre de tentatives et les opérations auxquelles elle s’applique.

Pour une opération d’écriture, vérifiez avant tout replay :

  • la clé d’idempotence réellement contrôlée par la cible ;
  • l’état aval associé à l’identifiant de corrélation ;
  • le nombre de tentatives déjà observées ;
  • le délai maximum accepté par l’appelant ;
  • le comportement du circuit breaker et sa condition de réouverture.

Une baisse du taux d’erreur accompagnée d’une hausse des tentatives et de la latence n’est pas une restauration. C’est une panne déplacée dans la couche de résilience.

Vérifier mTLS, composants et identité sans tout mélanger

Dapr chiffre les appels interservices avec mTLS, mais une erreur 401 ou 403 peut aussi venir de l’application cible, d’un composant Dapr ou d’une ressource Azure appelée ensuite. Identifiez l’émetteur du statut avant de modifier RBAC.

Le service invocation ne prouve pas que le code cible peut lire Key Vault, publier dans Service Bus ou accéder à Storage. Si l’appel arrive puis échoue sur une dépendance, poursuivez avec l’identité managée réelle de la révision et les logs de cette dépendance. À l’inverse, si aucune trace n’arrive au code cible, élargir les rôles Azure ne corrigera pas la rupture Dapr.

Comparer les révisions avec un canari

Si l’incident suit une nouvelle révision, gardez l’ancienne active le temps de comparer un appel sans effet sur chaque chemin disponible. Utilisez un endpoint de diagnostic authentifié qui vérifie le listener et les dépendances sans écrire. Comparez statut, latence, logs Dapr et trace applicative.

Le canari est valide seulement si :

  • il part du même environnement et du même type de caller que le trafic réel ;
  • il cible explicitement le bon app-id et la bonne méthode ;
  • il laisse une corrélation visible dans les deux applications ;
  • il ne crée aucun effet métier ;
  • il est répété assez longtemps pour traverser plusieurs replicas.

Une seule réussite peut tomber sur une replica saine. Regroupez les résultats par révision et replica avant de modifier les poids ou de désactiver une version.

Décider, valider ou rollbacker

text dapr-invocation-decision.txt
FIX CONFIGURATION
app-id, app-port ou protocole incorrect prouvé sur la configuration effective.
Corriger une seule valeur, créer une révision et exécuter le canari.

FIX APPLICATION
Dapr livre la requête et la cible retourne l'erreur.
Corriger le listener, le handler, l'idempotence ou la dépendance fautive.

HOLD AND OBSERVE
Panne transitoire non expliquée ou preuve incomplète.
Garder les révisions, augmenter temporairement la télémétrie ciblée et ne pas élargir les retries.

ROLLBACK
La panne suit la nouvelle révision ou la dernière configuration Dapr.
Restaurer la configuration connue saine, retirer la révision fautive du chemin actif
et vérifier qu'aucun effet partiel ne sera rejoué.

STOP
Doubles écritures, saturation aval ou circuit breaker continuellement ouvert.
Couper l'opération appelante ou revenir au mode dégradé prévu avant toute nouvelle tentative.

La validation finale exige un taux d’erreur revenu à la normale, une latence compatible avec le budget, des traces complètes source-cible, aucun doublon et plusieurs replicas testées. Conservez la configuration avant/après et la commande de retour. Si le correctif échoue, le rollback doit restaurer la dernière combinaison prouvée de révision applicative et de configuration Dapr, pas simplement « redémarrer les conteneurs ».

Conclusion

Une erreur d’invocation Dapr n’est pas un simple 503 entre deux conteneurs. C’est une chaîne composée de deux applications, deux sidecars, une identité logique, un protocole, une politique de résilience et des révisions qui peuvent évoluer séparément.

En figeant un appel, en testant chaque segment et en corrélant les logs par révision, l’équipe peut décider proprement : corriger le contrat Dapr, corriger l’application, observer avec davantage de preuve ou revenir à la combinaison connue saine. Le résultat attendu n’est pas un redéploiement qui semble aider, mais un chemin interservice expliqué et reproductible.