Observer
GuideExploitationDepuis la version 0.2.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.
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 traficobservability:
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 flexwayVé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.