Docs · Airframe
Airframe quickstart 3: a shared message broker
Part 2 ended with a stale gate board: move a flight in flight-api and the board shows the old gate for up to
three minutes. This part fixes it. You provision skyport-broker (RabbitMQ) once, then attach flight-api as
a publisher and boarding-api as a consumer. Every gate change reaches the board at once.
How the pieces fit
- One broker, in its own namespace. It is shared, so it can’t live in any app’s environment. It lives in an InfraService: a deploy repo with no source code. Its dev environment is
infra-skyport-broker-dev. - One broker user per app, created by an
attachcomponent in the app’s environment. Its permissions come from a few names:flight-apimay publish to theflights.eventsexchange;boarding-apimay read from it into queues namedboarding.*. The broker refuses anything else. - One vhost per domain (
flights). Apps that exchange messages share it.
1. Create the InfraService
Tower → Create → InfraService
| Field | Value |
|---|---|
| Name | skyport-broker |
| Namespace | infra-skyport-broker-cicd |
| Dev cluster | <dev-cluster> |
| Description | Skyport shared message broker (RabbitMQ) |
| Visibility | private |
Merge the PR.
Check
kubectl --context <dev-cluster> get infraservice skyport-broker -n infra-skyport-broker-cicdshowsSYNCED TrueandREADY True.
It creates one repo, gitops-infra-skyport-broker, with a README and a stub cicd.yaml. There is nothing to
build, so there is no pipeline.
2. Declare the broker
In gitops-infra-skyport-broker, create this file and push it to main:
appType: infra
envName: dev
rollout: { enabled: false } # no workload of its own: the broker is the component
components:
- type: rabbitmq
name: skyport-broker # also the Service: skyport-broker.infra-skyport-broker-dev.svc
spec:
mode: broker
size: small
instances: 1 # 3 for a replicated cluster
storageSize: 1Gi
vhosts: [flights]
# Only these namespaces may attach and reach the AMQP port.
allowedNamespaces: [app-flight-api-dev, app-boarding-api-dev, app-baggage-api-dev]
allowedNamespaces is the trust boundary: adding an app to the broker is one line here.
(app-baggage-api-dev is for part 4.)
Check After two or three minutes,
kubectl --context <dev-cluster> get rabbitmqs.catalog.hangar.io -n infra-skyport-broker-devshowsREADY True. Use the full resource name: plainrabbitmqclashes with the operator’s own kind.
3. Attach flight-api as the publisher
The publisher is already in the code you copied in part 2; it turns on with EVENTS_ENABLED. Add to
flight-api’s environment:
components:
# flight-db stays
- type: rabbitmq
name: flight-mq
spec:
mode: attach
brokerRef: { name: skyport-broker, namespace: infra-skyport-broker-dev }
vhost: flights
publish: [flights.events] # may declare and publish to this exchange, nothing else
env:
# the DB_* entries stay
- { name: EVENTS_ENABLED, value: "true" }
- { name: RABBITMQ_HOST, fromComponent: { name: flight-mq, output: host } }
- { name: RABBITMQ_VHOST, fromComponent: { name: flight-mq, output: vhost } }
- { name: RABBITMQ_USER, fromComponent: { name: flight-mq, output: username } }
- { name: RABBITMQ_PASSWORD, fromComponent: { name: flight-mq, output: password } }
flight-api publishes only after the database commit, and a broker outage never fails a flight change. Each
message carries the flight’s new state, with a routing key like flight.AC123.gate_changed.
4. Attach boarding-api as the consumer
The consumer turns on when RABBITMQ_HOST is set:
components:
# cache stays
- type: rabbitmq
name: board-mq
spec:
mode: attach
brokerRef: { name: skyport-broker, namespace: infra-skyport-broker-dev }
vhost: flights
queuePrefix: boarding # may create and read queues named boarding.*
consume: [flights.events] # may bind them to this exchange, not publish to it
env:
# REDIS_* and FLIGHT_API_URL stay
- { name: RABBITMQ_HOST, fromComponent: { name: board-mq, output: host } }
- { name: RABBITMQ_VHOST, fromComponent: { name: board-mq, output: vhost } }
- { name: RABBITMQ_USER, fromComponent: { name: board-mq, output: username } }
- { name: RABBITMQ_PASSWORD, fromComponent: { name: board-mq, output: password } }
boarding-api binds its queue boarding.flight-events to flight.# and drops each changed flight from the
Redis cache. Until flight-api has declared the exchange, the bind is refused and the consumer retries every
five seconds, so start order doesn’t matter.
Push both files.
5. See it work
kubectl --context <dev-cluster> port-forward -n app-flight-api-dev svc/flight-api 8081:8080 &
kubectl --context <dev-cluster> port-forward -n app-boarding-api-dev svc/boarding-api 8080:8080 &
curl -s localhost:8080/api/whoami # "events":"connected"
curl -s localhost:8080/api/boarding/AC123 # note the gate; ask again: "cached":true
curl -s -X PUT -H 'Content-Type: application/json' -d '{"gate":"B2"}' localhost:8081/api/flights/AC123/gate
curl -s localhost:8080/api/boarding/AC123 # B2 at once, "cached":false
eventsReceived in /api/whoami keeps climbing on its own: the simulator moves a flight every 30 seconds.
What each app may do. The broker enforces the names in each attach entry:
| Field | Allows | On |
|---|---|---|
publish: [flights.events] |
declare and publish | that exchange |
consume: [flights.events] |
bind queues, read only | that exchange |
queuePrefix: boarding |
create, bind and read queues | names starting boarding. |
So a leaked boarding-api credential can’t forge a flight event, and one app can’t read another’s queue.
6. Flight: staging on the prod cluster
The broker needs about 1Gi of memory; check the prod cluster’s headroom first.
- ApplicationEnvironment: name
skyport-broker-<prod-cluster>-staging, namespaceinfra-skyport-broker-cicd, appskyport-broker, cluster<prod-cluster>, envstaging, app typeinfra. Merge it. The app type sends the values togitops-infra-skyport-broker. - Broker values: in the PR on
gitops-infra-skyport-broker, give<prod-cluster>/staging/values.yamlthe samerolloutandcomponentsas step 2, withallowedNamespaces: [app-flight-api-staging, app-boarding-api-staging, app-baggage-api-staging]. The broker comes up ininfra-skyport-broker-staging. - App values: add each app’s
attachcomponent andenventries from steps 3 and 4 to its staging values, withbrokerRef: { name: skyport-broker, namespace: infra-skyport-broker-staging }.
No release is needed: the images already contain the code.
Remember
- A shared broker is an InfraService with a
rabbitmqcomponent inmode: broker. Apps usemode: attach. - InfraService XRs are in
infra-<name>-cicdand its environments areinfra-<name>-<env>. allowedNamespacesdecides who may attach.- Permissions come from
publish,consumeandqueuePrefix, never hand-written patterns. - Use
rabbitmqs.catalog.hangar.ioinkubectl. - Deleting the broker’s environment deletes its data.