Déclarer un cluster virtuel Kafka
Ouvrir un cluster virtuel aux clients Kafka : contrat AsyncAPI, écouteur TLS ou repli du maillage, amonts, clients et chart.
GuideExploitationDepuis la version 1.6.0
Sur cette page
Guide d'exploitation : configurer, déployer et observer la passerelle. Ce que fait la fonctionnalité côté produit : guide produit ; l'appeler : guide développeur.
Le contrat AsyncAPI déclare les sujets servis, flexway.yaml l'écouteur et l'amont, les clients pointent sur la passerelle. Ce que voit chaque tenant : Clients Kafka natifs.
Étapes
Déclarer le cluster au contrat
Une opération AsyncAPI marquée x-flexway-kafka-proxy est servie aux clients Kafka du cluster qu'elle nomme ; flexway.yaml déclare l'écouteur listeners.kafka et l'amont. L'exemple complet, ici dans un maillage (un port par courtier) :
# Kafka clients behind the gateway (1.6.0): producers, consumers and admin tools of any
# library reach the virtual cluster "shop" the AsyncAPI contract declares - each tenant inside its own
# names, under the same identities as HTTP. Here the fallback inside a mesh, a port per broker; the TLS
# listener, brokers named by SNI, is in the guide.
version: 1
contracts:
asyncapi: ./asyncapi.yaml
upstreams:
events:
protocol: kafka
brokers: [kafka-0.kafka:9093, kafka-1.kafka:9093, kafka-2.kafka:9093]
tls: { server_name: kafka.internal }
sasl: { mechanism: SCRAM-SHA-512, username: flexway, password: "file:/etc/flexway-secrets/kafka/password" }
timeout: 5s # a connection to a broker, TLS and SASL included
auth:
issuers:
- name: customers
discovery: https://login.example.net/.well-known/openid-configuration
audience: kafka
tenant_claim: org # every topic, group and transaction is the tenant's
api_keys:
store: "file:./api-keys.yaml" # PLAIN: the key's id as the user, the key as the password
listeners:
kafka:
plaintext:
mesh_only: true # clear text only where the mesh encrypts between pods
advertised_host: flexway-kafka.flexway.svc.cluster.local
clusters:
shop: { ports: { from: 19092, to: 19095 }, first_node_id: 0 } # bootstrap 19092, node n at 19093+nasyncapi.yaml
# examples/kafka-proxy/asyncapi.yaml
asyncapi: 3.0.0
info: { title: Shop events, version: "1.0.0" }
channels:
orders:
address: "{tenant}.orders"
parameters:
tenant: { x-flexway-bind: tenant }
messages:
order: { payload: { type: object, required: [id], properties: { id: { type: string } } } }
operations:
produceOrders:
action: send
channel: { $ref: "#/channels/orders" }
x-flexway-upstream: events
x-flexway-kafka-proxy: { cluster: shop, transactions: true }
security:
- $ref: "#/components/securitySchemes/apiKey"
- $ref: "#/components/securitySchemes/ordersWrite"
consumeOrders:
action: receive
channel: { $ref: "#/channels/orders" }
x-flexway-upstream: events
x-flexway-kafka-proxy: { cluster: shop, admin_scope: "orders:admin" }
security:
- $ref: "#/components/securitySchemes/apiKey"
- $ref: "#/components/securitySchemes/ordersRead"
components:
securitySchemes:
apiKey: { type: plain } # SASL PLAIN: an API key bound to the operation
ordersWrite: # SASL OAUTHBEARER: a token carrying the scope
type: oauth2
flows: { clientCredentials: { tokenUrl: "https://login.example.net/token", availableScopes: { "orders:write": Produce orders } } }
scopes: ["orders:write"]
ordersRead:
type: oauth2
flows: { clientCredentials: { tokenUrl: "https://login.example.net/token", availableScopes: { "orders:read": Read orders } } }
scopes: ["orders:read"]L'écouteur TLS, les courtiers nommés par SNI
Un seul port, le nom SNI dit quel courtier est visé -
bootstrap.<cluster>.<sni_domain> pour l'amorçage, broker-<id>.<cluster>.<sni_domain> pour chacun ;
Metadata et FindCoordinator répondent ces noms. Le certificat couvre *.<cluster>.<sni_domain>
pour chaque cluster virtuel (un joker ne couvre qu'une étiquette) ; tous ces noms résolvent vers la
passerelle (un enregistrement DNS joker, ou le Service LoadBalancer du chart).
flexway.yaml
# flexway.yaml - l'écouteur TLS ; le reste comme l'exemple ci-dessus
listeners:
kafka:
address: ":9093"
tls:
cert_file: /etc/flexway-kafka-tls/tls.crt # *.shop.kafka.example.com
key_file: /etc/flexway-kafka-tls/tls.key
client_ca_file: /etc/flexway-kafka-tls/ca.crt # facultatif : les certificats clients (X509)
sni_domain: kafka.example.com
advertised_port: 9093 # le port que voit le client (celui du LoadBalancer)
max_request_bytes: 16MiB # une requête authentifiée ; 64 Kio avant l'authentification
max_response_bytes: 64MiB # une réponse du courtier ; un Fetch est tenu sous la moitié
max_connections: 4096
auth_timeout: 10s
idle_timeout: 10m
auth:
mtls:
clients:
- { id: billing, subject: "CN=billing-app", tenant: acme, operations: [consumeOrders] }Le repli en clair, un port par courtier
Sans TLS, SNI n'existe pas ; chaque cluster
virtuel reçoit une plage - from pour l'amorçage, from + 1 + n pour le nœud first_node_id + n -,
annoncée sous advertised_host. Refusé au démarrage sans mesh_only: true : le clair ne traverse
qu'un maillage qui chiffre (Linkerd, Istio en mTLS strict). La plage couvre chaque identifiant de
nœud du cluster, sinon le client reçoit une adresse injoignable et le journal le dit.
Les amonts
Apache Kafka (apache/kafka, Confluent Platform, MSK, Strimzi) et Redpanda se
déclarent pareil ; la passerelle lit les versions de chaque courtier et n'annonce au client que
l'intersection avec ce qu'elle juge :
upstreams:
events: # Apache Kafka : SCRAM sur TLS
protocol: kafka
brokers: [kafka-0.kafka:9093, kafka-1.kafka:9093, kafka-2.kafka:9093]
tls: { ca_file: /etc/flexway-secrets/kafka/ca.crt, server_name: kafka.internal }
sasl: { mechanism: SCRAM-SHA-512, username: flexway, password: "file:/etc/flexway-secrets/kafka/password" }
events-redpanda: # Redpanda : SASL SCRAM-SHA-256, ou mTLS de la passerelle
protocol: kafka
brokers: [redpanda-0.redpanda:9093]
tls: { ca_file: /etc/flexway-secrets/redpanda/ca.crt, cert_file: /etc/flexway-secrets/redpanda/tls.crt, key_file: /etc/flexway-secrets/redpanda/tls.key }
timeout: 5s # une connexion au courtier, TLS et SASL comprisLes clients
Des propriétés Kafka standard, pointées sur la passerelle :
// franz-go : une clé d'API en SASL PLAIN sur TLS
cl, err := kgo.NewClient(
kgo.SeedBrokers("bootstrap.shop.kafka.example.com:9093"),
kgo.DialTLSConfig(&tls.Config{MinVersion: tls.VersionTLS12}),
kgo.SASL(plain.Auth{User: "acme-orders-producer", Pass: os.Getenv("FLEXWAY_KAFKA_KEY")}.AsMechanism()),
kgo.TransactionalID("tx-orders"), // devient acme.~t.tx-orders chez le courtier
)
// un jeton : kgo.SASL(oauth.Oauth(func(ctx context.Context) (oauth.Auth, error) { return oauth.Auth{Token: tok}, nil }))# librdkafka (confluent-kafka-go, Python, .NET) : un jeton OAUTHBEARER de l'émetteur déclaré
bootstrap.servers=bootstrap.shop.kafka.example.com:9093
security.protocol=SASL_SSL
sasl.mechanisms=OAUTHBEARER
sasl.oauthbearer.method=oidc
sasl.oauthbearer.client.id=billing
sasl.oauthbearer.client.secret=${BILLING_SECRET}
sasl.oauthbearer.token.endpoint.url=https://login.example.net/token
sasl.oauthbearer.scope=orders:read
group.id=billing # devient acme.~g.billing# Client Java (producteur, consommateur, AdminClient, Kafka Connect) : une clé d'API en PLAIN
bootstrap.servers=bootstrap.shop.kafka.example.com:9093
security.protocol=SASL_SSL
sasl.mechanism=PLAIN
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="acme-orders-reader" password="${FLEXWAY_KAFKA_KEY}";
# un certificat client à la place : security.protocol=SSL, ssl.keystore.location=..., ssl.truststore.location=...
# group.protocol=classic (par défaut) : le protocole de groupe de KIP-848 n'est pas serviLe chart : clair dans le maillage seulement
Les ports en clair vont dans un Service à part, <release>-flexway-kafka-mesh, toujours ClusterIP ;
le rendu échoue sans kafka.networkPolicy.from (les pods ou namespaces du maillage).
values.yaml
# rendu : le port TLS sur un LoadBalancer, la plage en clair réservée au maillage
config:
listeners:
kafka:
address: ":9093"
tls: { cert_file: /etc/flexway-kafka-tls/tls.crt, key_file: /etc/flexway-kafka-tls/tls.key }
sni_domain: kafka.example.com
plaintext: { mesh_only: true, advertised_host: orders-flexway-kafka-mesh.apps.svc.cluster.local, clusters: { shop: { ports: { from: 19092, to: 19095 } } } }
kafka:
service: { type: LoadBalancer }
tls: { clusters: [shop], issuerRef: { name: kafka-ca, kind: ClusterIssuer } }
networkPolicy:
from: [ { namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: apps } } } ]values.yaml
# refusé : du clair sans pairs déclarés
kafka:
networkPolicy: { from: [] }
# Error: execution error at (flexway/templates/kafka.yaml): listeners.kafka.plaintext opens ports in
# clear: kafka.networkPolicy.enabled and kafka.networkPolicy.from (the mesh's pods or namespaces) are
# required - clear text is for the mesh onlyLe chart rend aussi le Service <release>-flexway-kafka, sa NetworkPolicy et, avec cert-manager, le certificat à joker de chaque cluster (kafka.tls.clusters).
Vérifier
flexway validateaccepte la configuration ; un refus nomme l'extension ou la clé fautive (Refusé au chargement).Un client liste ses sujets sous leur nom court, et seulement ceux que ses opérations lui ouvrent :
kafka-topics --bootstrap-server bootstrap.shop.kafka.example.com:9093 --command-config client.properties --list # ordersLe courtier, lui, tient
acme.orders, etacme.~g.billingpour le groupebilling(Les noms des groupes et des transactions).Le
/configd'administration publie les clusters virtuels souskafka.Un client refusé se lit dans le journal
kafka authentication refused, avec sa cause et jamais la clé ; les autres cas : Dépannage.
À savoir
Rechargement
L'adresse, TLS, sni_domain, advertised_port, la plage de repli et
max_connections, max_total_in_flight_bytes et max_tenant_in_flight_bytes sont lus au démarrage (listeners.kafka refusé au
rechargement s'ils changent) ; le contrat, les identités, les bornes et les délais s'appliquent à la
requête suivante - une connexion garde son identité tant que son justificatif reste celui de la
configuration en vigueur (Sessions : révocation, durée, certificats), et chaque requête est
jugée sur celle-ci.
ACL du courtier
Le principal de la passerelle n'a besoin que des sujets, groupes et identifiants transactionnels des tenants servis (Une connexion amont par connexion cliente et par courtier), sous les noms de Les noms des groupes et des transactions.