Back to posts

VerneMQ on GKE — MQTT Broker via Helm

Read the full guide on docs.beyondyou.my.id
messagingvernemqmqttgkekuberneteshelm

VerneMQ on GKE — MQTT Broker via Helm

Table of Contents

SectionTopicDescription
01VerneMQ vs EMQXWhich broker fits your use case.
02ArchitectureVerneMQ cluster topology on GKE.
03Helm InstallationQuick deploy with Helm chart.
04Values ConfigurationProduction values.yaml breakdown.
05Services & NetworkingLoadBalancer, Headless, and ingress.
06Security & ACLAnonymous access, ACL rules, TLS.
07EMQX vs VerneMQ ComparisonSide-by-side feature matrix.

1. VerneMQ vs EMQX

Both are open-source MQTT brokers, but they have different strengths.

AspectVerneMQEMQX
LanguageErlang/OTPErlang/OTP
LicenseApache 2.0Apache 2.0 (EMQX Enterprise is commercial)
ClusteringAutomatic (Erlang distribution)DNS-based or etcd
Plugin systemLua hooksEMQX extensions (native + Lua)
DashboardNo built-in (use vmq-admin CLI)Built-in REST API + Dashboard
WebSocketSupportedSupported
Rule engineNoYes (SQL-based)
WebhookLua hooksNative bridges
CommunitySmaller, focusedLarge, active
Best forSimple MQTT, Erlang-native clusteringFeature-rich IoT, complex event processing

2. Architecture

graph TB
    subgraph CLIENTS["Clients"]
        iot["IoT Devices"]
        mobile["Mobile App"]
    end

    subgraph LB["GCP Internal LB"]
        ilb["Internal Load Balancer\nMQTT :1883"]
    end

    subgraph VERNEVMQ["VerneMQ Cluster (3 nodes)"]
        vmq1["vernemq-0\n(Erlang node)"]
        vmq2["vernemq-1\n(Erlang node)"]
        vmq3["vernemq-2\n(Erlang node)"]
    end

    iot -->|"MQTT :1883"| ilb
    mobile -->|"MQTT :1883"| ilb
    ilb --> vmq1
    ilb --> vmq2
    ilb --> vmq3
    vmq1 <-->|"Erlang\nclustering"| vmq2
    vmq2 <-->|"Erlang\nclustering"| vmq3
    vmq1 <-->|"Erlang\nclustering"| vmq3

Key Differences from EMQX

AspectVerneMQEMQX
Cluster discoveryErlang EPMD (built-in)DNS SRV or etcd
Headless serviceUses Kubernetes DNSUses DNS SRV records
Config formatDOCKER_VERNEMQ_* env varsEMQX_* env vars
Health check/metrics on port 8888/api/v5/status on port 18083

3. Helm Installation

Add Helm Repo

helm repo add vernemq https://vernemq.github.io/docker-vernemq
helm repo update

Install / Upgrade

helm upgrade --install vernemq vernemq/vernemq \
  -n <namespace> \
  -f values.yaml

Verify

kubectl get pods -n <namespace> -l app.kubernetes.io/name=vernemq
kubectl get svc -n <namespace>

4. Values Configuration

Full values.yaml

replicaCount: 3

image:
  repository: vernemq/vernemq
  tag: 2.0.1-alpine
  pullPolicy: IfNotPresent

nameOverride: ""
fullnameOverride: ""

service:
  enabled: true
  type: LoadBalancer
  annotations:
    cloud.google.com/load-balancer-type: Internal
  mqtt:
    enabled: true
    port: 1883
    targetPort: 1883
  api:
    enabled: true
    type: ClusterIP
    port: 8888
  mqtts:
    enabled: false
    port: 8883
    nodePort: 8883
  ws:
    enabled: false
    port: 8080
    nodePort: 8080
  wss:
    enabled: false
    port: 8443
    nodePort: 8443

headlessService:
  customPorts: []

ingress:
  className: ""
  enabled: false
  labels: {}
  annotations: {}
  hosts: []
  paths:
    - path: /
      pathType: ImplementationSpecific
  tls: []

resources: {}

securityContext:
  runAsUser: 10000
  runAsGroup: 10000
  fsGroup: 10000

rbac:
  create: true
  serviceAccount:
    create: true

persistentVolume:
  enabled: true
  accessModes:
    - ReadWriteOnce
  size: 5Gi
  storageClassName: standard

statefulset:
  podManagementPolicy: OrderedReady
  updateStrategy: RollingUpdate
  terminationGracePeriodSeconds: 60
  livenessProbe:
    initialDelaySeconds: 60
    periodSeconds: 10
    timeoutSeconds: 5
    successThreshold: 1
    failureThreshold: 3
  readinessProbe:
    initialDelaySeconds: 60
    periodSeconds: 10
    timeoutSeconds: 5
    successThreshold: 1
    failureThreshold: 3
  podAnnotations:
    prometheus.io/path: "/metrics"
    prometheus.io/scrape: "true"
    prometheus.io/scheme: "http"
    prometheus.io/servicemonitor: "true"
    prometheus.io/port: "8888"

pdb:
  enabled: false
  minAvailable: 1
  maxUnavailable: 0

additionalEnv:
  - name: DOCKER_VERNEMQ_ALLOW_REGISTER_DURING_NETSPLIT
    value: "on"
  - name: DOCKER_VERNEMQ_ALLOW_PUBLISH_DURING_NETSPLIT
    value: "on"
  - name: DOCKER_VERNEMQ_ALLOW_SUBSCRIBE_DURING_NETSPLIT
    value: "on"
  - name: DOCKER_VERNEMQ_ALLOW_UNSUBSCRIBE_DURING_NETSPLIT
    value: "on"
  - name: DOCKER_VERNEMQ_ACCEPT_EULA
    value: "yes"
  - name: DOCKER_VERNEMQ_ALLOW_ANONYMOUS
    value: "on"

acl:
  enabled: false
  content: |-
    topic #

Key Configuration Breakdown

SettingValuePurpose
replicaCount: 33-node clusterErlang cluster quorum
service.type: LoadBalancerGCP Internal LBDirect MQTT access
service.annotations: InternalInternal onlyNo public exposure
persistentVolume.size: 5GiPer-node storageOffline message queue
terminationGracePeriodSeconds: 60Graceful shutdownOffline queue migration
ALLOW_ANONYMOUS: onOpen authFor development (disable in prod)

Netsplit Handling

Env VariableValueEffect
ALLOW_REGISTER_DURING_NETSPLITonClients can connect during partition
ALLOW_PUBLISH_DURING_NETSPLITonMessages can be published
ALLOW_SUBSCRIBE_DURING_NETSPLITonSubscriptions can be created
ALLOW_UNSUBSCRIBE_DURING_NETSPLITonUnsubscriptions allowed

Warning: These settings are for development. In production, set to off to prevent split-brain issues.


5. Services & Networking

Service Architecture

graph LR
    subgraph EXTERNAL["External"]
        iot["IoT Devices"]
    end

    subgraph K8S["Kubernetes"]
        subgraph SVC["Services"]
            lb["LoadBalancer\n:1883 (Internal)"]
            headless["Headless\ncluster DNS"]
            api["ClusterIP\n:8888 (API)"]
        end
        subgraph PODS["VerneMQ Pods"]
            vmq0["vernemq-0"]
            vmq1["vernemq-1"]
            vmq2["vernemq-2"]
        end
    end

    iot --> lb
    lb --> vmq0
    lb --> vmq1
    lb --> vmq2
    headless --> vmq0
    headless --> vmq1
    headless --> vmq2
    api --> vmq0

Port Reference

PortProtocolService TypePurpose
1883TCPLoadBalancer (Internal)MQTT
8888TCPClusterIPREST API + metrics
8883TCP(disabled)MQTT/TLS
8080TCP(disabled)MQTT over WebSocket
8443TCP(disabled)MQTT over WSS

Internal LoadBalancer

service:
  type: LoadBalancer
  annotations:
    cloud.google.com/load-balancer-type: Internal
  mqtt:
    enabled: true
    port: 1883
    targetPort: 1883

This creates a GCP Internal TCP/UDP Load Balancer — MQTT traffic stays within the VPC.


6. Security & ACL

Anonymous Access

SettingValueProduction Recommendation
ALLOW_ANONYMOUSonSet to off
additionalEnv:
  - name: DOCKER_VERNEMQ_ALLOW_ANONYMOUS
    value: "off"  # Production

ACL Rules

acl:
  enabled: true
  content: |-
    # Allow specific user to publish/subscribe to all topics
    {allow, {user, "admin"}, publish, ["#"]}.
    {allow, {user, "admin"}, subscribe, ["#"]}.
    
    # Allow clients to subscribe to their own topic
    {allow, all, subscribe, ["clientid/${clientid}"]}.
    
    # Deny system topics
    {deny, all, subscribe, ["$SYS/#"]}.
    
    # Deny all other publish
    {deny, all, publish, ["#"]}.

ACL Syntax

PatternEffect
{allow, {user, "X"}, publish, ["topic"]}User X can publish to topic
{allow, all, subscribe, ["pattern"]}All users can subscribe to pattern
{deny, all, publish, ["#"]}Default deny all publish

TLS Configuration

# Enable in values.yaml
service:
  mqtts:
    enabled: true
    port: 8883
  wss:
    enabled: true
    port: 8443

# Mount TLS certificates
certificates:
  cafile: /etc/ssl/vernemq/ca.crt
  certfile: /etc/ssl/vernemq/tls.crt
  keyfile: /etc/ssl/vernemq/tls.key

7. EMQX vs VerneMQ Comparison

FeatureVerneMQEMQX
DeploymentHelm onlyHelm, Operator, YAML
ClusteringErlang native (automatic)DNS SRV or etcd
DashboardNone (CLI only)Built-in web UI
REST APILimited (vmq-admin)Full REST API v5
Rule engineNoYes (SQL-based)
WebhookLua hooksNative bridges
AuthenticationBuilt-in + pluginsJWT, HTTP, LDAP, PostgreSQL
AuthorizationACL fileFile, HTTP, PostgreSQL
WebSocketYesYes
TLSYesYes
PrometheusYes (port 8888)Yes (port 19001)
Lua scriptingYesYes
Offline messagesYes (per-node)Yes (persistent sessions)
Message queuingYes (QoS 1/2)Yes
Session persistencePer-nodeDistributed
Max connections~100K per node~5M per node
LicenseApache 2.0Apache 2.0 (Enterprise: commercial)

When to Use VerneMQ

Use CaseWhy VerneMQ
Simple MQTT deploymentLightweight, less config
Erlang-native clusteringAutomatic node discovery
No dashboard neededCLI-only management
Budget constraintsFully open-source
Existing Erlang infrastructureSame stack

When to Use EMQX

Use CaseWhy EMQX
Complex event processingBuilt-in rule engine
Need dashboardWeb UI for monitoring
High connection countBetter horizontal scaling
Multiple auth backendsJWT, HTTP, LDAP, PostgreSQL
Webhook integrationNative bridge support
Production SLAEnterprise support available

Migration: VerneMQ to EMQX

StepAction
1Deploy EMQX cluster alongside VerneMQ
2Update client connection strings to EMQX
3Verify client reconnection
4Monitor EMQX metrics
5Decommission VerneMQ

References