Apache ActiveMQ is a popular open source, multi-protocol, Java-based message broker. It supports industry standard protocols, so you get the benefits of client choices across a broad range of languages and platforms. By using Oracle’s MySQL Database service as a persistence store for ActiveMQ, you can employ AMQ as a highly available cloud native messaging platform for Kubernetes.

This guide uses ActiveMQ 5, but you can use a similar approach for the more modern ActiveMQ Artemis. You can find the complete code samples on GitHub.

Setup

To begin, you need access to an Oracle Cloud Infrastructure (OCI) account. You also need to provision an Oracle Container Engine for Kubernetes (OKE) cluster, a MySQL Database instance, and a bastion server. For the OKE cluster, the quick create wizard supplies some sensible defaults for your cluster and is the quickest way to get up and running. This process creates a virtual cloud network (VCN) with three subnets: A public subnet for the k8s API endpoint, a private subnet for worker nodes, and another public subnet for load balancers.

Either deploy the MySQL instance into the worker node subnet or create a separate private subnet and ensure that the security lists are open for the worker nodes to connect to the database on port 3306. Deploy the MySQL instance with high availability turned on. Then, create another instance to act as bastion in the load balancer subnet.

MySQL

When the database is up and running, connect to it through the bastion following the documentation for the MySQL Shell. Create a database and user for ActiveMQ with the following commands:

> CREATE DATABASE activemq;
> CREATE USER 'activemq'@'%' IDENTIFIED BY 'MyComplexPass#12';
> GRANT ALL ON activemq.* TO 'activemq'@'%';

Docker

Apache doesn’t publish a canonical Docker image for ActiveMQ, so we can build our own. Use the following minimal Dockerfile for a reference:

FROM openjdk:8-jre-alpine
WORKDIR /home/alpine
RUN apk update
ADD https://www.apache.org/dyn/closer.cgi?filename=/activemq/5.16.2/apache-activemq-5.16.2-bin.tar.gz&action=download amq.tar.gz
RUN tar -xvf amq.tar.gz --directory=/opt/
EXPOSE 8161 61616 5672 61613 1833
CMD ["/bin/sh","/opt/apache-activemq-5.16.2/bin/activemq","console"]

Build the docker image and push it to OCI Container Registry or your repository of choice.

Configuration

When you have connected to your Kubernetes cluster, create a namespace for your ActiveMQ deployment with the following command:

$ kubectl create ns active-mq

To use MySQL as a persistence store for AMQ, we need to inject some configuration files into the AMQ image. From this example config file, update the MySQL IP address, username, and password as appropriate.

<bean class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" id="mysql-ds">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver">
<property name="url" value="jdbc:mysql://<IP ADDRESS>/activemq?relaxAutoCommit=true">
<property name="username" value="activemq">
<property name="password" value="MyComplexPass#12">
<property name="poolPreparedStatements" value="true">
</property>

Save this file and use it to create a Kubernetes ConfigMap.

$ kubectl create configmap activemq-config --from-file=activemq.xml -n active-mq

We also create another ConfigMap from this file so that the web UI is accessible from outside the instance with the following command:

$ kubectl create configmap jetty-cm --from-file=jetty.xml -n active-mq

Deployment

With those configurations in place and the Docker image built, you can now create your AMQ deployment.

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
  labels:
    app: active-mq
  name: active-mq
  namespace: active-mq
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: active-mq
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: active-mq
    spec:
      imagePullSecrets:
        - name: ocir-secret # Remember to create your image pull secret if you are using OCIR
      initContainers:
      - image: busybox
        name: jar-getter
        command: ["/bin/sh", "-c"]
        args:
        - "wget -O /work-dir/mysql-connector-java-8.0.26.tar.gz https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.26.tar.gz; tar -xf /work-dir/mysql-connector-java-8.0.26.tar.gz --directory=/work-dir"
        volumeMounts:
        - name: workdir
          mountPath: "/work-dir"
      containers:
      - image: <my activemq="" image="">
        imagePullPolicy: Always
        name: activemq
        ports:
        - containerPort: 61616
          name: jmx
          protocol: TCP
        - containerPort: 8161
          name: ui
          protocol: TCP
        - containerPort: 61616
          name: openwire
          protocol: TCP
        - containerPort: 5672
          name: amqp
          protocol: TCP
        - containerPort: 61613
          name: stomp
          protocol: TCP
        - containerPort: 1883
          name: mqtt
          protocol: TCP
        resources:
          requests:
            memory: 500Mi
            cpu: 200m
          limits:
            memory: 1000Mi
            cpu: 400m
        volumeMounts:
        - name: activemq-config
          mountPath: /opt/apache-activemq-5.16.2/conf/activemq.xml
          subPath: activemq.xml
        - name: jetty-cm
          mountPath: /opt/apache-activemq-5.16.2/conf/jetty.xml
          subPath: jetty.xml
        - name: workdir
          mountPath: /opt/apache-activemq-5.16.2/lib/optional/mysql-connector-java-8.0.26.jar
          subPath: mysql-connector-java-8.0.26/mysql-connector-java-8.0.26.jar
      volumes:
      - name: activemq-config
        configMap:
          name: activemq-config
          items:
          - key: activemq.xml
            path: activemq.xml
      - name: jetty-cm
        configMap:
          name: jetty-cm
          items:
          - key: jetty.xml
            path: jetty.xml
      - name: workdir
        emptyDir: {}
      restartPolicy: Always

$ kubectl apply -f activemq-deploy.yaml

Next, check that your deployment was successful and that you have a running ActiveMQ pod.

$ kubectl get pods -n active-mq
NAME                         READY   STATUS    RESTARTS   AGE
active-mq-64bd4bb5d9-8p4p2   1/1     Running   0          42s

Also check that ActiveMQ has created the tables in your MySQL database as expected.

> USE activemq;
Default schema set to `activemq`.
Fetching table and column names from `activemq` for auto-completion... Press ^C to stop.
> show tables;
+--------------------+
| Tables_in_activemq |
+--------------------+
| ACTIVEMQ_ACKS      |
| ACTIVEMQ_LOCK      |
| ACTIVEMQ_MSGS      |
+--------------------+

Service

Now that ActiveMQ has been deployed successfully, you need to create a service to access it. Here we’re exposing both the web UI and all the messaging ports through a public load balancer, but in production, these services are internal to Kubernetes.

apiVersion: v1
kind: Service
metadata:
  name: active-mq
  namespace: active-mq
  labels:
    app: active-mq
spec:
  selector:
    app: active-mq
  ports:
  - name: dashboard
    port: 8161
    targetPort: 8161
    protocol: TCP
  - name: openwire
    port: 61616
    targetPort: 61616
    protocol: TCP
  - name: amqp
    port: 5672
    targetPort: 5672
    protocol: TCP
  - name: stomp
    port: 61613
    targetPort: 61613
    protocol: TCP
  - name: mqtt
    port: 1883
    targetPort: 1883
    protocol: TCP
  type: LoadBalancer

$ kubectl apply -f activemq-svc.yaml

High availability

For high availability of the platform… That’s it! If the ActiveMQ pod goes down, it’s automatically rescheduled and the new pod acquires the lock in the database, all in just a few seconds. We can test this out. Log in to the web UI at <loadbalancer ip>:8161/admin/ and log in with the default admin/admin. Create a test queue and use ‘send to’ to publish a test message to the queue. Then simulate failover.

A screenshot of the ActiveMQ dashboard showing the queues.

$ kubectl get pods -n active-mq
NAME                         READY   STATUS    RESTARTS   AGE
active-mq-64bd4bb5d9-djcgn   1/1     Running   0          8m
$ kubectl delete pod active-mq-64bd4bb5d9-djcgn -n active-mq
pod "active-mq-64bd4bb5d9-djcgn" deleted
$ kubectl get pods -n active-mq
NAME                         READY   STATUS    RESTARTS   AGE
active-mq-64bd4bb5d9-8p4p2   1/1     Running   0          5s

Refresh your browser open to the AMQ web UI. Your test message is still there!

Conclusion

It’s easy to get started running popular open source technology on Oracle Cloud Infrastructure. Sign up for your free trial today and dig into OKE by deploying a Helidon application.

For more information, see the following resources: