A demo project showing RabbitMQ federation between two nodes, with a Symfony producer app publishing messages to the upstream node and a Symfony consumer app receiving them from the downstream node after federation. Includes a bidirectional response flow where the consumer sends confirmation messages back to the producer.
┌──────────────────┐ ┌──────────────────┐
│ RabbitMQ #1 │ │ RabbitMQ #2 │
│ (upstream) │ │ (downstream) │
│ │ │ │
│ exchange: │ fed │ exchange: │
│ federation.in │◄────────│ federation.in │
│ │ │ │ bind │
│ │ │ ▼ │
│ │ │ queue: │
│ │ │ consumer.queue│
└───────▲──────────┘ └───────▲──────────┘
│ │
│ publish │ consume
│ │
┌───────┴──────────┐ ┌──────┴───────────┐
│ PRODUCER app │ │ CONSUMER app │
│ (Symfony) │ │ (Symfony) │
│ │ │ │
│ app:produce │ │ messenger:consume│
│ "<text>" │ │ → var/log/ │
│ │ │ consumed.log │
└──────────────────┘ └──────────────────┘
REQUEST (rabbit1 → rabbit2) RESPONSE (rabbit2 → rabbit1)
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ RabbitMQ #1 │ │ RabbitMQ #2 │ │ RabbitMQ #1 │
│ (upstream) │ │ (downstream) │ │ (downstream) │
│ │ │ │ │ │
│ exchange: │ fed │ exchange: │ │ exchange: │
│ federation.in │◄────────│ federation.in │ │ federation.out│
│ │ │ │ bind │ │ │ bind │
│ │ │ ▼ │ │ ▼ │
│ │ │ queue: │ │ queue: │
│ │ │ consumer.queue│ │ response.queue│
│ │ │ │ │ │
│ exchange: │ │ exchange: │ fed │ │
│ federation.out│◄────────│ federation.out │◄────────│ │
│ │ bind │ │ │ │ │
│ ▼ │ │ │ │ │
│ queue: │ │ │ │ │
│ response.queue│ │ │ │ │
└───────▲──────────┘ └───────▲──────────┘ └──────────────────┘
│ │ │
│ publish │ consume + confirm │ consume
│ │ │
┌───────┴──────────┐ ┌──────┴───────────┐ ┌──────┴───────────┐
│ PRODUCER app │ │ CONSUMER app │ │ PRODUCER app │
│ (Symfony) │ │ (Symfony) │ │ confirmation │
│ │ │ │ │ sidecar │
│ app:produce │ │ messenger:consume │ messenger:consume│
│ "<text>" │ │ consumer │ confirmation │
│ │ │ → var/log/consumed.log │ → var/log/ │
│ │ │ → dispatch │ confirmed.log │
│ │ │ ConfirmationMessage │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Request message flow:
app:produce "<text>"dispatches aFederationMessagevia Symfony Messenger- Messenger publishes to
federation.inexchange on RabbitMQ #1 - RabbitMQ #2 has a federation link that pulls messages from RabbitMQ #1
- On RabbitMQ #2,
federation.inis bound toconsumer.queue - Consumer's
messenger:consume consumerworker picks up the message, logs it, and dispatches aConfirmationMessage
Response message flow:
ConfirmationMessageis published tofederation.outexchange on RabbitMQ #2- RabbitMQ #1 has a federation link that pulls messages from RabbitMQ #2
- On RabbitMQ #1,
federation.outis bound toresponse.queue - Producer's
messenger:consume confirmationsidecar worker picks up the confirmation and logs it
- Docker & Docker Compose (for local development)
- minikube + kubectl (for Kubernetes)
- Make (optional, for convenience commands)
# Start RabbitMQ nodes + federation setup
make up
# Produce a message (prompts for text)
make produce
# Consume messages (runs until stopped with Ctrl+C)
make consume
# One-shot end-to-end test
make testmake upThis starts:
- rabbit1 — RabbitMQ #1 (upstream) — management UI at http://localhost:15672
- rabbit2 — RabbitMQ #2 (downstream) — management UI at http://localhost:15673
- setup — one-shot container that configures federation (exchange, queue, upstream, policy)
Both management UIs use credentials guest / guest.
docker compose run --rm producer app:produce "your message text here"Or use the Make target:
make producedocker compose run --rm consumer messenger:consume consumer -vvOr:
make consumeMessages are logged to var/log/consumed.log inside the consumer container. View the log:
make consume-logmake testThis produces a timestamped message, consumes it, and prints the log output.
make downmake resetThe project includes Kubernetes manifests for running the federation demo on minikube, managed by ArgoCD in a GitOps workflow. The RabbitMQ infrastructure is managed by the RabbitMQ Cluster Operator and RabbitMQ Messaging Topology Operator.
| Layer | Technology |
|---|---|
| RabbitMQ clusters | RabbitMQ Cluster Operator (RabbitmqCluster CRD) |
| Exchanges, queues, bindings, upstreams, policies | RabbitMQ Messaging Topology Operator (Exchange, Queue, Binding, Federation, Policy CRDs) |
| Git reconciliation | ArgoCD with sync waves |
| App orchestration | Kustomize overlays |
| Wave | Resources |
|---|---|
| 0 | RabbitmqCluster (rabbit1, rabbit2) |
| 1 | Secret (rabbit1-amqp-uri, rabbit2-amqp-uri) |
| 2 | Exchange, Queue |
| 3 | Binding |
| 4 | Federation, Policy |
| 5 | Producer / Consumer Deployments |
# Start minikube
minikube start --cpus=4 --memory=8192
minikube addons enable ingress
# Install operators
kubectl apply -f https://github.com/rabbitmq/cluster-operator/releases/download/v2.20.1/cluster-operator.yml
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
kubectl apply -f https://github.com/rabbitmq/messaging-topology-operator/releases/latest/download/messaging-topology-operator.yaml
# Apply all resources
kubectl apply -k k8s/overlays/minikube
# Verify
kubectl get pods -n distributed-rabbit
kubectl get rabbitmqcluster -n distributed-rabbit# Produce a message
kubectl exec -it deploy/producer -n distributed-rabbit -c producer -- php bin/console app:produce "hello k8s"
# Check consumer logs
kubectl logs -f deploy/consumer -n distributed-rabbit
# Check confirmation logs
kubectl exec -it deploy/producer -n distributed-rabbit -c confirmation-consumer -- cat /app/var/log/confirmed.log# Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Expose ArgoCD UI via Ingress (or use port-forward)
kubectl apply -f k8s/overlays/minikube/argocd-ingress.yaml
# Bootstrap the App of Apps
kubectl apply -f argocd/root-application.yaml
# Retrieve initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -dArgoCD will now auto-sync any changes pushed to the k8s/ manifests in this repository.
The rabbitmq/setup/run.sh script runs inside a container and configures:
- Exchanges —
federation.in(type:direct, durable) on both nodes - Queue —
consumer.queue(durable) on RabbitMQ #2, bound tofederation.in - Upstream —
rabbit1-upstreamon RabbitMQ #2, pointing toamqp://guest:guest@rabbit1 - Policy —
federation-policyon RabbitMQ #2, matching^federation\.in$, applying the upstream
The topology is declared as CRDs and reconciled by the Messaging Topology Operator:
- Exchange CRDs —
federation.inon both clusters,federation.outon both clusters - Queue CRDs —
consumer.queueon rabbit2,response.queueon rabbit1 - Binding CRDs — exchange-to-queue bindings
- Federation CRDs — upstream definitions referencing URI secrets
- Policy CRDs — pattern matching for federation
You can verify federation status via:
# On rabbit1 (response flow)
kubectl exec rabbit1-server-0 -n distributed-rabbit -- rabbitmqctl eval 'rabbit_federation_status:status().'
# On rabbit2 (request flow)
kubectl exec rabbit2-server-0 -n distributed-rabbit -- rabbitmqctl eval 'rabbit_federation_status:status().'The link status should be running.
| Path | Purpose |
|---|---|
docker-compose.yml |
All services (rabbit1, rabbit2, setup, producer, consumer) |
rabbitmq/setup/run.sh |
Automated federation configuration via HTTP API (Docker Compose only) |
rabbitmq/rabbit1/enabled_plugins |
Enables federation plugin on node 1 |
rabbitmq/rabbit2/enabled_plugins |
Enables federation plugin on node 2 |
k8s/base/ |
Base K8s manifests (producer/consumer deployments, configmaps) |
k8s/overlays/minikube/rabbitmq/ |
Operator CRDs (clusters, topology) |
k8s/overlays/minikube/ingress.yaml |
Ingress for management UIs |
argocd/ |
ArgoCD Application manifests for GitOps management |
Makefile |
Convenience commands |
| Path | Purpose |
|---|---|
producer/src/Command/ProduceCommand.php |
CLI command app:produce <text> |
producer/src/Message/FederationMessage.php |
Request message DTO |
producer/src/Message/ConfirmationMessage.php |
Confirmation message DTO |
producer/src/MessageHandler/ConfirmationMessageHandler.php |
Logs confirmation messages |
producer/config/packages/messenger.yaml |
AMQP transports (federation publish, confirmation consume) |
| Path | Purpose |
|---|---|
consumer/src/MessageHandler/FederationMessageHandler.php |
Handles messages, logs them, dispatches confirmation |
consumer/src/Message/FederationMessage.php |
Message DTO (must match producer) |
consumer/src/Message/ConfirmationMessage.php |
Confirmation DTO (must match producer) |
consumer/config/packages/messenger.yaml |
AMQP transports (consumer consume, response publish) |
Federation link not running?
- Check both RabbitMQ nodes are healthy:
curl -u guest:guest http://localhost:15672/api/aliveness-test/%2f - Re-run setup:
docker compose up -d setup
Messages stuck on RabbitMQ #1?
- Check federation link status on the management UI or API
- Ensure the exchange name matches exactly (
federation.in) - Verify the policy is applied:
curl -s -u guest:guest http://localhost:15673/api/policies | python3 -m json.tool
Deserialization errors on consumer?
- Both apps must use the same
FederationMessageclass structure (same properties, same types) - Old non-serialized messages in the queue will cause errors — purge with:
curl -u guest:guest -X DELETE http://localhost:15673/api/queues/%2f/consumer.queue/contents
Confirmation messages not arriving?
- Check both federation links are running (rabbit1-upstream and rabbit2-upstream)
- Verify the
response.queueexists on rabbit1 and is bound tofederation.out - Check the producer sidecar is running:
kubectl logs -f deploy/producer -n distributed-rabbit -c confirmation-consumer
Want to test federation resilience?
- Stop rabbit1:
docker compose stop rabbit1(orkubectl delete pod rabbit1-server-0 -n distributed-rabbit) - Produce messages (they'll queue locally on rabbit1 when it restarts)
- Start rabbit1:
docker compose start rabbit1(or wait for the operator to recreate the pod) - Federated messages should flow to rabbit2 automatically