Deploy RabbitMQ with Portworx Enterprise
RabbitMQ is an open-source message broker. It plays a central role in many distributed systems. You can configure RabbitMQ to safely pass messages between decoupled systems. Learn how you can run RabbitMQ with Portworx on Kubernetes. Portworx will provide a reliable persistent storage layer which makes sure no messages are lost.
The RabbitMQ cluster will use mirrored queues to persist data and metadata to Portworx volumes. Portworx also replicates the volumes providing multiple layers of redundancy.
To deploy with RabbitMQ Portworx Enterprise on Kubernetes, complete the following collection of tasks:
- Create a StorageClass for dynamic volume provisioning.
- Set up RabbitMQ to configure message broker.
Create a StorageClass
Enter the following kubectl apply command to create a StorageClass:
kubectl apply -f - <<'_EOF'
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: portworx-rabbitmq
parameters:
io_priority: high
repl: "2"
group: "rmq_vg"
provisioner: pxd.portworx.com
allowVolumeExpansion: true
reclaimPolicy: Delete
_EOF
storageclass.storage.k8s.io/portworx-rabbitmq created
Note the following about this StorageClass:
- The
provisionerparameter is set topxd.portworx.com. For details about the Portworx-specific parameters, refer to the Portworx Volume section. - Two replicas of each volume will be created
- A high-priority storage pool will be used
- The
allowVolumeExpansionparameter is set totrue - The
reclaimPolicyparameter is set toDelete.
You'll be referencing this StorageClass later in this while creating a PersistentVolumeClaim. Configure the RabbitMQ pods to use the persistent volumes mapped to these PVC.
If you're using Portworx with CSI, you need to set the value of the provisioner parameter to pxd.portworx.com.
Set up RabbitMQ
In this section, we'll show how you can install RabbitMQ.
Note that the spec files used in this section are based on these Helm templates.
-
Enter the following command to create a
ConfigMapand a Kubernetes secret:kubectl apply -f - <<'_EOF'---# Source: rabbitmq-ha/templates/configmap.yamlapiVersion: v1kind: ConfigMapmetadata:name: rmq-rabbitmq-hanamespace: defaultlabels:app: rabbitmq-hadata:enabled_plugins: |[rabbitmq_shovel,rabbitmq_shovel_management,rabbitmq_federation,rabbitmq_federation_management,rabbitmq_consistent_hash_exchange,rabbitmq_management,rabbitmq_peer_discovery_k8s].rabbitmq.conf: |## RabbitMQ configuration## Ref: https://github.com/rabbitmq/rabbitmq-server/blob/master/docs/rabbitmq.conf.example## Authentification## Clusteringcluster_formation.peer_discovery_backend = rabbit_peer_discovery_k8scluster_formation.k8s.host = kubernetes.default.svc.cluster.localcluster_formation.k8s.address_type = hostnamecluster_formation.node_cleanup.interval = 10# Set to false if automatic cleanup of absent nodes is desired.# This can be dangerous, see http://www.rabbitmq.com/cluster-formation.html#node-health-checks-and-cleanup.cluster_formation.node_cleanup.only_log_warning = truecluster_partition_handling = autoheal## The default "guest" user is only permitted to access the server## via a loopback interface (e.g. localhost)loopback_users.guest = falsemanagement.load_definitions = /etc/definitions/definitions.json## Memory-based Flow Control thresholdvm_memory_high_watermark.absolute = 256MB## Auth HTTP Backend Plugin## LDAP Plugin## MQTT Plugin## Web MQTT Plugin## STOMP Plugin## Web STOMP Plugin## Prometheus Plugin## AMQPS support---# Source: rabbitmq-ha/templates/secret.yamlapiVersion: v1kind: Secretmetadata:name: rmq-rabbitmq-hanamespace: defaultlabels:app: rabbitmq-hatype: Opaquedata:rabbitmq-username: "YWRtaW4="rabbitmq-password: "c2VjcmV0cGFzc3dvcmQ="rabbitmq-management-username: "bWFuYWdlbWVudA=="rabbitmq-management-password: "YW5vdGhlcnNlY3JldHBhc3N3b3Jk"rabbitmq-erlang-cookie: "c2VjcmV0Y29va2ll"definitions.json: "ewogICJnbG9iYWxfcGFyYW1ldGVycyI6IFsKICAgIAogIF0sCiAgInVzZXJzIjogWwogICAgewogICAgICAibmFtZSI6ICJtYW5hZ2VtZW50IiwKICAgICAgInBhc3N3b3JkIjogImFub3RoZXJzZWNyZXRwYXNzd29yZCIsCiAgICAgICJ0YWdzIjogIm1hbmFnZW1lbnQiCiAgICB9LAogICAgewogICAgICAibmFtZSI6ICJhZG1pbiIsCiAgICAgICJwYXNzd29yZCI6ICJzZWNyZXRwYXNzd29yZCIsCiAgICAgICJ0YWdzIjogImFkbWluaXN0cmF0b3IiCiAgICB9CiAgXSwKICAidmhvc3RzIjogWwogICAgewogICAgICAibmFtZSI6ICIvIgogICAgfQogIF0sCiAgInBlcm1pc3Npb25zIjogWwogICAgewogICAgICAidXNlciI6ICJhZG1pbiIsCiAgICAgICJ2aG9zdCI6ICIvIiwKICAgICAgImNvbmZpZ3VyZSI6ICIuKiIsCiAgICAgICJyZWFkIjogIi4qIiwKICAgICAgIndyaXRlIjogIi4qIgogICAgfQogIF0sCiAgInBhcmFtZXRlcnMiOiBbCiAgICAKICBdLAogICJwb2xpY2llcyI6IFsKICAgIAogIF0sCiAgInF1ZXVlcyI6IFsKICAgIAogIF0sCiAgImV4Y2hhbmdlcyI6IFsKICAgIAogIF0sCiAgImJpbmRpbmdzIjogWwogICAgCiAgXQp9"_EOFconfigmap/rmq-rabbitmq-ha createdsecret/rmq-rabbitmq-ha createdNote that we're separating the application code from the configuration. Refer to the Configuration management with Containers for more details about the
ConfigMapresource. -
To set up RBAC, apply the following spec in your cluster:
kubectl apply -f - <<'_EOF'---# Source: rabbitmq-ha/templates/serviceaccount.yamlapiVersion: v1kind: ServiceAccountmetadata:labels:app: rabbitmq-haname: rmq-rabbitmq-hanamespace: default---# Source: rabbitmq-ha/templates/role.yamlapiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata:labels:app: rabbitmq-haname: rmq-rabbitmq-hanamespace: defaultrules:- apiGroups: [""]resources: ["endpoints"]verbs: ["get"]---# Source: rabbitmq-ha/templates/rolebinding.yamlapiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:labels:app: rabbitmq-haname: rmq-rabbitmq-hanamespace: defaultsubjects:- kind: ServiceAccountname: rmq-rabbitmq-hanamespace: defaultroleRef:apiGroup: rbac.authorization.k8s.iokind: Rolename: rmq-rabbitmq-ha_EOFserviceaccount/rmq-rabbitmq-ha createdrole.rbac.authorization.k8s.io/rmq-rabbitmq-ha createdrolebinding.rbac.authorization.k8s.io/rmq-rabbitmq-ha created -
Enter the following command to create a
StatefulSetand the required supporting services:kubectl apply -f - <<'_EOF'---# Source: rabbitmq-ha/templates/service-discovery.yamlapiVersion: v1kind: Servicemetadata:name: rmq-rabbitmq-ha-discoverynamespace: defaultlabels:app: rabbitmq-haspec:clusterIP: Noneports:- name: httpprotocol: TCPport: 15672targetPort: http- name: amqpprotocol: TCPport: 5672targetPort: amqp- name: epmdprotocol: TCPport: 4369targetPort: epmdpublishNotReadyAddresses: trueselector:app: rabbitmq-hatype: ClusterIP---# Source: rabbitmq-ha/templates/service.yamlapiVersion: v1kind: Servicemetadata:name: rmq-rabbitmq-hanamespace: defaultlabels:app: rabbitmq-haspec:clusterIP: "None"ports:- name: httpprotocol: TCPport: 15672targetPort: http- name: amqpprotocol: TCPport: 5672targetPort: amqp- name: epmdprotocol: TCPport: 4369targetPort: epmdselector:app: rabbitmq-hatype: ClusterIP---# Source: rabbitmq-ha/templates/statefulset.yamlapiVersion: apps/v1kind: StatefulSetmetadata:name: rmq-rabbitmq-hanamespace: defaultlabels:app: rabbitmq-haspec:podManagementPolicy: OrderedReadyserviceName: rmq-rabbitmq-ha-discoveryreplicas: 2updateStrategy:type: OnDeleteselector:matchLabels:app: rabbitmq-hatemplate:metadata:labels:app: rabbitmq-haannotations:checksum/config: 1b2c44d6700bf1d0c528a1dc3867f71e5a5e7c9099e15d68639ba407205c1d30spec:terminationGracePeriodSeconds: 10schedulerName: storksecurityContext:fsGroup: 101runAsGroup: 101runAsNonRoot: truerunAsUser: 100serviceAccountName: rmq-rabbitmq-hainitContainers:- name: bootstrapimage: busybox:1.30.1imagePullPolicy: IfNotPresentcommand: ['sh']args:- "-c"- |set -excp /configmap/* /etc/rabbitmqecho "${RABBITMQ_ERLANG_COOKIE}" > /var/lib/rabbitmq/.erlang.cookieenv:- name: POD_NAMEvalueFrom:fieldRef:apiVersion: v1fieldPath: metadata.name- name: RABBITMQ_MNESIA_DIRvalue: /var/lib/rabbitmq/mnesia/rabbit@$(POD_NAME).rmq-rabbitmq-ha-discovery.default.svc.cluster.local- name: RABBITMQ_ERLANG_COOKIEvalueFrom:secretKeyRef:name: rmq-rabbitmq-hakey: rabbitmq-erlang-cookievolumeMounts:- name: configmapmountPath: /configmap- name: configmountPath: /etc/rabbitmq- name: datamountPath: /var/lib/rabbitmqcontainers:- name: rabbitmq-haimage: rabbitmq:3.7.19-alpineimagePullPolicy: IfNotPresentports:- name: epmdprotocol: TCPcontainerPort: 4369- name: amqpprotocol: TCPcontainerPort: 5672- name: httpprotocol: TCPcontainerPort: 15672livenessProbe:exec:command:- /bin/sh- -c- 'wget -O - -q --header "Authorization: Basic `echo -n \"$RABBIT_MANAGEMENT_USER:$RABBIT_MANAGEMENT_PASSWORD\"| base64`" http://localhost:15672/api/healthchecks/node | grep -qF "{\"status\":\"ok\"}"'failureThreshold: 6initialDelaySeconds: 120periodSeconds: 10timeoutSeconds: 5readinessProbe:exec:command:- /bin/sh- -c- 'wget -O - -q --header "Authorization: Basic `echo -n \"$RABBIT_MANAGEMENT_USER:$RABBIT_MANAGEMENT_PASSWORD\"| base64`" http://localhost:15672/api/healthchecks/node | grep -qF "{\"status\":\"ok\"}"'failureThreshold: 6initialDelaySeconds: 20periodSeconds: 5timeoutSeconds: 3env:- name: MY_POD_NAMEvalueFrom:fieldRef:apiVersion: v1fieldPath: metadata.name- name: RABBITMQ_USE_LONGNAMEvalue: "true"- name: RABBITMQ_NODENAMEvalue: rabbit@$(MY_POD_NAME).rmq-rabbitmq-ha-discovery.default.svc.cluster.local- name: K8S_HOSTNAME_SUFFIXvalue: .rmq-rabbitmq-ha-discovery.default.svc.cluster.local- name: K8S_SERVICE_NAMEvalue: rmq-rabbitmq-ha-discovery- name: RABBITMQ_ERLANG_COOKIEvalueFrom:secretKeyRef:name: rmq-rabbitmq-hakey: rabbitmq-erlang-cookie- name: RABBIT_MANAGEMENT_USERvalueFrom:secretKeyRef:name: rmq-rabbitmq-hakey: rabbitmq-management-username- name: RABBIT_MANAGEMENT_PASSWORDvalueFrom:secretKeyRef:name: rmq-rabbitmq-hakey: rabbitmq-management-passwordvolumeMounts:- name: datamountPath: /var/lib/rabbitmq- name: configmountPath: /etc/rabbitmq- name: definitionsmountPath: /etc/definitionsreadOnly: trueaffinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 1podAffinityTerm:topologyKey: "kubernetes.io/hostname"labelSelector:matchLabels:app: rabbitmq-havolumes:- name: configemptyDir: {}- name: configmapconfigMap:name: rmq-rabbitmq-ha- name: definitionssecret:secretName: rmq-rabbitmq-haitems:- key: definitions.jsonpath: definitions.jsonvolumeClaimTemplates:- metadata:name: dataannotations:spec:accessModes:- "ReadWriteOnce"resources:requests:storage: "8Gi"storageClassName: "portworx-rabbitmq"_EOFservice/rmq-rabbitmq-ha-discovery createdservice/rmq-rabbitmq-ha createdstatefulset.apps/rmq-rabbitmq-ha createdNote that the last line of the spec references the
portworx-rabbitmqstorage class defined in the Create a StorageClass section. As a result, Kubernetes will automatically create a new PVC for each replica.
Validate the cluster functionality
Once you've set up RabbitMQ manually or using Helm, you can use the PerfTest tool and perform the following to test your installation:
- Create a RabbitMQ policy for H/A
- Set up RabbitMQ monitoring
- Create a containerized testing environment
- Simulate basic workloads
- Simulate a failure and check recovery
Create a RabbitMQ policy for H/A
Configure a policy named perf-test-with-ha with the following settings:
- The
rabbitmqctl set_policy perf-test-with-ha '^perf-test'portion of the command only applies these settings to queues that begin withperf-test - Sets the ha-mode parameter equal to
2. - Places the queue leader on the least-loaded node.
- Sets up the queues as lazy. RabbitMQ will save the queues to disk as early as possible.
kubectl exec rmq-rabbitmq-ha-0 -- \
rabbitmqctl set_policy perf-test-with-ha '^perf-test' \
'{
"ha-mode":"exactly",
"ha-params":2,
"ha-sync-mode":"automatic",
"queue-master-locator":"min-masters",
"queue-mode":"lazy"
}' --apply-to queues
Setting policy "perf-test-with-ha" for pattern "^perf-test" to "{
"ha-mode":"exactly",
"ha-params":2,
"ha-sync-mode":"automatic",
"queue-master-locator":"min-masters",
"queue-mode":"lazy"
}" with priority "0" for vhost "/" ...
Set up RabbitMQ monitoring
RabbitMQ provides a web-based user interface that you can use to manage your cluster. To access it, you must forward connections made to a local port to the 15672 port on the rmq-rabbitmq-ha-0 Pod.
-
Enter the following
kubectl port-forwardcommand to forward all connections made tolocalhost:15672tormq-rabbitmq-ha-0:15672:kubectl port-forward rmq-rabbitmq-ha-0 15672:15672Forwarding from 127.0.0.1:15672 -> 15672 -
You can now access the RabbitMQ user interface by pointing your browser to
localhost:15672. Use username asadminand password assecretpasswordto login. -
Visit the Queue section at your localhost:
http://127.0.0.1:15672/#/queues, to see that theperf-test-with-hapolicy is applied. -
Additionally, you can stream to
stdoutthe logs of the Pod that survives the failover test by entering the following command:kubectl logs -f rmq-rabbitmq-ha-0
Create a containerized testing environment
The following kubectl run command creates a Pod with a sleeping container that runs for an hour (see the "args": ["-c", "sleep 3600"] line). Then, Kubernetes automatically restarts the container (see the --restart=Always line).
kubectl run perftest \
--restart=Always \
--image=pivotalrabbitmq/perf-test:latest \
--overrides='
{
"spec": {
"affinity": {
"podAntiAffinity": {
"requiredDuringSchedulingIgnoredDuringExecution": [
{
"labelSelector": {
"matchExpressions": [
{
"key": "app",
"operator": "In",
"values": ["rabbitmq-ha"]
}
]
},
"topologyKey": "kubernetes.io/hostname"
}
]
}
},
"containers": [
{
"name": "perftest",
"image": "pivotalrabbitmq/perf-test:latest",
"command": ["sh", "-c", "sleep 3600"]
}
]
}
}'
pod/perftest created
This creates a Pod with one sleeping container. It is then scheduled onto a node that doesn't run RabbitMQ. This way, you evenly distribute bandwidth between both RabbitMQ nodes. We use a non-root user to run the commands which is useful when you have PodSecurityPolicies enabled.
Simulate basic workloads
To start the simulated producers and consumers, run the bin/runjava command in the perftest container:
kubectl exec -it perftest -- \
java -jar /perf_test/perf-test.jar \
--time 900 \
--queue-pattern "perf-test-%d" \
--queue-pattern-from 1 \
--queue-pattern-to 2 \
--producers 2 \
--consumers 8 \
--queue-args x-cancel-on-ha-failover=true \
--flag persistent \
--uri "amqp://admin:secretpassword@rmq-rabbitmq-ha:5672?failover=failover_exchange"
There are several things to note about this example:
- Runs the session for 15 minutes with queues named in the format of
perf-test-followed by a number (in our case1and2) - Creates threads for 2 producers and 8 consumers (so theoretically each RabbitMQ replica hosts 1 queue and gets 1 producer and 4 consumers
- Trades off performance for reliability, by flagging the messages as persistent
Simulate a failure and check recovery
Simulate a failure by force deleting the rmq-rabbitmq-ha-1 Pod:
kubectl delete pod rmq-rabbitmq-ha-1 --force --grace-period=0
warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "rmq-rabbitmq-ha-1" force deleted
As the system recovers from the failure, you will see that the performance testing session will pause for a short period. As a result, the messages will pile up in the queue. After the failover, the cluster resumes normal processing, and no messages are lost.
Uninstall RabbitMQ
Enter the following command to delete all the associated resources created with RabbitMQ:
kubectl delete sts/rmq-rabbitmq-ha \
svc/rmq-rabbitmq-ha svc/rmq-rabbitmq-ha-discovery \
rolebinding/rmq-rabbitmq-ha \
role/rmq-rabbitmq-ha \
sa/rmq-rabbitmq-ha \
secret/rmq-rabbitmq-ha \
cm/rmq-rabbitmq-ha
Finally, regardless of the deployment method, execute the following command:
kubectl delete pvc/data-rmq-rabbitmq-ha-0 pvc/data-rmq-rabbitmq-ha-1 \
sc/portworx-rabbitmq \
po/perftest
These kubectl delete commands remove the following:
- The workload itself (StatefulSet and the associated services)
- The RBAC settings for the workload (Rolebinding, Role, and ServiceAccount)
- The RBAC configuration (ConfigMap and secret)
- The volumes including data (PersistentVolumeClaims)
- The volume parameters (StorageClass)
- The Pod we used for testing