flexway
FRENParler à un expert

Observer

GuideExploitationDepuis la version 0.2.0

Markdown

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.

Objectif : voir le trafic par opération, pas par URL brute.

Commande

curl -s localhost:9090/metrics | grep '^flexway_'     # le port d'administration, jamais celui du trafic
observability:
  otlp_endpoint: tempo.observ:4317    # facultatif, hôte:port ou URL (http://, https://) ; sinon OTEL_EXPORTER_OTLP_ENDPOINT, sinon rien n'est poussé

Traces et métriques partent vers ce point. Un point qui ne prend que des traces (Tempo, Jaeger), ou un collecteur qui scrute déjà /metrics, appelle OTEL_METRICS_EXPORTER=none : les métriques restent alors sur /metrics, poussées nulle part.

Chaque réplica se nomme dans la ressource qu'il pousse : service.instance.id vaut POD_NAME (à poser par l'API descendante, metadata.name) ou, à défaut, le nom d'hôte, qui est le nom du pod dans Kubernetes. Un backend qui traduit l'OTLP en séries Prometheus en fait le label instance ; sans lui, deux réplicas écriraient la même série et les compteurs sauteraient de l'un à l'autre. OTEL_RESOURCE_ATTRIBUTES est lu en dernier et peut ajouter des attributs ou remplacer celui-ci.

Métrique Pour répondre à
flexway_requests_total Combien, par operation, method, status_class, upstream, tenant (borné)
flexway_request_duration_seconds Combien de temps, hors flux - la durée d'un SSE noierait la latence
flexway_upstream_duration_seconds Combien de ce temps est celui de l'amont
flexway_requests_in_flight Ce qui est en cours, par amont - le signal de mise à l'échelle
flexway_retries_total Combien de reprises, par amont et motif (connection, status_502, status_503)
flexway_inference_tokens_total Combien de tokens chaque tenant consomme, par backend et sens (input, output) - ce que rapporte le backend ; par agent depuis 1.2.0 : un agent qu'un override ai déclare, _other pour tout autre, none sans agent - jamais un sujet ; par capability et metered_by depuis 1.5.0 - une consommation n'additionne que metered_by!="upstream" (une série d'avant 1.5.0 n'a pas l'étiquette)
flexway_inference_calls_total Combien d'appels d'inférence servis - complétions, embeddings, méthodes gRPC mesurées -, par tenant, agent, client, backend, capability et metered_by
flexway_inference_cost_EUR_total, flexway_inference_fallback_total Combien d'euros, par tenant, agent et backend qui a servi ; combien d'appels passés à un backend de repli, par from, to, cause et, depuis 1.2.0, tenant
flexway_quota_requests_total Combien de requêtes chaque politique quota a comptées pour chaque tenant - le consommé de son quota, par réplique ; flexway_quota_exceeded_total compte les refus, sans tenant
flexway_build_info Combien de répliques, et quelle version chacune sert (version, commit) - pendant un déploiement progressif
flexway_streams_open Combien de flux sont ouverts, par operation et protocol (sse, websocket, grpc, http) - jamais dans la latence
flexway_server_in_flight, flexway_server_in_flight_limit, flexway_server_shedding La réplique face à son plafond en vol (déclaré ou dérivé de sa mémoire), et si elle se protège (1) - délesteur ou limite adaptative sous son plafond
flexway_config_last_reload_successful 1 si le dernier rechargement de la réplique a été appliqué (ou aucun depuis son démarrage), 0 s'il a été refusé : la console la dit « À surveiller » tant qu'elle sert une configuration en retard
flexway_server_draining 1 dès que la réplique draine pour s'arrêter, porté par son dernier envoi : la console la dit « Retirée », pas muette
flexway_circuit_state, flexway_circuit_retry_at_seconds, flexway_circuit_transitions_total L'état du coupe-circuit de chaque amont, lu à la collecte (plus jamais figé sans trafic), son prochain essai, ses transitions (from, to) - un cycle plus court qu'une collecte compte quand même
flexway_upstream_connections, flexway_upstream_errors_total Les connexions ouvertes vers chaque amont ; les réponses que la passerelle a écrites pour lui, par code (upstream_timeout, upstream_unavailable, upstream_error)

Journal d'évènements - GET /events, sur le port d'administration : les derniers rechargements (avec la cause d'un refus), transitions de coupe-circuit et débuts ou fins de délestage de cette réplique, le plus récent d'abord ; 128 de chaque sorte, en mémoire, perdus au redémarrage. C'est ce que la vue d'ensemble de la console lit - jamais les journaux.

curl -s localhost:9090/events | jq '.events[:5]'
curl -s localhost:8428/api/v1/query --data-urlencode 'query=sum(flexway_requests_total)'
# tableau de bord : http://localhost:3000/d/flexway-gateway ; traces : Explore > Tempo, service flexway

Vérif : une trace traverse edge -> passerelle -> amont sans rupture de contexte, même sans collecteur côté passerelle. Les métriques portent l'opération, jamais le chemin concret : un _unmatched qui grimpe est du trafic vers des routes non déclarées. Aucun log ne contient de donnée personnelle en clair.

Lire un arrêt. Au SIGTERM, readyz passe au rouge d'abord - le temps que l'orchestrateur retire la réplique -, puis ce qui est en cours se termine, flux compris, dans la limite de server.shutdown.drain. Le journal le dit en une ligne :

{"level":"INFO","msg":"shutting down: readiness withdrawn, draining in-flight requests","readiness_delay":5000000000,"drain":20000000000}

Au-delà de drain, le processus coupe ce qui reste, le dit (the drain ran out: the requests still in flight are cut) et sort proprement, après sa dernière poussée OTLP : un terminationGracePeriodSeconds Kubernetes - ou un stop_grace_period Docker Compose, 10 s par défaut - doit donc dépasser drain de quelques secondes. L'arrêt est annoncé (flexway_server_draining = 1) par une poussée immédiate au SIGTERM : même un processus tué avant la fin de son drain est dit « Retirée » par la console.

Métriques et propagation implémentées depuis 0.2.0 ; le tenant est une dimension depuis 0.3.0.

Journal d'accès, échantillonnage (1.0.0) - observability.access_log: true écrit une ligne access par requête, avec le gabarit de chemin (jamais le chemin reçu). observability.sampling: { max_per_second: 50 } plafonne les traces racines par réplique, sans couper une trace venue d'amont.

Niveau des journaux (1.1.x) - observability.log_level: debug montre ce que fait chaque requête (opération, gabarit, amont, statut, durée), sans redémarrer : le niveau suit le rechargement, et une ligne log level changed le dit. Revenir à info après le diagnostic. Chaque ligne porte trace_id quand la requête a une trace ; un arrêt au démarrage est une ligne FATAL.

Simuler (1.0.0) - « que ferait cette configuration ? » sans la déployer, sur le port d'administration ; rien n'est appelé, débité ni compté :

curl -s localhost:9090/simulate -d '{"method":"GET","host":"api.example.com","path":"/orders?status=open",
  "identity":{"issuer":"customers","subject":"alice","tenant":"acme","scopes":["orders:read"]}}'

La réponse donne le verdict (served, ou refused et son code), l'opération, la chaîne et la requête que l'amont recevrait.

Auditer les décisions (1.0.0) - audit: { decisions: refusals } (ou all) écrit une ligne authz_decision par décision sur une opération déclarée, sur la sortie standard mais marquée log.type: audit pour que votre pipeline la range à part. Elle nomme l'opération et son gabarit de chemin (jamais le chemin reçu), le statut et le code de refus, le mécanisme, l'émetteur, le tenant, les agents, et le sujet haché. Chaque ligne porte seq, prev et hash : une ligne retirée ou modifiée se voit. Avec audit: { key: "${AUDIT_KEY}" }, ces empreintes sont des HMAC - sans la clé, qui peut écrire tout le flux peut aussi le réécrire.

Jetons opaques (1.0.0) - un fournisseur qui émet des références plutôt que des JWT se déclare avec opaque_tokens (un émetteur au plus, client exigé) : chaque jeton est introspecté (RFC 7662) et soumis aux mêmes règles qu'un JWT ; la réponse est gardée cache (30 s par défaut), jamais au-delà de son exp - le délai avant qu'une révocation se voie.

Étapes suivantes