KubeMQ
ConnectorsRabbitMQ (AMQP 0-9-1)

RabbitMQ (AMQP 0-9-1)

Point a RabbitMQ app at KubeMQ by changing only the connection string — AMQP 0-9-1 over KubeMQ Queues, where every queue is a Queue channel.

Point a RabbitMQ app at KubeMQ by changing only the connection string. The RabbitMQ (AMQP 0-9-1) connector is a built-in, wire-protocol bridge inside kubemq-server that speaks the RabbitMQ wire dialect natively — any standard AMQP 0-9-1 client (amqp091-go, pika, amqp-client, amqplib, RabbitMQ.Client, bunny, lapin) talks to KubeMQ's Queues with no KubeMQ SDK, no library swap, and no code rewrite.

What is the RabbitMQ connector

AMQP 0-9-1 is the wire protocol that RabbitMQ popularized: a client opens a connection, multiplexes channels over it, declares queues and exchanges, and publishes messages routed by exchange type and routing key. The KubeMQ connector accepts every one of these operations from a stock client and bridges them onto KubeMQ — it is a gateway, not a client library, so your application only needs its existing AMQP 0-9-1 client.

Mental model — everything is a Queue. Every AMQP queue maps to exactly one KubeMQ Queue channel named amqp.{vhost}.{queue}. Exchanges and bindings are virtual, connector-side routing metadata resolved at publish time — not data stores. AMQP only ever touches the KubeMQ Queue primitive, so the connector's "patterns" mirror AMQP routing concepts (work queues, pub/sub, routing, topics, RPC), not KubeMQ's five messaging patterns.

Key capabilities:

  • Drop-in connection-string migration — keep your RabbitMQ client and code; change only the broker host in the URL.
  • Everything is a Queue — every AMQP queue is a durable KubeMQ Queue channel amqp.{vhost}.{queue}; exchanges (default, direct, fanout, topic, headers) route to those queues virtually at publish time.
  • Native RPC — request/reply uses RabbitMQ's amq.rabbitmq.reply-to (direct reply-to); there is no gRPC responder.
  • Cross-protocol interop — a message published over AMQP to amqp.default.orders is consumable by a gRPC or REST KubeMQ client on the same channel, and vice-versa.

On by default. A stock kubemq-server binds port 5672 out of the box (5671 as well once TLS is configured) — no enable variable is needed. To turn the connector off, set CONNECTORS_AMQP_ENABLE=false (Docker) or spec.amqp.enabled: false (Kubernetes). If 5672 is already taken on the host (a real RabbitMQ running locally), the server logs an error and keeps running without the connector, while the dashboard still lists it as enabled — stop the other broker or move the port with CONNECTORS_AMQP_PORT. See Getting started.

How it works

An AMQP client publishes to an exchange with a routing key. The connector resolves the exchange routing (default / direct / fanout / topic / headers) to a set of target queues at publish time, then writes each message to that queue's KubeMQ Queue channel through the message broker. A consumer on the same queue — over AMQP or any other KubeMQ transport — receives it.

A publish to queue orders on vhost / resolves to the KubeMQ Queue channel amqp.default.orders; any consumer on that channel — AMQP or gRPC/REST — receives the message.

Ports & protocol surface

PortTransportProtocolNotes
5672Plain TCP (SASL PLAIN / AMQPLAIN)AMQP 0-9-1 (RabbitMQ wire dialect)Default plain listener. Shared with the AMQP 1.0 connector via the internal amqpmux.
5671TLS / AMQPS over TCPAMQP 0-9-1Binds only when the server-global Security block is configured. Shared TLS listener with AMQP 1.0.

A single amqpmux listener accepts every connection on 5672/5671, reads the 8-byte AMQP protocol header, and routes it to the matching dialect engine — so AMQP 0-9-1 and AMQP 1.0 coexist on the same ports. SASL offers PLAIN and AMQPLAIN always, and EXTERNAL (certificate login) once it is switched on; ANONYMOUS is never offered. The AMQP vhost / maps to the connector's configured DefaultVhost segment (literal "default"); see Architecture for the dispatch detail.

Publish to a queue

The example below declares a queue, publishes one text/plain message to it through the default exchange (routing key = queue name), and consumes it back. Queue hello on vhost / lands on the KubeMQ Queue channel amqp.default.hello. Every client reads the broker endpoint from KUBEMQ_AMQP_URL (default amqp://guest:guest@localhost:5672/).

Which to use

This is the same round trip as the Getting started tutorial, shown here inline for reference. For a step-by-step walkthrough — enabling the connector, running a local broker, and verifying each step — use Getting started instead.

package main

import (
	"context"
	"log"
	"os"
	"time"

	amqp "github.com/rabbitmq/amqp091-go"
)

func amqpURL() string {
	if v := os.Getenv("KUBEMQ_AMQP_URL"); v != "" {
		return v
	}
	return "amqp://guest:guest@localhost:5672/"
}

func main() {
	conn, err := amqp.Dial(amqpURL())
	if err != nil {
		log.Fatalf("dial: %v", err)
	}
	defer func() { _ = conn.Close() }()

	ch, err := conn.Channel()
	if err != nil {
		log.Fatalf("channel: %v", err)
	}
	defer func() { _ = ch.Close() }()

	// Declare "hello" → KubeMQ Queue channel amqp.default.hello.
	q, err := ch.QueueDeclare("hello", false, false, false, false, nil)
	if err != nil {
		log.Fatalf("declare queue: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	// Publish on the default exchange — routing key = queue name.
	if err := ch.PublishWithContext(ctx, "", q.Name, false, false, amqp.Publishing{
		ContentType: "text/plain",
		Body:        []byte("Hello World!"),
	}); err != nil {
		log.Fatalf("publish: %v", err)
	}
	log.Printf(" [x] Sent %q", "Hello World!")

	// Consume with auto-ack and print the exact body.
	msgs, err := ch.Consume(q.Name, "", true, false, false, false, nil)
	if err != nil {
		log.Fatalf("consume: %v", err)
	}
	select {
	case d := <-msgs:
		log.Printf(" [x] Received %q", string(d.Body))
	case <-ctx.Done():
		log.Fatalf("timed out: %v", ctx.Err())
	}
}
import os

import pika

URL = os.environ.get("KUBEMQ_AMQP_URL", "amqp://guest:guest@localhost:5672/")
QUEUE = "hello"  # → KubeMQ Queue channel amqp.default.hello


def main() -> None:
    connection = pika.BlockingConnection(pika.URLParameters(URL))
    channel = connection.channel()

    # Default (nameless) exchange: routing key == queue name.
    channel.queue_declare(queue=QUEUE, durable=False, exclusive=False, auto_delete=False)

    channel.basic_publish(
        exchange="",
        routing_key=QUEUE,
        body=b"Hello World!",
        properties=pika.BasicProperties(content_type="text/plain"),
    )
    print(" [x] Sent 'Hello World!'")

    method, _props, body = channel.basic_get(queue=QUEUE, auto_ack=True)
    if method is None:
        raise SystemExit("no message received from queue")
    print(f" [x] Received {body.decode()!r}")

    channel.close()
    connection.close()


if __name__ == "__main__":
    main()
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public final class Main {

    private static final String QUEUE = "hello"; // → amqp.default.hello

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(System.getenv().getOrDefault(
                "KUBEMQ_AMQP_URL", "amqp://guest:guest@localhost:5672/"));

        try (Connection connection = factory.newConnection();
                Channel channel = connection.createChannel()) {

            // queue.declare: non-durable, non-exclusive, no auto-delete.
            channel.queueDeclare(QUEUE, false, false, false, null);

            // Publish via the default exchange ("") with routing key = queue name.
            AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
                    .contentType("text/plain")
                    .build();
            channel.basicPublish("", QUEUE, props, "Hello World!".getBytes("UTF-8"));
            System.out.println("[x] Sent 'Hello World!'");

            BlockingQueue<String> received = new ArrayBlockingQueue<>(1);
            DeliverCallback onDeliver = (tag, delivery) ->
                    received.offer(new String(delivery.getBody(), "UTF-8"));
            channel.basicConsume(QUEUE, true, onDeliver, tag -> { });

            String got = received.poll(15, TimeUnit.SECONDS);
            if (got == null) {
                throw new IllegalStateException("timed out waiting for the message");
            }
            System.out.println("[x] Received '" + got + "'");
        }
    }
}
import amqp from "amqplib";

// amqplib reads the URL path as the vhost; the bare trailing "/" in the dev URL
// resolves to the default vhost (KubeMQ segment "default").
const URL = process.env.KUBEMQ_AMQP_URL ?? "amqp://guest:guest@localhost:5672/";
const QUEUE = "hello"; // → KubeMQ Queue channel amqp.default.hello

async function main() {
  const conn = await amqp.connect(URL);
  const ch = await conn.createChannel();

  await ch.assertQueue(QUEUE, { durable: false, autoDelete: false, exclusive: false });

  const received = new Promise((resolve) => {
    ch.consume(
      QUEUE,
      (msg) => {
        if (msg === null) return;
        console.log(`[x] Received: ${msg.content.toString()}`);
        resolve();
      },
      { noAck: true },
    );
  });

  // Default exchange ("") routes by queue name.
  ch.publish("", QUEUE, Buffer.from("Hello World!"), { contentType: "text/plain" });
  console.log("[x] Sent:     Hello World!");

  await received;
  await ch.close();
  await conn.close();
}

main().catch((err) => {
  console.error("publish failed:", err);
  process.exit(1);
});
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

const string queueName = "hello"; // → KubeMQ Queue channel amqp.default.hello

var factory = new ConnectionFactory
{
    Uri = new Uri(Environment.GetEnvironmentVariable("KUBEMQ_AMQP_URL")
                  ?? "amqp://guest:guest@localhost:5672/"),
};

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var ct = cts.Token;

await using var connection = await factory.CreateConnectionAsync(ct);
await using var channel = await connection.CreateChannelAsync(cancellationToken: ct);

// Non-durable, not exclusive, not auto-delete — a plain shared queue.
await channel.QueueDeclareAsync(queueName, durable: false, exclusive: false,
    autoDelete: false, arguments: null, cancellationToken: ct);

var received = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.ReceivedAsync += (_, ea) =>
{
    received.TrySetResult(Encoding.UTF8.GetString(ea.Body.Span));
    return Task.CompletedTask;
};
await channel.BasicConsumeAsync(queueName, autoAck: true, consumer: consumer, cancellationToken: ct);

// Publish to the default exchange with routing key = queue name.
var props = new BasicProperties { ContentType = "text/plain" };
await channel.BasicPublishAsync(exchange: "", routingKey: queueName, mandatory: false,
    basicProperties: props, body: Encoding.UTF8.GetBytes("Hello World!"), cancellationToken: ct);
Console.WriteLine("[x] Sent 'Hello World!'");

var message = await received.Task.WaitAsync(ct);
Console.WriteLine($"[x] Received '{message}'");
# frozen_string_literal: true
require "bunny"

URL = ENV.fetch("KUBEMQ_AMQP_URL", "amqp://guest:guest@localhost:5672/")
QUEUE = "hello" # → KubeMQ Queue channel amqp.default.hello

conn = Bunny.new(URL)
conn.start
ch = conn.create_channel

# Publisher confirms: wait for the broker to accept and route the publish before
# reading it back, so a fire-and-forget publish is never lost in flight.
ch.confirm_select

queue = ch.queue(QUEUE, durable: false, auto_delete: false, exclusive: false)

# Default ("") exchange routes by queue name.
ch.default_exchange.publish("Hello World!", routing_key: queue.name, content_type: "text/plain")
ch.wait_for_confirms
puts " [x] Sent 'Hello World!'"

body = nil
deadline = Time.now + 5
while body.nil? && Time.now < deadline
  _info, _props, body = queue.pop(manual_ack: false)
  sleep 0.2 if body.nil?
end
puts " [x] Received '#{body}'"

conn.close
use futures_lite::StreamExt;
use lapin::{
    options::{BasicConsumeOptions, BasicPublishOptions, QueueDeclareOptions},
    types::FieldTable,
    BasicProperties, Connection, ConnectionProperties,
};

const QUEUE: &str = "hello"; // → KubeMQ Queue channel amqp.default.hello

// lapin reads the URL path as the vhost and treats a bare trailing "/" as an
// empty vhost, which the connector rejects. The default "/" vhost must be
// percent-encoded as "%2f".
fn amqp_url() -> String {
    let url = std::env::var("KUBEMQ_AMQP_URL")
        .unwrap_or_else(|_| "amqp://guest:guest@localhost:5672/".into());
    let host = url.rsplit_once('@').map_or(url.as_str(), |(_, h)| h);
    if host.ends_with('/') && !host.ends_with("/%2f") {
        format!("{url}%2f")
    } else {
        url
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conn = Connection::connect(&amqp_url(), ConnectionProperties::default()).await?;
    let channel = conn.create_channel().await?;

    channel
        .queue_declare(QUEUE, QueueDeclareOptions::default(), FieldTable::default())
        .await?;

    // Publish to the default exchange, routing key = queue name.
    channel
        .basic_publish(
            "",
            QUEUE,
            BasicPublishOptions::default(),
            b"Hello World!",
            BasicProperties::default().with_content_type("text/plain".into()),
        )
        .await?
        .await?;
    println!("[x] Sent 'Hello World!'");

    let mut consumer = channel
        .basic_consume(
            QUEUE,
            "hello-consumer",
            BasicConsumeOptions { no_ack: true, ..Default::default() },
            FieldTable::default(),
        )
        .await?;

    if let Some(delivery) = consumer.next().await {
        let delivery = delivery?;
        println!("[x] Received '{}'", String::from_utf8_lossy(&delivery.data));
    }

    conn.close(0, "done").await?;
    Ok(())
}

Supported languages

The connector speaks standard AMQP 0-9-1, so any conformant RabbitMQ client works. The examples pin one native client per language — there is no KubeMQ SDK, no proto bindings, and no published package.

LanguageClient libraryNotes
Gogithub.com/rabbitmq/amqp091-goThe RabbitMQ team's Go client.
PythonpikaBlockingConnection with URLParameters.
Javacom.rabbitmq:amqp-clientThe official RabbitMQ Java client.
JavaScript / TypeScriptamqplibEncode the default / vhost as %2f in the URL.
C# / .NETRabbitMQ.ClientTask-based async API (v7+).
RubybunnyUse publisher confirms before consuming.
Rustlapinasync/await on Tokio; percent-encode the / vhost.

Next steps

Was this page helpful?

On this page