Getting Started
Connect a stock RabbitMQ client to KubeMQ and run a declare-publish-consume round-trip over a KubeMQ Queue in minutes — no KubeMQ SDK required.
Get a message flowing through the KubeMQ RabbitMQ (AMQP 0-9-1) connector in minutes. You point a standard RabbitMQ client at the broker, declare a queue, publish a message through the default exchange, and consume it back — all over the native AMQP 0-9-1 wire, with no KubeMQ SDK. This walkthrough takes you from a running server to a verified round-trip.
Prerequisites
- A running kubemq-server with the RabbitMQ connector enabled and reachable on port 5672 (plain TCP). The connector is opt-in (disabled by default) — see the enable step below.
- One of the AMQP 0-9-1 clients below for your language (the examples pin a native RabbitMQ client per language — there is no KubeMQ SDK).
Enable the connector
The RabbitMQ (AMQP 0-9-1) connector is disabled by default — a stock kubemq-server does not bind the AMQP listener until you turn it on. Enable it with its enable variable:
docker run -d \ --name kubemq \ -p 5672:5672 \ -p 5671:5671 \ -p 50000:50000 \ -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY \ -e CONNECTORS_AMQP_ENABLE=true \ europe-docker.pkg.dev/kubemq/images/kubemq:nextThe enable variable is CONNECTORS_AMQP_ENABLE — note that this is the AMQP 0-9-1
(RabbitMQ) connector, distinct from the AMQP 1.0 connector's CONNECTORS_AMQP10_ENABLE.
For Kubernetes, set spec.amqp.enabled: true in the KubemqCluster CR.
Bring up a throwaway local broker with the RabbitMQ connector enabled:
Every example reads a single environment variable for the broker endpoint. On a development
broker, auth is disabled and guest:guest is accepted with no JWT:
# default: amqp://guest:guest@localhost:5672/
export KUBEMQ_AMQP_URL="amqp://guest:guest@localhost:5672/"The AMQP vhost / maps to the connector's configured DefaultVhost segment (literal
"default"), so queue hello on vhost / lands on the KubeMQ Queue channel
amqp.default.hello. Connect to vhost /, not to a literal vhost named default — the
default segment is reserved and a direct connection to it is rejected. See
Channel mapping.
To disable the RabbitMQ connector after enabling it, set its enable variable to false:
docker run -d -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY -e CONNECTORS_AMQP_ENABLE=false europe-docker.pkg.dev/kubemq/images/kubemq:nextAMQP 0-9-1 and AMQP 1.0 share ports 5672/5671 but have separate enable flags — disabling
RabbitMQ leaves AMQP 1.0 reachable on the same ports, and vice-versa. You can also disable
just one listener by setting CONNECTORS_AMQP_PORT=0 (plain) or CONNECTORS_AMQP_TLS_PORT=0
(TLS). See Configuration for the full settings list.
How it works
A publisher declares a queue and publishes to an exchange with a routing key. The connector
resolves the exchange routing to the target queue at publish time and writes the message to
that queue's KubeMQ Queue channel amqp.{vhost}.{queue}. A consumer subscribed to the same
queue receives it.
Publishing to queue hello on the default exchange maps to the KubeMQ Queue channel amqp.default.hello; a consumer on the same queue receives the message.
Steps
Connect and declare a queue
Open a connection to the endpoint in KUBEMQ_AMQP_URL, open a channel, and declare the
target queue. The language tabs run the complete round-trip from a single program:
connect, declare the hello queue, publish one message through the default exchange, and
consume it back.
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.Println(" [x] Sent '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()) {
channel.queueDeclare(QUEUE, false, false, false, null);
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";
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 },
);
});
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("getting-started 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);
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);
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
ch.confirm_select # publisher confirms, so the publish is routed before we consume
queue = ch.queue(QUEUE, durable: false, auto_delete: false, exclusive: false)
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.closeuse futures_lite::StreamExt;
use lapin::{
options::{BasicConsumeOptions, BasicPublishOptions, QueueDeclareOptions},
types::FieldTable,
BasicProperties, Connection, ConnectionProperties,
};
const QUEUE: &str = "hello"; // → KubeMQ Queue channel amqp.default.hello
// The default "/" vhost must be percent-encoded as "%2f" for lapin's URI parser.
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?;
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(())
}Publish a message
The program above publishes one text/plain message to the default exchange with the
routing key set to the queue name hello. The default (nameless) exchange routes a message
to the queue whose name equals the routing key, so the message lands in queue hello — the
KubeMQ Queue channel amqp.default.hello. Because a KubeMQ Queue is durable and
at-least-once, you can publish before a consumer is attached and the message waits in the
queue.
Consume and verify
A consumer subscribed to hello receives the message. When it arrives the program prints
it and exits:
[x] Sent 'Hello World!'
[x] Received 'Hello World!'Unlike fire-and-forget pub/sub, the Queue holds the message until a consumer acknowledges it, so order of operations is forgiving. For competing consumers and fair dispatch across a worker pool, see Work queues.
Exchanges and bindings are virtual — resolved at publish time, not stored. A fanout,
direct, topic, or headers exchange routes to a set of queues, and each resolved queue
is a normal KubeMQ Queue channel. See
Exchanges and routing.
Next steps
Configuration
The 12 connector settings, the CONNECTORS_AMQP_ENABLE disable var, the reserved default vhost, and TLS.
Architecture
The everything-is-a-Queue model, virtual exchanges, and cross-protocol interop.
Work queues
Competing consumers, prefetch, and fair dispatch over a single KubeMQ Queue channel.
Pub/sub
Fanout exchanges fan a publish out to every bound queue at publish time.
Was this page helpful?
Exchanges and Routing
How RabbitMQ exchange types (direct, fanout, topic, headers) work as virtual connector-side routing, resolved at publish time into KubeMQ Queue channels.
Authentication
How a RabbitMQ (AMQP 0-9-1) client authenticates to KubeMQ — SASL PLAIN with the password as a KubeMQ JWT, the accept-any dev default, and Casbin authorization.