Exemples : TLS, remplacement, quotas partagés, gRPC, cache, corps, appel externe
ExempleExploitationDepuis la version 1.0.0
Sur cette page
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.
mTLS vers un amont
Hors maillage seulement : dans un maillage, le sidecar le fait déjà. Les fichiers doivent exister au chargement - c'est pourquoi ce cas n'a pas de fichier d'exemple validé en CI.
upstreams:
payments:
url: https://payments.partner.example:8443 # TLS exige https://
tls:
ca_file: /etc/flexway/tls/partner-ca.pem
cert_file: /etc/flexway/tls/client.crt # relu à sa rotation, sans redémarrage
key_file: /etc/flexway/tls/client.key
server_name: payments.partner.exampleÉcouteur TLS de la passerelle
Sans edge qui termine le TLS : la passerelle vérifie elle-même le certificat client, sur
FLEXWAY_TLS_ADDR (:8443 par défaut). Les clients se déclarent comme en 12.15 ; les fichiers
doivent exister au chargement, d'où l'absence de fichier d'exemple validé en CI.
server:
tls:
cert_file: /etc/flexway/tls/tls.crt # relu à sa rotation, sans redémarrage
key_file: /etc/flexway/tls/tls.key
client_ca_file: /etc/flexway/tls/clients-ca.pem # autorité des certificats clients, relue aussi
auth:
mtls:
clients:
- { id: bank-settler, uri: "spiffe://bank.example/settler", tenant: bank, operations: [settle] }Remplacer une passerelle en place
Venir d'une passerelle existante : l'import de sa configuration écrit le point de départ - amonts, émetteurs, débits, coupe-circuits, extensions du contrat - et le rapport de ce qui ne passe pas (Reprendre une passerelle existante). Ce qui suit est ce qu'on y ajoute le premier jour.
# What a team replacing its gateway usually needs on day one (1.0.0): anonymous
# users behind a BFF counted apart, a bucket per operation as most gateways count, errors typed under
# its own portal with field codes in its own words, the body left to the service, tokens of an old
# and a new audience accepted while the audience is renamed.
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://shop.internal:8080
hosts:
api.example.com: { default_upstream: default, validation: params } # the services validate their bodies
server:
trusted_proxies: [local] # the mesh proxy: the pod's own addresses (not the loopback)
peer_identity_header: l5d-client-id # the mTLS identity that proxy verified
errors:
type_base: https://docs.example.com/errors/ # type = https://docs.example.com/errors/<code>
field_codes: { minLength: too_short, maxLength: too_long, minimum: out_of_range, maximum: out_of_range }
auth:
issuers:
- name: customers
discovery: https://login.example.com/.well-known/openid-configuration
audiences: [shop-api, api] # the old name and the new, during the rename
policies:
definitions:
per-user:
type: rate-limit
config:
key: header # anonymous: X-Client-Key, if the BFF set it
header: X-Client-Key
trusted: [web-bff.web.serviceaccount.identity.linkerd.cluster.local]
scope: operation # a bucket per operation and user, not one per class
classes:
read: { rps: 20, burst: 40 }
write: { rps: 5, burst: 10 }
chains:
global: [per-user]# extrait de examples/replace-gateway/openapi.yaml
/orders:
post:
operationId: createOrder
x-flexway-validation: full # this one is checked here too, body includedDébit partagé, forfaits, délestage
# Several replicas sharing one rate, a plan per tenant, and load shed when the gateway saturates
# (1.0.0).
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://reports.internal:8080
server:
counters: { store: "${COUNTERS_URL}", sync: 1s } # Redis, Dragonfly or Valkey, shared by the replicas
shedding: { target: 20ms } # p99 scheduling latency above which load is shed
auth:
issuers:
- name: customers
discovery: https://login.example.com/.well-known/openid-configuration
audience: api
tenant_claim: org # what a quota counts by
policies:
definitions:
per-caller:
type: rate-limit
config:
classes:
read: { rps: 50, burst: 100 }
write: { rps: 10, burst: 20 }
plans:
type: quota
config: { limits: { day: 100000, month: 2000000 } }
chains:
global: [per-caller, plans]# extrait de examples/shared-quotas/openapi.yaml
/reports:
post:
operationId: buildReport
x-flexway-priority: sheddable # shed first when the gateway saturates
/payments/{id}:
get:
operationId: getPayment
x-flexway-priority: critical # never shedAmont gRPC en gRPC, gRPC-Web et REST
# A gRPC upstream served three ways from its own .proto (1.1.0): to gRPC clients (h2c on the
# traffic listener), to browsers in gRPC-Web, and in REST/JSON at the routes its google.api.http
# annotations declare. One operation per method - shop.v1.Orders/GetOrder - whatever the protocol:
# the same admission, rate limits, quotas and audit.
#
# orders.binpb is the descriptor set built from orders.proto (`buf build -o orders.binpb`): Flexway
# reads it, never the .proto, and needs no generated code. Rebuild it when the .proto changes.
version: 1
contracts:
protobuf: [./orders.binpb]
upstreams:
# Named by the methods' option (upstream: "orders"), as x-flexway-upstream names one.
orders:
# Inside a mesh the sidecar does mTLS, so the gateway speaks HTTP/2 in clear (h2c).
url: http://orders.internal:9000
protocol: grpc
auth:
issuers:
- { name: staff, discovery: "https://id.example.com/realms/staff/.well-known/openid-configuration", audience: orders-api }// extrait de examples/grpc-upstream/orders.proto
service Orders {
rpc GetOrder(GetOrderRequest) returns (Order) {
option (google.api.http) = { get: "/v1/shop/orders/{id}" }; // the REST route
option (flexway.v1.operation) = { // what x-flexway-* says in OpenAPI
upstream: "orders"
security: [ { scopes: ["orders:read"] } ]
timeout: "2s"
};
}
rpc CreateOrder(CreateOrderRequest) returns (Order) {
option (google.api.http) = { post: "/v1/shop/orders" body: "order" };
option (flexway.v1.operation) = { upstream: "orders" security: [ { anonymous: true } ] };
}
rpc WatchOrder(GetOrderRequest) returns (stream Order) { // an event stream in REST
option (google.api.http) = { get: "/v1/shop/orders/{id}/watch" };
option (flexway.v1.operation) = { upstream: "orders" security: [ { anonymous: true } ] };
}
}Cache de réponses
# Responses kept by the gateway (1.2.0): opt-in, per operation in the contract
# (x-flexway-cache) or by a cache policy over routes - the operation's own wins. Public by default: an
# identified caller is never served from, nor stored into, a public entry; per tenant and subject only
# when the operation declares it. The upstream's Cache-Control only shortens the ttl; no-store,
# private, Set-Cookie and streams are never kept.
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://catalog.internal:8080
server:
cache:
max_bytes: 64MiB # per replica; past it, the least recently used goes
max_entry_bytes: 512KiB # a larger response is relayed, never kept
auth:
issuers:
- name: customers
discovery: https://login.example.com/.well-known/openid-configuration
audience: api
tenant_claim: org # a cache per subject keys by tenant and subject
policies:
definitions:
catalog-cache:
type: cache
config: { ttl: 5m } # public, always: a policy never caches per subject
chains:
routes:
"/products/**": [catalog-cache] # the reads under it; the writes are left alone# extrait de examples/response-cache/openapi.yaml
/products:
get:
operationId: listProducts
x-flexway-cache: { ttl: 60s } # its own, over the route's policy
/me/cart:
get:
operationId: getCart
security: [ { customers: [] } ]
x-flexway-cache: { ttl: 30s, scope: subject } # each subject of each tenant, its own entryCorps et statut de réponse
# Response bodies shaped by the gateway (1.2.0): a closed list of fields served, personal
# data masked, a legacy upstream's statuses turned into semantic ones in RFC 9457. Only the operations
# that declare one of these policies are read whole - every other keeps the direct relay. A stream, a
# response that is not JSON, or a projection that drops a field the contract requires refuses the
# start.
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://orders.internal:8080
server:
body:
max_bytes: 2MiB # past it: 502 response_too_large, never a cut body
validate_sample: 0.05 # one served body in twenty checked against its schema
policies:
definitions:
public-order:
type: transform-body
config: { project: [id, status, total, items, customer], on_missing: fail }
no-pii:
type: redact
config: { fields: [body.customer.email, body.customer.phone], with: "***" }
legacy-errors:
type: map-status
config:
- { from: 200, when: "has(body.error)", to: 422, code: legacy_rejected }
- { from: 520, to: 502 } # a CDN's private status: the upstream failing
chains:
routes:
"/orders/**": [legacy-errors] # every order read; /health keeps the direct relay# extrait de examples/body-transforms/openapi.yaml
/orders/{id}:
get:
operationId: getOrder
x-flexway-policies: [public-order, no-pii] # what is not listed is not served; the PII masked
/health:
get:
operationId: health # no body policy: relayed without a bufferAppel externe
# A decision that belongs to the client's own system (1.2.0): a risk service asked
# before an order reaches its upstream, a loyalty service asked once a profile is answered. The
# service is an upstream declared like any other - its pool, bulkhead and breaker are its own -,
# never a URL in a policy. The timeout and what a failed call does are declared, never defaulted;
# the call consumes the operation's budget.
version: 1
contracts:
openapi: ./openapi.yaml
auth:
issuers:
- name: customers
discovery: https://id.example.com/.well-known/openid-configuration
audience: api
tenant_claim: org
tenant_header: X-Tenant # sent to the risk service when named, never set by it
upstreams:
default:
url: http://orders.internal:8080
risk:
url: http://risk.internal:8080 # the external service: an upstream like any other
max_in_flight: 200 # its bulkhead
circuit_breaker: { min_requests: 20, failure_ratio: 0.5, cooldown: 10s }
loyalty:
url: http://loyalty.internal:8080
policies:
definitions:
risk-check:
type: external-call
phase: request
when: "request.method == 'POST'"
config:
upstream: risk
path: /score
timeout: 150ms # required, within the operation's budget
on_failure: fail-closed # an authorization: no answer, no order
send: { headers: [X-Tenant], body: true }
deny: { when: "response.status == 403", status: 403, code: risk_refused }
set_headers: { X-Risk-Score: "response.body.score" }
loyalty-tier:
type: external-call
phase: response
config:
upstream: loyalty
path: /tier
timeout: 80ms
on_failure: fail-open # an enrichment: no answer, no header
send: { headers: [X-Tenant] }
set_headers: { X-Loyalty-Tier: "response.body.tier" }# extrait de examples/external-call/openapi.yaml
/orders:
post:
operationId: createOrder
x-flexway-timeout: 2s # the budget the risk call consumes
x-flexway-policies: [risk-check]
/health:
get:
operationId: health # no external call: nothing asked of anyonePas encore construit
Écrit ici pour que la forme cible soit connue. D'ici sa version, une clé nouvelle de
flexway.yaml est refusée au chargement, un type de politique à venir est accepté avec un
avertissement et jamais appliqué, et une extension de contrat à venir est refusée au
chargement avec la version qui l'apporte - le détail, relevé avec flexway validate, et un exemple
complet et commenté par cas d'usage sont en Configurations cibles des prochaines versions. La syntaxe est
celle prévue, pas une promesse de détail.
| Cas | Forme cible | Version |
|---|---|---|
| Appareils et industrie : écouteur MQTT des appareils (X.509, jeton ou clé en mot de passe), topic lié au tenant et au certificat, Sparkplug B, OPC UA PubSub JSON | listeners.mqtt, x-flexway-device, x-flexway-sparkplug, x-flexway-bind: "cert:<champ>" |
1.7.0 |
| Plans et budgets : limites par plan (claim vérifiée ou attribut de clé, plan par défaut), budgets d'inférence par utilisateur et par cas d'usage | plans, issuers[].plan_claim, ai.budget.per_subject, ai.use_cases, ai.aliases, x-flexway-use-case |
1.8.0 |
| Webhooks entrants signés, listes d'adresses : HMAC générique, Standard Webhooks, préréglages de fournisseurs ; listes blanche et noire, PROXY protocol | x-flexway-webhook, webhooks.secrets, server.ip_deny, server.proxy_protocol, politique ip-filter |
1.8.0 |
| Compression négociée, coalescence des lectures en vol | Politique compress (gzip, brotli, zstd), x-flexway-coalesce |
1.8.0 |
| Cartes A2A amont vérifiées, carte publiée re-signée | agents.a2a.signers, agents.a2a.signing, agents.a2a.cards, x-flexway-a2a.signer |
1.9.0 |
| Délégation d'agent à agent, registre | Bornes resserrées par agent sur deux sauts A2A, verdicts dans /config |
1.9.0 |
Licence : sans licence valide, Flexway refuse de démarrer ; clé Développeur gratuite hors production (90 jours, en-tête Flexway-License: developer, 100 requêtes par seconde et par réplique), Business et Enterprise avec 30 jours de grâce ; état dans le journal, la métrique flexway.license.state, /config et la console ; jamais d'arrêt d'un processus en service |
Fichier de licence monté comme un secret, son chemin dans flexway.yaml (nom de clé encore à fixer) ; flexway validate reste utilisable sans licence |
À l'ouverture publique, à dater |