mosquitto 是一個開源的輕量級消息代理服務, 支持 MQTT-3.1 和 MQTT-3.1.1, 採用發佈訂閱模式. mosquitto 目前普遍用於手機設備, 底端傳感器, 嵌入式計算機的消息通訊, 是一個成熟的物聯網通訊服務方案. 做爲一個用 C 編寫的應用服務, mosquiitto 項目一樣提供了 C library 便於 MQTT 服務的拓展, 好比有名的 mosquitto-auth-plug.python
這裏咱們打算部署在 docker 容器內.mysql
參考 eclipse-mosquitto docker hub
構建目錄 /mosquitto
目錄結構是:linux
- mosquitto - config - mosquitto.conf - data - log
其中 mosquitto.conf
的內容是:git
persistence true persistence_location /mosquitto/data/ log_dest file /mosquitto/log/mosquitto.log
啓動命令:github
$ docker run -d -p 1883:1883 -p 9001:9001 -v mosquitto/:/mosquitto/ eclipse-mosquitto
參考: https://www.loraserver.io/gui...sql
注意: 在 docker 內部構建編譯環境的時候(尤爲是在 alpine 環境中)會常常出現:docker
temporary error (try again later)
這是由於在獲取像openssl
,build-base
之類包的時候, 須要從一些官方的鏡像庫中獲取, 而因爲國內的"某些"網絡緣由, DNS 每每不能直接解析到官方鏡像庫. 參考這篇文章:完全解決docker build時安裝軟件失敗問題, 能夠用tcpdump
觀察這一現象. 因此咱們最好先安排一下宿主機的 DNS.數據庫
根據阿里雲ECS啓動Docker容器沒法訪問外網這篇文章, 咱們能夠這樣來獲取本機 nameserver:json
$ cat /etc/resolv.conf nameserver 10.143.xx.xxx nameserver 10.143.xx.xxy options timeout:2 attempts:3 rotate single-request-reopen
這裏咱們能夠看到有可能有多個域名解析服務地址, 這是一種容災措施, 若是其中一個解析失敗了, 在若干次嘗試後會使用另一個服務器去解析. 咱們選取其中一個就好.segmentfault
而後修改/etc/docker/daemon.json
, 添加:
"dns": ["10.143.xx.xxx"]
重啓守護進程:
$ sudo systemctl daemon-reload // 最好也重啓一下docker $ sudo systemctl restart docker
固然, 若是網絡環境足夠好(好比在國外), 能夠不須要配置 DNS. 即便不配置 DNS, 也有必定的概率安裝軟件都成功.
如今發現單純改 DNS 仍是常常會下載軟件失敗, 只好修改容器的軟件源了. 在 Dockerfile 中將 aliyun 的軟件源寫入 /etc/apk/repositores
:
RUN echo "http://mirrors.aliyun.com/alpine/v3.4/main/" > /etc/apk/repositories
目錄結構以下:
- mosquitto - Dockerfile - config - mosquitto.conf - config.mk - data - log - src - postgres - Dockerfile - docker-compose.yml
其中config
放置 mosquitto 的啓動配置mosquitto.conf
和 mosquitto-auth-plug 的編譯配置config.mk
. data
, log
分別做爲 mosquitto 的數據存儲目錄和日誌存儲目錄, src
用於放置編譯須要的源文件.
mosquitto.conf 應該聲明 mosquitto-auth-plug 插件編譯以後生產的 auth-plug.so
的位置, 並說明 postgres 數據庫的鏈接用戶, 鏈接端口, 鏈接密碼, 以及處理權限和讀寫的 sql 語句. 參考:
Setting up ACL in Mosquitto using Postgres, 和MQTT authentication & authorization, 和mosquitto-auth-plug 在 github 上的 README.下面是一個可供參考和使用的 mosquitto.conf:
persistence true persistence_location /mosquitto/data/ log_dest file /mosquitto/log/mosquitto.log allow_anonymous false auth_plugin /mosquitto/src/mosquitto-auth-plug/auth-plug.so auth_opt_backends postgres auth_opt_host pgsql_db auth_opt_port 5432 auth_opt_dbname mosquitto auth_opt_user root auth_opt_pass root_password auth_opt_userquery SELECT password FROM account WHERE username = $1 limit 1 auth_opt_superquery SELECT COALESCE(COUNT(*), 0) FROM account WHERE username = $1 AND super = 1 auth_opt_aclquery SELECT topic FROM acls WHERE (username = $1) AND rw >= $2
這裏咱們打算將 postgres 做爲驗證後端, 所以 config.mk 應該是這樣的:
BACKEND_CDB ?= no BACKEND_MYSQL ?= no BACKEND_SQLITE ?= no BACKEND_REDIS ?= no BACKEND_POSTGRES ?= yes BACKEND_LDAP ?= no BACKEND_HTTP ?= no BACKEND_JWT ?= no BACKEND_MONGO ?= no BACKEND_FILES ?= no BACKEND_MEMCACHED ?= no # MOSQUITTO_SRC = /mosquitto/src/mosquitto MOSQUITTO_SRC = OPENSSLDIR = /usr/lib SUPPORT_DJANGO_HASHERS ?= no CFG_LDFLAGS = CFG_CFLAGS =
OPENSSLDIR
指定爲 /usr/lib
,MOSQUITTO_SRC
指定爲/mosquitto/src/mosquitto/src
, 也能夠不須要指定, 由於在容器中已經安裝了 mosquitto, 編輯腳本能夠從環境中獲取源碼地址, 這樣甚至後面咱們都不須要用 git 從 github 上拉取 mosquitto 的源碼了, 注意: 這裏最好不要指定, 由於在實際編譯的時候常常會發生報錯, 直接使用環境中便可.
鏡像的源來自 eclipse mosquitto 的 Docker Hub, 基於 alpine linux.
這是我根據編譯經驗寫的Dockerfile:
FROM eclipse-mosquitto WORKDIR /mosquitto ADD mosquitto/config /mosquitto/config RUN apk update && \ apk add wget && \ apk add mosquitto-dev && \ apk add postgresql-dev && \ apk add openssl && \ apk add build-base && \ mkdir /mosquitto/src && \ wget https://github.com/jpmens/mosquitto-auth-plug/archive/0.1.2.tar.gz \ -O /mosquitto/src/mosquitto-auth-plug.tar.gz && \ tar zxf /mosquitto/src/mosquitto-auth-plug.tar.gz -C /mosquitto/src && \ mv /mosquitto/src/mosquitto-auth-plug-0.1.2 /mosquitto/src/mosquitto-auth-plug && \ rm -rf /mosquitto/src/mosquitto_auth_plug.tar.gz && \ mv /mosquitto/config/config.mk /mosquitto/src/mosquitto-auth-plug/config.mk && \ cd /mosquitto/src/mosquitto-auth-plug && \ make clean && make EXPOSE 1881 9001
能夠看到這裏咱們安裝了以 postgresql 爲後端編譯 auth-plug 須要的依賴: mosquitto-dev
, postgresql-dev
, openssl
, 和 C 語言的 GCC 編譯環境build-base
. 用 git 拉取了 mosquitto 和 mosqtuitto-auth-plug 的源碼, 最好仍是直接從mosquitto-auth-plug/release中獲取發佈的穩定版本, 最後使用自定義的 config.mk
進行 make 編譯.
打算用容器編排工具 docker-compose 運行編譯了 mosquitto-auth-plug 的 mosquitto 容器, 下面是一個可供使用和參考的 docker-compose.yml:
version: '3' services: mosquitto: build: ./Dockerfile_mosquitto volumes: - ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf command: ['mosquitto', '-c', '/mosquitto/config/mosquitto.conf'] ports: - '1881:1881' - '9001:9001'
前面已經把 mosquitto 部署成功, 可是因爲這裏咱們打算讓 mosquitto 的權限認證和讀寫認證交給 postgres(否則爲何咱們要花那麼費勁去編譯 mosquitto-auth-plug?), 因此咱們還要部署一個 mosquitto 能夠訪問的 postgres 後端或者在已有的 postgres 上建立相關的表結構. 注意 postgres 後端的用戶, 密碼等應該和 mosquitto.conf 上配置的一致.
直接在原有的 docker-compose.yml 上加一個 service:
services: ... postgres_db: image: postgres environment: POSTGRES_DB: mosquitto POSTGRES_USER: root POSTGRES_PASSWORD: root_password
這裏咱們不暴露 postgres 的 5432 端口, 由於 docker 內部的同一個網絡能夠直接經過容器名字來訪問對應的容器, 並且咱們不但願容器外的其餘人能夠訪問數據庫. 注意這裏的POSTGERS_USER
應該和 mosquitto.conf 中的auth_opt_user
對應, 相似的也有POSTGRES_DB
, POSTGRES_PASSWORD
. 而容器名字postgres_db
能夠成爲auth_opt_host
的值, docker 會爲這個 host 作 DNS 解析.
而後咱們就能夠直接運行 postgres 容器:
$ docker-compose up -d
爲了知足 mosquitto-auth-plug 的業務需求, 須要爲 postgres 生成對應的表, 能夠參考mosquitto-auth-plug/examples/mysql.sql
CREATE TABLE account( id SERIAL, username TEXT NOT NULL, password TEXT, super smallint DEFAULT 0 NOT NULL, PRIMARY KEY (id) ); CREATE INDEX account_username ON account (username); CREATE TABLE acls ( id SERIAL, username TEXT NOT NULL, topic TEXT NOT NULL, rw INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (id) ); CREATE UNIQUE INDEX acls_user_topic ON acls (username, topic);
這些 SQL 語句也能夠寫在 Dockerfile 裏面做爲第一次構建時就執行的.
接着就能夠插入幾條測試數據了. 用戶的密碼生成須要使用 mosquitto-auth-plug 提供的密碼生成器, 它是以 PBKDF2 的形式儲存在數據庫後端的.
好比咱們 進入 mosquitto 容器內:
$ docker exec -it <mosquitto_auth_container_id> /bin/sh
爲用戶生成一個明文爲123456
的密碼:
$ /mosquitto/src/mosquitto-auth-plug/np Enter password: Re-enter same password: PBKDF2$sha256$901$Yf1FSeMi1j7OjWdW$u1MqFR7iIZj+m6P7vKDvuMx+oDDJW4ub
以後爲 account 表內爲test_user
添加一條記錄:
> INSERT INTO account(username, pasword, super) values ( 'test_user', 'PBKDF2$sha256$901$Yf1FSeMi1j7OjWdW$u1MqFR7iIZj+m6P7vKDvuMx+oDDJW4ub', 0)
並設定test_user
對於 topic /topic1
爲可讀可寫:
> INSERT INTO acls(username, topic, rw) values ( 'test_user', '/topic1', 2)
如今數據庫內的記錄是:
id | username | password | super ----+----------+---------------------------------------------------------------------+------- 1 | test_user | PBKDF2$sha256$901$S55aebUoogFy6XSf$E6PbQWDFQ06KLFlqqR3x+NBjg6ixjwez | 0 id | username | topic | rw ----+----------+------------+---- 1 | test_user| /topic1 | 2
意味着用戶 test_user 能夠用密碼123456
向 topic topic1
發佈主題和訂閱主題.
咱們這裏用 python 中一樣實現了 mqtt 協議的 paho-mqtt 進行測試, 若是生產環境是使用 python 的話, 也能夠直接使用這個庫做爲鏈接庫.
安裝很簡單的:
$ pip install paho-mqtt
應用程序代碼就放在 python-alpine 容器裏好了. 下面是一個可供參考和使用的 docker-compose.yml:
services: mqtt_server: image: python-alpine command: ['tail']
用 docker-compose up mqtt_server -d
使得該容器掛起, 而後用docker exec -it <container_id> /bin/sh
黑魔法進入容器. 編寫一個測試用的訂閱者(client.py):
import paho.mqtt.client as mqtt import time HOST = 'mosquitto' PORT = 1883 def client_loop(): client_id = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) client = mqtt.Client( client_id=client_id, clean_session=True) client.username_pw_set('test_user', '123456') client.on_connect = on_connect client.on_message = on_message client.connect(HOST, PORT, 60) client.loop_forever() def on_connect(client, userdata, flags, rc): print('Connected with result code ' + str(rc)) client.subscribe('/topic1') def on_message(client, userdata, msg): print(msg.topic + ' ' + msg.payload.decode('utf-8')) if __name__ == '__main__': client_loop()
而後編寫一個測試用的發佈者(publish.py):
import paho.mqtt.publish as publish import time HOST = 'mosquitto' PORT = 1883 if __name__ == '__main__': client_id = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) publish.single('topic1', 'hello mqtt', qos=2, hostname=HOST, port=PORT, client_id=client_id, auth={'username': 'test_user', 'password': '123456'})
當運行 client.py 以後, client 陷入監聽 mosquitto 的狀態, 等待收到發送在 topic topic1
的消息。 再運行一次 publish.py, 它往 topic1
發送了一個消息, 咱們能夠看到 client.py 就已經收到了.
咱們成功地搭建了一個 mosquitto 的消息代理服務, 併爲它編譯了 mosquitto-auth-plug, 能夠供後續後端消息業務的開發. 詳細代碼也能夠參考已經上傳到 github 的 example.