{"id":16243405,"url":"https://github.com/bkupidura/broker-ha","last_synced_at":"2025-07-12T13:05:51.214Z","repository":{"id":43390325,"uuid":"490705249","full_name":"bkupidura/broker-ha","owner":"bkupidura","description":"Golang MQTT broker with clustering capabilities","archived":false,"fork":false,"pushed_at":"2024-10-02T21:03:22.000Z","size":230,"stargazers_count":60,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-05-08T20:41:34.663Z","etag":null,"topics":["cluster","golang","iot","kubernetes","mqtt","mqtt-broker"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bkupidura.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-10T13:18:59.000Z","updated_at":"2024-10-29T13:01:12.000Z","dependencies_parsed_at":"2023-02-14T00:35:17.681Z","dependency_job_id":"42ddd873-d80a-46f7-bdf8-3d957a05cc6c","html_url":"https://github.com/bkupidura/broker-ha","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bkupidura/broker-ha","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkupidura%2Fbroker-ha","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkupidura%2Fbroker-ha/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkupidura%2Fbroker-ha/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkupidura%2Fbroker-ha/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bkupidura","download_url":"https://codeload.github.com/bkupidura/broker-ha/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkupidura%2Fbroker-ha/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264995202,"owners_count":23694907,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cluster","golang","iot","kubernetes","mqtt","mqtt-broker"],"created_at":"2024-10-10T14:14:51.934Z","updated_at":"2025-07-12T13:05:51.190Z","avatar_url":"https://github.com/bkupidura.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Overview\n\nBroker-HA is golang MQTT broker with clustering capabilities build for K8s.\nIts based on [mochi-co/mqtt](https://github.com/mochi-co/mqtt/) and [hashicorp/memberlist](https://github.com/hashicorp/memberlist).\n\nFeatures:\n- Paho MQTT 3.0 / 3.1.1 / 5.0 compatible (drop-in replacement for Mosquitto [MQTT 3.0/3.1.1/5.0])\n- Clustering!\n- HTTP API\n\n## Why\n\nI was unable to find any (working) open-source MQTT broker which allows to run highly available setups.\n\n## How it works\n\n`broker-ha` will try to build cluster with other instances in the cluster.  \nInitial cluster members are discovered with DNS SRV records.  \n\nWhen new member join cluster, every other node from cluster will send `retained` messages to it.\n\nWhen client will send MQTT message to any `broker-ha` instance, this instance will try to forward it to other cluster members.\n\nDNS SRV discovery is only used to discover initial (after startup) cluster members. After that member join/leave is handled by `memberlist`.\n\n## Limitations\n\n- Member auto-discovery requires DNS SRV (e.g K8s headless service)\n- [Shared subscriptions not supported](https://github.com/bkupidura/broker-ha/issues/9)\n\n\n## Configuration\n\n`broker-ha` is using [spf13/viper](https://github.com/spf13/viper) for config.\n\nConfig file should be located in `/config/config.yaml`.\n\n```\napi:\n  user:\n    admin: admin\ndiscovery:\n  domain: broker-headless.broker-ha.svc.cluster.local\n  subscription_size:\n    \"cluster:message_to\": 1024\nmqtt:\n  port: 1883\n  subscription_size:\n    \"cluster:message_from\": 1024\n    \"cluster:new_member\": 10\n  auth:\n    - username: test\n      password: test\n      allow: true\n    - client: someClientID\n      allow: true\n    - remote: 127.0.0.1\n      allow: true\n  acl:\n    // 0 = deny, 1 = read only, 2 = write only, 3 = read and write\n    - username: test\n      filters:\n        \"a/#\": 0\n        \"b/#\": 2\ncluster:\n  expected_members: 3\n  config:\n    secret_key: \"someSecretKey13$\"\n    tcp_timeout: 1000\n    push_pull_interval: 15000\n    probe_interval: 1000\n    probe_timeout: 200\n    gossip_interval: 100\n    gossip_to_the_dead_time: 15000\n    indirect_checks: 1\n    retransmit_mult: 2\n    suspicion_mult: 3\n```\n\nAll `cluster.config` options are used to configure `memberlist`. For more details check [hashicorp/memberlist](https://github.com/hashicorp/memberlist/blob/master/config.go).\n\nEvery config key can be set with environment variable e.g `BROKER_MQTT_PORT`.\n\nDefault config:\n\n```\ndiscovery:\n  subscription_size:\n    \"cluster:message_to\": 1024\nmqtt:\n  port: 1883\n  subscription_size:\n    \"cluster:message_from\": 1024\n    \"cluster:new_member\": 10\ncluster:\n  expected_members: 3\n  config:\n    probe_interval: 500\n```\n\n## K8s Manifest\n\n`broker-headless` service is used to discover initial cluster members.\n\n`mqtt` service is used to expose MQTT broker for clients.\n\n```\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: broker-ha\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: broker-headless\n  namespace: broker-ha\nspec:\n  clusterIP: None\n  selector:\n    app.kubernetes.io/name: broker-ha\n  ports:\n    - protocol: TCP\n      port: 7946\n      targetPort: 7946\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: mqtt\n  namespace: broker-ha\n  labels:\n    app.kubernetes.io/name: mqtt\nspec:\n  type: LoadBalancer\n  externalTrafficPolicy: Local\n  publishNotReadyAddresses: false\n  ports:\n    - name: mqtt\n      port: 1883\n      protocol: TCP\n  selector:\n    app.kubernetes.io/name: broker-ha\n\n---\napiVersion: v1\ndata:\n  config: |\n    discovery:\n      domain: broker-headless.broker-ha.svc.cluster.local\n    mqtt:\n      port: 1883\n      auth:\n        - username: test\n          password: test\n          allow: true\n    cluster:\n      config:\n        probe_interval: 500\n        secret_key: \"someSecretKey13$\"\nkind: ConfigMap\nmetadata:\n  name: broker-ha\n  namespace: broker-ha\n\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: broker-ha\n  namespace: broker-ha\n  labels:\n    app.kubernetes.io/name: broker-ha\nspec:\n  replicas: 3\n  strategy:\n    type: RollingUpdate\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: broker-ha\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: broker-ha\n      annotations:\n        prometheus.io/port: \"8080\"\n        prometheus.io/scrape: \"true\"\n    spec:\n      containers:\n      - name: broker-ha\n        image: ghcr.io/bkupidura/broker-ha:latest\n        volumeMounts:\n          - mountPath: /config\n            name: config\n        readinessProbe:\n          failureThreshold: 2\n          httpGet:\n            path: /ready\n            port: 8080\n          initialDelaySeconds: 30\n          periodSeconds: 2\n          successThreshold: 1\n          timeoutSeconds: 1\n        livenessProbe:\n          failureThreshold: 3\n          httpGet:\n            path: /healthz\n            port: 8080\n          initialDelaySeconds: 70\n          periodSeconds: 5\n          successThreshold: 1\n          timeoutSeconds: 1\n      terminationGracePeriodSeconds: 60\n      dnsPolicy: ClusterFirstWithHostNet\n      volumes:\n        - name: config\n          configMap:\n            name: broker-ha\n            items:\n              - key: config\n                path: config.yaml\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n            - weight: 1\n              podAffinityTerm:\n                labelSelector:\n                  matchExpressions:\n                    - key: app.kubernetes.io/name\n                      operator: In\n                      values:\n                        - broker-ha\n                topologyKey: kubernetes.io/hostname\n```\n\n## Event bus\n`broker-ha` use internaly event bus (`internal/bus`) to communicate between components (`broker`, `discovery`, `api`).\n\nEvents are passed to subscribers over golang channel.\n\nList of channels:\n- `cluster:message_from` - event is generated when new MQTT message from cluster is received. Message will be published to local broker instance.\n- `cluster:message_to` - event is generated when new MQTT message is published to broker. Message will be published to cluster for other instances.\n- `cluster:new_member` - event is generated when new member joins cluster.\n\n## HTTP\n\nBy default `/api` and `/debug` prefixed endpoints are available without authentication.\nIf you need authentication for those endpoints, add to `config.yaml` `api.user` key to enable HTTP basic auth.\n\n```\napi:\n  user:\n    username: password\n```\n\nAll `/api` prefixed endpoints will return `200` on success, `400` on wrong request or `500` when request cant be executed.\n\nSome `/api` endpoints are supporting `/proxy` prefix. This allow to send request to single `broker-ha` instance and get combined response from every node in the cluster.\n\n### :8080/metrics endpoint\n\nExpose Prometheus metrics.\n\ncURL:\n```\ncurl localhost:8080/metrics\n```\n\nResponse:\n```\n# HELP broker_clients_connected Number of connected clients\n# TYPE broker_clients_connected gauge\nbroker_clients_connected 0\n# HELP broker_cluster_member_health Cluster member health\n# TYPE broker_cluster_member_health gauge\nbroker_cluster_member_health 0\n# HELP broker_cluster_members Number of cluster members\n# TYPE broker_cluster_members gauge\nbroker_cluster_members 1\n[...]\n```\n\n### :8080/ready endpoint\n\nExpose K8s [readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) endpoint.\n\ncURL:\n```\ncurl localhost:8080/ready\n```\n\nResponse:\n```\n{\"status\":\"up\",\"details\":{\"readiness_cluster_health\":{\"status\":\"up\",\"timestamp\":\"2022-09-04T08:08:27.650382Z\"}}}\n```\n\nResponse code:\n- 200 when ready\n- 503 when not ready\n\n### :8080/healthz endpoint\n\nExpose K8s [liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) endpoint.\n\ncURL:\n```\ncurl localhost:8080/healthz\n```\n\nResponse:\n```\n{\"status\":\"up\",\"details\":{\"liveness_cluster_discovered_members\":{\"status\":\"up\",\"timestamp\":\"2022-09-04T08:11:14.993799Z\"},\"liveness_cluster_health\":{\"status\":\"up\",\"timestamp\":\"2022-09-04T08:11:14.993785Z\"},\"liveness_member_in_cluster\":{\"status\":\"up\",\"timestamp\":\"2022-09-04T08:11:14.993755Z\"}}}\n```\n\nResponse code:\n- 200 when health\n- 503 when not healthy\n\n### :8080/debug endpoint\n\nExpose [net/http/pprof](https://pkg.go.dev/net/http/pprof) endpoint.\n\ncURL:\n```\ncurl localhost:8080/debug\n```\n\n### :8080/api/sse endpoint\n\nExpose Server-Sent Events stream.\n\ncURL:\n```\ncurl localhost:8080/api/sse -d '{\"filters\": []}' -H 'Content-Type: application/json'\n```\n\nRequest params:\n- filters - list of event bus channels we want to get messages from (default [], all channels)\n- channel_size - size of subscription channel (how many messages should be buffered if they cant be delivered to SSE immediately) (default 100)\n\nResponse:\n```\nevent: cluster:message_to\ndata: {\"Node\":[\"all\"],\"Payload\":\"MTY2MjI3OTQ4ODQyMg==\",\"Topic\":\"dead-man-switch/mqtt-ping\",\"Retain\":false,\"Qos\":0}\nevent: sse:keepalive:[::1]:53398\ndata: \"keepalive\"\nevent: cluster:message_from\ndata: {\"Payload\":\"MA==\",\"Topic\":\"red/action/event\",\"Retain\":false,\"Qos\":0}\n```\n\n### :8080/api/discovery/members | :8080/proxy/api/discovery/members\n\nReturn discovery (memberlist) members.\n\ncURL:\n```\ncurl localhost:8080/api/discovery/members\n```\n\nResponse:\n```\n[{\"Name\":\"broker-ha-6ffc959cff-v5495\",\"Addr\":\"10.42.2.242\",\"Port\":7946,\"Meta\":\"\",\"State\":0,\"PMin\":1,\"PMax\":5,\"PCur\":2,\"DMin\":0,\"DMax\":0,\"DCur\":0},{\"Name\":\"broker-ha-6ffc959cff-v62ct\",\"Addr\":\"10.42.1.14\",\"Port\":7946,\"Meta\":\"\",\"State\":0,\"PMin\":1,\"PMax\":5,\"PCur\":2,\"DMin\":0,\"DMax\":0,\"DCur\":0},{\"Name\":\"broker-ha-6ffc959cff-mrzrk\",\"Addr\":\"10.42.0.205\",\"Port\":7946,\"Meta\":\"\",\"State\":0,\"PMin\":1,\"PMax\":5,\"PCur\":2,\"DMin\":0,\"DMax\":0,\"DCur\":0}]\n```\n\n### :8080/api/mqtt/clients | :8080/proxy/api/mqtt/clients\n\nReturn MQTT broker clients.\n\ncURL:\n```\ncurl localhost:8080/api/mqtt/clients\n```\n\nResponse:\n```\n[{\"ID\":\"cf1up11t673b49rllusg\",\"ProtocolVersion\":4,\"Username\":\"recorder\",\"CleanSession\":true,\"Done\":false,\"Subscriptions\":{\"recorder\":{\"ShareName\":null,\"Filter\":\"recorder\",\"Identifier\":0,\"Identifiers\":null,\"RetainHandling\":0,\"Qos\":2,\"RetainAsPublished\":false,\"NoLocal\":false}}}]\n```\n\n### :8080/api/mqtt/client/stop\n\nStop (disconnect) MQTT client.\n\ncURL:\n```\ncurl localhost:8080/api/mqtt/client/stop -d '{\"client_id\": \"cf1up11t673b49rllusg\"}' -H 'Content-Type: application/json'\n```\n\nRequest params:\n- client_id - MQTT client ID.\n\n### :8080/api/mqtt/client/inflight | :8080/proxy/api/mqtt/client/inflight\n\nReturn MQTT in-flight messages for client.\n\ncURL:\n```\ncurl localhost:8080/api/mqtt/client/inflight -d '{\"client_id\": \"akjnd9qjamz\"}' -H 'Content-Type: application/json'\n```\n\nRequest params:\n- client_id - MQTT client ID.\n\nResponse:\n```\n[{\"Connect\":{\"willProperties\":{\"cd\":null,\"si\":null,\"ad\":null,\"user\":null,\"ct\":\"\",\"rt\":\"\",\"aci\":\"\",\"am\":\"\",\"ri\":\"\",\"sr\":\"\",\"rs\":\"\",\"me\":0,\"sei\":0,\"wdi\":0,\"mps\":0,\"ska\":0,\"rm\":0,\"tam\":0,\"ta\":0,\"pf\":0,\"fpf\":false,\"fsei\":false,\"fska\":false,\"rpi\":0,\"frpi\":false,\"rri\":0,\"fta\":false,\"mqos\":0,\"fmqos\":false,\"ra\":0,\"fra\":false,\"wsa\":0,\"fwsa\":false,\"sida\":0,\"fsida\":false,\"ssa\":0,\"fssa\":false},\"password\":null,\"username\":null,\"protocolName\":null,\"willPayload\":null,\"clientId\":\"\",\"willTopic\":\"\",\"keepalive\":0,\"passwordFlag\":false,\"usernameFlag\":false,\"willQos\":0,\"willFlag\":false,\"willRetain\":false,\"clean\":false},\"Properties\":{\"cd\":null,\"si\":[0],\"ad\":null,\"user\":null,\"ct\":\"\",\"rt\":\"\",\"aci\":\"\",\"am\":\"\",\"ri\":\"\",\"sr\":\"\",\"rs\":\"\",\"me\":0,\"sei\":0,\"wdi\":0,\"mps\":0,\"ska\":0,\"rm\":0,\"tam\":0,\"ta\":0,\"pf\":0,\"fpf\":false,\"fsei\":false,\"fska\":false,\"rpi\":0,\"frpi\":false,\"rri\":0,\"fta\":false,\"mqos\":0,\"fmqos\":false,\"ra\":0,\"fra\":false,\"wsa\":0,\"fwsa\":false,\"sida\":0,\"fsida\":false,\"ssa\":0,\"fssa\":false},\"Payload\":\"dGVzdA==\",\"ReasonCodes\":null,\"Filters\":null,\"TopicName\":\"TestMqttClientInflightHandler\",\"Origin\":\"inline\",\"FixedHeader\":{\"remaining\":0,\"type\":3,\"qos\":2,\"dup\":false,\"retain\":true},\"Created\":1673806755,\"Expiry\":1673807355,\"Mods\":{\"MaxSize\":0,\"DisallowProblemInfo\":false,\"AllowResponseInfo\":false},\"PacketID\":1,\"ProtocolVersion\":4,\"SessionPresent\":false,\"ReasonCode\":0,\"ReservedBit\":0}]\n```\n\n### :8080/api/mqtt/topic/messages | :8080/proxy/api/mqtt/topic/messages\n\nReturn MQTT retained messages for topic.\n\ncURL:\n```\ncurl localhost:8080/api/mqtt/topic/messages -d '{\"topic\": \"#\"}' -H 'Content-Type: application/json'\n```\n\nRequest params:\n- topic - MQTT subscription topic (MQTT wildcards supported)\n\nResponse:\n```\n[{\"Connect\":{\"willProperties\":{\"cd\":null,\"si\":null,\"ad\":null,\"user\":null,\"ct\":\"\",\"rt\":\"\",\"aci\":\"\",\"am\":\"\",\"ri\":\"\",\"sr\":\"\",\"rs\":\"\",\"me\":0,\"sei\":0,\"wdi\":0,\"mps\":0,\"ska\":0,\"rm\":0,\"tam\":0,\"ta\":0,\"pf\":0,\"fpf\":false,\"fsei\":false,\"fska\":false,\"rpi\":0,\"frpi\":false,\"rri\":0,\"fta\":false,\"mqos\":0,\"fmqos\":false,\"ra\":0,\"fra\":false,\"wsa\":0,\"fwsa\":false,\"sida\":0,\"fsida\":false,\"ssa\":0,\"fssa\":false},\"password\":null,\"username\":null,\"protocolName\":null,\"willPayload\":null,\"clientId\":\"\",\"willTopic\":\"\",\"keepalive\":0,\"passwordFlag\":false,\"usernameFlag\":false,\"willQos\":0,\"willFlag\":false,\"willRetain\":false,\"clean\":false},\"Properties\":{\"cd\":null,\"si\":null,\"ad\":null,\"user\":null,\"ct\":\"\",\"rt\":\"\",\"aci\":\"\",\"am\":\"\",\"ri\":\"\",\"sr\":\"\",\"rs\":\"\",\"me\":0,\"sei\":0,\"wdi\":0,\"mps\":0,\"ska\":0,\"rm\":0,\"tam\":0,\"ta\":0,\"pf\":0,\"fpf\":false,\"fsei\":false,\"fska\":false,\"rpi\":0,\"frpi\":false,\"rri\":0,\"fta\":false,\"mqos\":0,\"fmqos\":false,\"ra\":0,\"fra\":false,\"wsa\":0,\"fwsa\":false,\"sida\":0,\"fsida\":false,\"ssa\":0,\"fssa\":false},\"Payload\":\"dGVzdA==\",\"ReasonCodes\":null,\"Filters\":null,\"TopicName\":\"TestMqttTopicMessagesHandler\",\"Origin\":\"inline\",\"FixedHeader\":{\"remaining\":0,\"type\":3,\"qos\":0,\"dup\":false,\"retain\":true},\"Created\":1673825332,\"Expiry\":0,\"Mods\":{\"MaxSize\":0,\"DisallowProblemInfo\":false,\"AllowResponseInfo\":false},\"PacketID\":0,\"ProtocolVersion\":4,\"SessionPresent\":false,\"ReasonCode\":0,\"ReservedBit\":0}]\n```\n\n### :8080/api/mqtt/topic/subscribers | :8080/proxy/api/mqtt/topic/subscribers\n\nReturn MQTT client ID and QoS for subscription topic.\n\ncURL:\n```\ncurl localhost:8080/api/mqtt/topic/subscribers -d '{\"topic\": \"something\"}' -H 'Content-Type: application/json'\n```\n\nRequest params:\n- topic - MQTT subscription topic\n\nResponse:\n```\n{\"Shared\":{},\"SharedSelected\":{},\"Subscriptions\":{\"cf1up11t673b49rllusg\":{\"ShareName\":null,\"Filter\":\"recorder\",\"Identifier\":0,\"Identifiers\":{\"recorder\":0},\"RetainHandling\":0,\"Qos\":2,\"RetainAsPublished\":false,\"NoLocal\":false}}}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbkupidura%2Fbroker-ha","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbkupidura%2Fbroker-ha","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbkupidura%2Fbroker-ha/lists"}