Dépannage
DépannageExploitationDepuis la version 0.1.0
Guide d'exploitation : configurer, déployer et observer la passerelle. Ce que fait la capacité côté produit : guide produit ; l'appeler : guide développeur.
| Symptôme | Cause probable | Ce qu'il faut faire |
|---|---|---|
| Quelle version tourne ? | - | Le pied de la console (écran de connexion compris) affiche les deux ; ou flexway version (docker run --rm ghcr.io/flexwiz/flexway:<version> version), build dans le /config d'administration. dev désigne un build qui n'est pas une release : son commit dit lequel |
flexway validate : x-flexway-sunset ... is set on an operation that is not deprecated |
Une date de retrait sur une opération (ou héritée de info) qui n'est pas dépréciée |
Ajouter deprecated: true (en gRPC option deprecated = true;), ou retirer la date |
Démarrage refusé : kms.key "...": kms key absent or unusable |
La clé du chiffrement par champ n'existe pas au KMS (faute de frappe) ou ne peut pas envelopper (clé Vault non dérivée) | Corriger kms.key ou créer la clé - Chiffrement par champ |
Consommateurs Kafka en CORRUPT_MESSAGE après un changement de kms.key |
Les enregistrements écrits avant nomment l'ancienne clé | La déclarer dans kms.previous_keys - Chiffrement par champ |
Démarrage refusé : reads the schema registry's wire format and is isolated by the tenant attribute alone |
Une consommation d'un canal au format filaire du registre filtrée sur l'attribut tenant, sans liaison : les messages d'un producteur Kafka ordinaire n'ont aucun attribut |
Lier le canal au tenant (x-flexway-bind: tenant, adresse {tenant}.<sujet>), ou le déclarer public (x-flexway-public: true) |
POST d'une porte x-flexway-publish : 504 upstream_timeout |
Le courtier n'a pas accusé dans le budget : issue inconnue, le message a pu être écrit | Renvoyer avec la même Idempotency-Key ; vérifier la santé du courtier, élargir timeout |
503 overloaded sur une publication |
Le tampon max_in_flight du courtier est plein : il accuse moins vite qu'on ne publie |
Lire flexway_async_publish_duration_seconds ; relever max_in_flight ou la capacité du courtier |
502 upstream_error sur une publication |
Le courtier a refusé : ACL, sujet absent (la passerelle n'en crée jamais), message rejeté ; AMQP : aucune file ne prend le message | Créer le sujet du tenant, accorder l'écriture au principal de la passerelle, lier une file à l'échange |
Démarrage refusé : is consumed and isolates no tenant |
Une consommation sur un canal ni lié au tenant, ni filtré sur l'en-tête tenant |
x-flexway-bind: tenant sur un paramètre du canal, ou l'en-tête tenant dans headers de chaque message |
| Un abonné WebSocket reçoit un message deux fois | Au moins une fois : pas d'accusé dans ack_timeout, ou une coupure avant l'accusé |
Accuser chaque id ({"ack": "<id>"}) ; dédupliquer sur source + id |
flexway validate échoue sur « opération sans amont » |
Une opération ajoutée au contrat sans x-flexway-upstream, et pas d'amont default |
Ajouter l'extension, ou déclarer un amont default |
flexway import krakend sort en 3 |
Des éléments de la configuration importée n'ont pas d'équivalent ; le reste est écrit | Lire la section « No equivalent » de coverage.md : chaque ligne dit pourquoi et, s'il existe, l'équivalent à écrire à la main |
flexway import krakend : the converted configuration does not load - nothing written |
Le résultat ne démarrerait pas : opération du contrat qu'aucun endpoint ne couvre, agrégation, sd: dns, validateur sans audience, politique nommée par le contrat et non définie |
Le message du chargeur nomme l'opération ou la clé ; poser x-flexway-upstream au contrat ou corriger, puis relancer |
flexway import krakend : is not empty: the import never overwrites |
--out contient déjà des fichiers |
Un dossier vide ou absent |
| Un endpoint de la configuration importée manque après l'import | Le contrat ne le déclare pas : il est listé dans coverage.md, jamais inventé |
Le déclarer au contrat, relancer l'import |
| La passerelle refuse de démarrer | Configuration partielle - c'est voulu | Lire le message : il nomme l'opération ou l'amont fautif |
404 sur une route qu'on croit exposée |
Elle n'est pas dans le contrat. « Servi = déclaré » | L'ajouter au contrat, pas à la configuration |
| Un flux arrive d'un coup à la fin | Un intermédiaire bufferise - pas la passerelle | Vérifier l'edge et tout proxy entre le client et elle |
Un flux s'arrête au milieu, journal read budget exhausted |
L'amont s'est tu plus longtemps que server.timeouts.read |
Faire émettre un battement (: keepalive) à l'amont, ou relever read |
504 upstream_timeout |
L'amont n'a pas répondu dans le budget de l'opération | Lire flexway_upstream_duration_seconds ; relever x-flexway-timeout sur cette opération, pas le défaut global |
Flexway-Cache: miss à chaque requête |
L'amont interdit de garder (no-store, no-cache, private, Set-Cookie), répond sans Content-Length, au-delà de server.cache.max_entry_bytes, ou avec un statut autre que 200, 203, 204, 301, 404 |
Lire les en-têtes de la réponse de l'amont ; flexway_cache_lookups_total par opération |
Aucun Flexway-Cache sur une opération cachée |
Contournement : appelant identifié sur un cache public, condition when fausse, méthode autre que GET/HEAD |
result="bypass" dans flexway_cache_lookups_total ; un cache par appelant se déclare scope: subject à l'opération |
Démarrage refusé : response cache declaration(s) refused |
Un cache sur un flux, un WebSocket, gRPC, l'inférence, une écriture, ou public sur une opération authentifiée | Le message nomme l'opération et la porte (x-flexway-cache ou la politique) |
502 response_too_large |
Une opération à politique de corps a reçu une réponse au-delà de server.body.max_bytes |
Relever server.body.max_bytes (rechargeable), ou paginer côté amont |
502 upstream_error sur une opération transformée, journal an upstream answer could not be transformed |
L'amont a répondu autre chose que du JSON lisible, compressé malgré Accept-Encoding: identity, ou sans un champ projeté avec on_missing: fail |
Le journal nomme la cause et le champ ; corriger l'amont, ou passer à on_missing: omit |
Démarrage refusé : body policy attachment(s) refused |
Une politique de corps atteint un flux, une réponse non JSON, gRPC ou l'inférence ; ou elle retire un champ requis, garde un champ non déclaré, masque avec un type refusé | Le message nomme l'opération et la politique ; l'attacher plus étroitement ou !nom |
flexway_policy_nonconformant_total monte |
Un corps servi ne tient plus le schéma de son statut (échantillon) - l'amont a changé, ou le statut n'est pas déclaré | Le journal departs from its schema donne le champ et le mot-clé ; la réponse est servie quand même |
Refus au chargement « body is read by the conditions of transform-body, redact and map-status only » |
Une autre politique lit body dans sa condition ou un gabarit |
Lire le corps dans une politique de corps, ou s'en passer |
403 (ou autre 4xx) avec le code d'une politique external-call |
Le service externe a refusé : deny.when vaut vrai sur sa réponse |
Attendu ; le motif est chez le service, jamais recopié dans le problème |
504, 503 ou 502 sans que l'amont soit appelé, journal external call failed_closed |
Le service externe n'a pas répondu à temps, est injoignable, a son circuit ouvert ou a rendu une réponse illisible pour deny.when / set_headers - et la politique est fail-closed |
Le journal donne la politique et la cause ; flexway_policy_external_calls_total{outcome="failed_closed"} ; réparer le service, ou fail-open si la décision peut manquer |
Un en-tête attendu d'un external-call manque, journal external call en WARN |
fail-open a absorbé une panne du service |
flexway_policy_external_calls_total{outcome="failed_open"} ; le journal donne la cause |
504 upstream_timeout « budget was spent before its upstream could be called » |
Un external-call de la phase requête a consommé tout le budget de l'opération |
Raccourcir son timeout, ou relever x-flexway-timeout de l'opération |
Démarrage refusé : external-call attachment(s) refused |
timeout au-delà du budget d'une opération, send.body sur un flux, phase response sur un WebSocket, gRPC ou l'inférence |
Le message nomme l'opération et la politique |
503 upstream_unavailable avec Retry-After |
L'amont refuse les connexions : il est arrêté | Vérifier l'amont ; la passerelle a déjà repris une fois si la méthode le permettait |
502 upstream_error |
L'amont a répondu quelque chose qui n'est pas du HTTP exploitable | Chercher côté amont, pas côté passerelle |
401 sur des jetons valides juste après un démarrage |
Les clés de l'émetteur ne sont pas encore chargées | Le journal dit issuer not reachable yet ; vérifier que la passerelle joint l'URL de découverte |
401 invalid_token sur un jeton qui marche ailleurs |
Audience, émetteur ou algorithme : le motif est au journal (token refused), jamais dans la réponse |
Comparer aud et iss du jeton à auth.issuers |
Inférence : 401 tenant_required malgré les en-têtes de l'ingress |
Le pair n'est pas dans server.trusted_proxies : ses en-têtes ne sont pas crus |
Déclarer la plage de l'ingress, et elle seule |
Inférence : 429 budget_exceeded |
Le budget du tenant (jour ou mois) est épuisé - une requête pré-réserve max_tokens, 512 sans lui |
Relever ai.budget ou une surcharge du tenant ; le compteur est dans ai.budget.store |
Inférence : 429 rate_limited pour tous les tenants derrière l'ingress |
Une politique de débit trop serrée sur /v1/* |
Une politique propre sur routes: { "/v1/**": [...] } (exemple 12.17) |
Inférence : Flexway-Budget: soft sur toutes les réponses d'un tenant |
Il a dépassé soft.at de son budget en euros du mois |
Attendu ; relever monthly_eur du tenant (surcharge) ou attendre le mois suivant |
Inférence : Flexway-Degraded: backend-fallback |
Le backend choisi est en panne (connexion, 5xx, délai, circuit ouvert) ; le suivant de fallback a servi |
La métrique flexway.inference.fallback donne le backend quitté et la cause |
| Refus au chargement « price is required: the budget is counted in euros » | Un budget en euros existe et un backend n'a pas de prix | Déclarer price sur chaque backend, 0 pour un interne |
Inférence : 403 sensitive_no_internal_backend |
Requête sensible et aucun ai.sensitive_backend interne |
Déclarer un backend interne ; jamais un externe pour ce flux |
Inférence : 400 capability_unavailable |
Le backend que la politique choisit ne sert pas ce que la requête demande - une image sans vision, des embeddings sans embeddings |
Déclarer la capacité sur le backend (capabilities) ou une dérogation match.capability vers un backend qui la sert |
Avertissement « is a relay metered locally » (flexway validate, démarrage) |
Un kind: relay sans metering: upstream : la Flexway amont compte aussi le coût |
Déclarer metering: upstream ; le plafond local reste appliqué |
Inférence : 502 backend_unavailable |
Backend injoignable, ou sa clé introuvable (${VAR} non posée, fichier absent) |
Le journal (inference backend failed) donne la cause ; la clé est relue à chaque appel |
Démarrage refusé : FLEXWAY_CONFIG is not set |
Aucun flexway.yaml désigné ; l'ancien config/policy.yaml n'est plus lu depuis 0.9.0 |
Pointer FLEXWAY_CONFIG vers un flexway.yaml ; convertir l'ancien fichier (Servir l'inférence) |
helm install : Flexway serves what a contract declares, and none was given |
Ni openapi ni config dans les valeurs |
La commande affichée (Passer en Kubernetes) |
403 insufficient_scope |
Le jeton ne porte pas la portée ou le rôle que le contrat exige | Lire scope= dans WWW-Authenticate ; pour un rôle, vérifier roles_claim |
A2A : 200 avec l'erreur JSON-RPC -32009 (VERSION_NOT_SUPPORTED) |
Pas d'en-tête A2A-Version - c'est la 0.3 pour la spécification |
Envoyer A2A-Version: 1.0 (ou ?A2A-Version=1.0) ; seule la 1.0 est servie |
A2A : 403 PUSH_TARGET_NOT_ALLOWED |
Cible push hors agents.a2a.push.allowed_hosts, en http, écrite en adresse, avec user@, ou aucune liste déclarée |
Déclarer le nom de l'hôte du webhook ; flexway_a2a_push_refused_total donne la raison |
A2A : 403 TENANT_MISMATCH |
Le tenant de la requête (corps, préfixe de chemin, paramètre) n'est pas celui de l'identité, ou l'identité n'en porte aucun |
Envoyer le tenant de l'identité, ou rien : la passerelle transmet le sien |
A2A : 429 TOO_MANY_STREAMS |
L'appelant tient déjà max_streams flux ouverts vers cet agent sur cette réplique |
Fermer un flux, ou relever max_streams de l'agent |
| Un flux A2A se termine à heure fixe | max_task_duration atteint (flexway_a2a_calls_total{outcome="duration_exceeded"}) |
Le relever sur l'agent, ou suivre la tâche par GetTask |
| Démarrage refusé sur une carte A2A | Liaison gRPC, schéma sans mécanisme, amont sans token_exchange ou pas protocol: a2a, signatures déclarées, signer (prévu en 1.9.0), verify: none absent, flux sans max_streams ou max_task_duration |
Le message nomme la carte, l'agent et la règle |
401 WWW-Authenticate: ApiKey sur une clé qu'on croit bonne |
Clé inconnue (retirée, mal copiée), expirée, ou sans le préfixe fwk_ |
Le journal (API key refused) donne la cause et, pour une clé expirée, son id ; recréer la clé |
401 « does not accept API keys » |
L'opération n'a pas d'alternative apiKey dans le contrat |
Ajouter { key: [] } à son security, si c'est voulu |
403 not bound to this operation (clé ou certificat) |
L'entrée ne nomme pas cette opération | Ajouter l'operationId à operations de l'entrée ; rechargement |
| Refus au chargement « no contract declares » sur une clé ou un certificat | L'entrée nomme une opération qui n'existe pas (renommée ?) | Corriger l'operationId |
401 « requires a client certificate » malgré un certificat |
Certificat non déclaré, ou x-forwarded-client-cert venu d'un pair hors server.trusted_proxies |
Vérifier l'URI SAN ou le sujet face à auth.mtls.clients ; déclarer la plage de l'edge |
Poignée de main TLS refusée sur :8443 |
Certificat client d'une autre autorité que client_ca_file |
Ajouter l'autorité au fichier : il est relu sans redémarrage |
| Refus au chargement « google.api.http ... not supported at this version » | Variable sur plusieurs segments, joker ou verbe dans une règle REST | Une variable par segment (/v1/shelves/{shelf}/books/{book}), ou la méthode en gRPC seulement |
| Refus au chargement « is a gRPC method and relays to upstream » | La méthode tombe sur un amont HTTP (le default) |
Nommer son amont dans flexway.v1.operation.upstream, déclaré protocol: grpc |
Client gRPC : UNIMPLEMENTED |
La méthode n'est pas dans le jeu servi - jeu périmé | Reconstruire le .binpb : il est relu sans redémarrage |
REST : upstream_status |
L'amont gRPC a répondu une erreur : c'est sa réponse, pas un échec de la passerelle | Lire grpc.status et grpc.details du problème |
400 « Query parameter ... is not declared » |
Fermé par défaut : le contrat ne déclare pas ce paramètre | Le déclarer dans le contrat, pas contourner la passerelle |
429 rate_limited |
Le seau de l'appelant est vide | Lire Retry-After ; relever rps/burst de la classe, ou changer la classe de l'opération |
| Tous les anonymes semblent partager un seau | La passerelle voit l'adresse du bord - ou, derrière un load balancer cloud sans proxy protocol, celle du load balancer - pas celle du client | Déclarer le bord dans server.trusted_proxies ; lire x-forwarded-for dans le journal d'accès du bord, et activer le proxy protocol au load balancer et au bord (Poser des quotas du guide) |
422 invalid_request |
La requête ne respecte pas le schéma du contrat | L'extension errors nomme le champ et la règle |
503 avec Flexway-Degraded: circuit-open |
L'amont échoue ; le coupe-circuit répond à sa place | Réparer l'amont ; un appel d'essai passe après cooldown |
503 avec Flexway-Degraded: upstream-saturated |
max_in_flight atteint sur cet amont |
Relever le plafond, ou trouver pourquoi l'amont ralentit |
503 overloaded avec Flexway-Degraded: shedding, flexway_shed_total{reason="in_flight"} qui monte |
La réplique est à son plafond de requêtes en vol (server.in_flight, dérivé de sa limite mémoire s'il n'est pas écrit) : elle refuse l'excédent au lieu d'être tuée (OOM) |
Ajouter des répliques, ou plus de CPU et de mémoire à chacune ; la limite en vigueur est dans /config (server.in_flight) et au journal de démarrage |
| Un en-tête n'arrive pas à l'amont | Il n'est pas déclaré dans le contrat - il est retiré, pas refusé | Le déclarer comme paramètre in: header |
403 « This origin is not allowed » |
L'origine du navigateur n'est dans aucune politique cors |
L'ajouter à allow_origins |
| Compteurs de quota incohérents entre répliques | Pas de magasin partagé | Déclarer un Redis, Dragonfly ou Valkey |
| Une modification n'est pas prise | La nouvelle version est refusée : l'ancienne sert toujours | Lire last_reload.cause dans /config ; en grappe, vérifier que le ConfigMap n'est pas monté en subPath |
| Rechargement refusé « restart required » | server.shutdown, observability ou server.reload a changé |
Redémarrer : c'est le seul moyen de les appliquer |
404 sur un hôte qui déclare contracts |
Cet hôte sert exactement ses contrats, rien du contrat par défaut | Ajouter le contrat voulu à hosts.<hôte>.contracts |
Agent : 401 malgré un jeton valide |
Le jeton n'est pas émis pour la surface : son audience doit être l'URL de agents.mcp.resources |
Demander le jeton avec l'indicateur de ressource (resource=https://…/mcp) |
Un outil attendu manque dans tools/list |
Le jeton de l'agent n'a pas la portée ou le rôle que l'opération exige - la liste est filtrée | Donner la portée à l'agent, pas élargir l'opération |
Un outil répond 401 depuis l'amont |
L'amont vérifie lui-même un JWT, et le jeton de l'agent ne lui est jamais relayé | Déclarer upstreams.<nom>.token_exchange et le client de l'émetteur (Le jeton que reçoit l'amont, Jeton échangé vers l'amont) |
| Un outil d'un serveur fédéré n'apparaît pas | Serveur injoignable ou pas encore découvert ; outil hors de la liste déclarée ; portée manquante ; nom déjà pris | /config, mcp_federation : état, outils servis, not_served et pourquoi ; le journal dit MCP server not reachable |
| Refus au chargement « is offered twice … rename one » | Un outil fédéré porte le nom d'un outil du contrat ou d'un autre serveur | agents.mcp.servers.<nom>.rename |
Un outil fédéré répond forbidden |
L'appelant n'a pas les scopes (ou tool_scopes) déclarés, ou n'est pas authentifié |
Donner la portée à l'agent |
403 « The chain of agents … is longer than allowed » ou « older than allowed » |
Le jeton porte plus d'agents (act) que max_depth, ou l'humain s'est authentifié il y a plus de max_age - l'opération peut resserrer |
Le journal (delegation chain refused) donne la raison et les agents ; réauthentifier l'humain, ou raccourcir la chaîne |
401 « The delegation was revoked » |
L'émetteur répond que le jeton n'est plus actif (introspection) | Attendu après une révocation ; obtenir un nouveau consentement |
503 « The delegation cannot be verified now » |
L'introspection de l'émetteur est injoignable, ou l'émetteur n'en publie pas | Vérifier introspection_endpoint dans sa découverte et le client ; un jeton direct, lui, passe |
403 sur un amont qui échange, journal widened |
Le fournisseur a accordé une portée que l'appelant n'avait pas | Régler la politique d'échange du fournisseur : un saut ne doit jamais élargir |
Découverte en échec 401 |
Le serveur exige un jeton même pour lister | discovery.issuer, avec un client sur cet émetteur |
403 « The identity provider refused to issue a token for this upstream » |
Le fournisseur refuse l'échange : client non autorisé à échanger, audience inconnue, secret faux | Le journal (token exchange refused) donne le code du fournisseur ; régler le client côté fournisseur |
503 « could not issue a token », avec Retry-After |
Fournisseur injoignable, ou secret client introuvable (${VAR} non posée, fichier absent) |
Le journal (token exchange unavailable) dit lequel ; le secret est relu à chaque échange, sans rechargement |
| Refus « operationId … is declared by both » | Deux contrats nomment deux opérations pareil | En renommer une : l'identifiant indexe politiques, métriques et /config |
| Refus au démarrage « is protected - the gateway alone writes it » | Une politique écrit un en-tête que seule la passerelle pose (identité, trace, tenant, X-Forwarded-*) |
Choisir un autre nom d'en-tête |
L'amont reçoit X-User-Roles: [seller admin] |
Passerelle antérieure à 1.6.0 : une liste rendue à la manière de Go |
Mettre à jour : une liste arrive seller,admin ; {{ auth.claims.roles.join(' ') }} pour un autre séparateur (Enrichir un flux) |
Refus au démarrage undeclared reference to 'x' ou found no matching overload dans une valeur |
Une fonction que le langage n'a pas, ou join sur un séparateur qui n'est pas du texte |
Seules les fonctions de CEL et join existent ; corriger l'expression |
| Refus « segment … has two parameters side by side » ou « a '{' that is never closed » | Un segment de chemin que le routeur ne sait pas lire | Mettre un texte entre les deux paramètres, fermer l'accolade |
| Refus « segments … rank the same and can match the same text » | Deux segments mixtes de même rang lisent le même texte (a{x} et {x}a) |
Rendre l'un plus littéral, ou renommer un chemin |
404 sur /reports/2026.pdf alors que /reports/{year}.pdf est au contrat |
Passerelle antérieure à 1.6.0 : le segment mixte était pris pour un littéral |
Mettre à jour (Servir sa première API) |
Première requête lente après un démarrage, sur un grand contrat en validation: params |
Passerelle antérieure à 1.6.0 : le validateur des paramètres était construit à sa première requête, après /readyz |
Mettre à jour : les deux validateurs sont compilés avant /readyz ; la première requête vers un amont ouvre encore sa connexion |
| Refus au démarrage « on_missing is required » | Une valeur est une expression {{ … }} sans dire quoi faire si elle manque |
Ajouter on_missing: skip, empty ou fail |
| Refus au démarrage « two … policies at the same level » | Deux cors (ou rate-limit, rewrite-path) au même niveau |
En garder une, ou déplacer l'autre vers un niveau plus précis |
500 internal_error, journal a policy value is missing |
on_missing: fail et la valeur manque pour cet appelant |
Ajouter une condition when, ou passer à skip |
| Une politique n'agit pas | Le journal de démarrage dit NOT applied : son type vient d'une version ultérieure |
Lire effective_chains dans /config |
Sur les langues. Cette documentation existe en français et en anglais, page pour page ; les deux sont tenus à jour ensemble. Les spécifications, les ADR et la feuille de route sont en français - c'est le dossier d'ingénierie. Le produit lui-même, ses clés de configuration, ses codes d'erreur et ses journaux sont en anglais.